標籤:字元裝置 android驅動開發 jni
回想當初在大學玩51單片機的時候,實驗室的老師第一個任務,就是設計一個基於51單片機的LED流水燈設計,並實現幾種樣式。第二個任務,就是設計一個基於51單片機的按鍵控制LED流水燈樣式的設計。需要自己設計硬體圖、畫protel電路圖,並設計出PCB,實現keil和proteus的聯調,然後焊接電路板,實現其功能。那時候什麼都不懂,秉這一股衝勁,各種百度、看書,那時候郭天祥的51單片機視頻超火,所以那時候基本以他的書和視頻學得,牛人,膜拜。
所以,這主要講關於按鍵最簡單的字元驅動,通過設定串連該引腳為輸入,並讀取其資料寄存器,來判斷按鍵按下,然後在整體的講述android的應用如何調用led和按鍵的字元裝置,並實現簡單的功能測試(按鍵按下,led亮,按鍵鬆開,led滅)。下一節,也是最後一節,主要講字元裝置中一些關於定時器、中斷、競爭一些機制。廢話有點多,進入主題:
1. 按鍵最簡單的驅動程式:
<span style="font-size:14px;">#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/input.h>#include <linux/platform_device.h>#include <linux/miscdevice.h>#include <mach/gpio.h>#include <linux/io.h>#include <mach/hardware.h>#include <linux/delay.h>#include <asm/irq.h>#include <asm/uaccess.h>static struct class *buttondrv_class;static struct device *buttondrv_class_dev;int major;volatile unsigned long *GPCCON;volatile unsigned long *GPCDAT;static int button_drv_open(struct inode *inode, struct file *file){printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>button_drv_open\n");*GPCCON &= ~((0xf<<(2*4)) | (0xf<<(3*4))); *GPCCON |= (0<<(2*4) | (0<<(3*4)));return 0;}static int button_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *offp){printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>button_drv_read\n");unsigned char k_val[2];int regval;regval = *GPCDAT;k_val[0] = (regval & (1<<2)) ? 1 : 0;k_val[1] = (regval & (1<<3)) ? 1 : 0;printk("keyvalue[0]=%02x keyvalue[1]=%02x \n",k_val[0],k_val[1]);copy_to_user(buf, k_val, sizeof(k_val));return sizeof(k_val);}static struct file_operations button_drv_fops = { .owner = THIS_MODULE, .open = button_drv_open, .read=button_drv_read,};static int button_drv_init(void){printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>button_drv_init\n"); GPCCON = (volatile unsigned long *)ioremap(0xE0200080, 8);GPCDAT= GPCCON + 1;if (!GPCCON) {return -EIO;}major = register_chrdev(0, "button_drv", &button_drv_fops); buttondrv_class = class_create(THIS_MODULE, "buttondrv");buttondrv_class_dev = device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); return 0;}static void button_drv_exit(void){printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>button_drv_exit\n");unregister_chrdev(major, "button_drv"); device_unregister(buttondrv_class_dev);class_destroy(buttondrv_class);iounmap(GPCCON);}module_init(button_drv_init);module_exit(button_drv_exit);MODULE_LICENSE("GPL");</span><span style="font-size:24px;"></span> 關於demo的簡單說明
按鍵這裡有兩個,一個接GPC12,GPC13,想想最早操作51單片機的時候,檢測按鍵最簡單的做好就是,把該引腳設為輸入,然後讀取該引腳的狀態。這裡S5PV210的字元裝置,這裡也先試試。
首先,使用者或者上層APP,對於按鍵的功能需求,無非就是通過按下按鍵,讀取按鍵的不同狀態,從而去做某些事情。所以,按鍵,就是上報某種事件或者說是資訊。當然,不同的按鍵類型,各自的功能也會有差別。這裡所說的是最普通的按鍵。
1. ioremap:根據引腳映射其物理地址 2. 設定為輸入:*GPCCON &= ~((0xf<<(2*4)) | (0xf<<(3*4))); *GPCCON |= (0<<(2*4) | (0<<(3*4)));
3.在read函數中,讀取該引腳DAT的值。regval = *GPCDAT;
k_val[0] = (regval & (1<<2)) ? 1 : 0;
k_val[1] = (regval & (1<<3)) ? 1 : 0;
2. jni共用庫的編寫
2.1 源碼
Button_test.cpp
<span style="font-size:14px;">#define LOG_TAG "RfidSerialPort"#include "utils/Log.h"#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <errno.h> #include <string.h>#include <jni.h>#include "button_test.h"#include <ctype.h>static int fd_led=-1;static int fd_button=-1;jint led_open(JNIEnv *env, jobject thiz){LOGE("led_open >>>>>>>>>>>>>>>>>>>>>>>>>>");fd_led = open("/dev/led", O_RDWR);if (fd_led < 0){ LOGE("open fd_lederror: %s\n", strerror(errno)); return -1;}return 0;}jint led_close(JNIEnv *env, jobject thiz){close(fd_led);return 0;}jint led_set(JNIEnv *env, jobject thiz, jint state){if(state==1)write(fd_led,"1",1);elsewrite(fd_led,"0",1);return 0;}jint button_open(JNIEnv *env, jobject thiz){LOGE("button_open >>>>>>>>>>>>>>>>>>>>>>>>>>");fd_button = open("/dev/button", O_RDWR);if (fd_button < 0){ LOGE("open fd_buttonerror: %s\n", strerror(errno)); return -1;}return 0;}jint button_close(JNIEnv *env, jobject thiz){close(fd_button);return 0;}jint button_get(JNIEnv *env, jobject thiz, jbyteArray state){LOGE("button_get >>>>>>>>>>>>>>>>>>>>>>>>>>");jbyte *jstate = env->GetByteArrayElements(state, NULL);int reply=-1;char value[2];memset(value,0,2);reply=read(fd_button,value,2);LOGE("value[0]=%02x value[1]=%02x ",value[0],value[1]);if(reply<0)return -1;memcpy(jstate,value,2);env->ReleaseByteArrayElements( state, jstate, 0);return 0;}static JNINativeMethod gMethods[] = {{"led_open", "()I" , (void*)led_open},{"led_close", "()I" , (void*)led_close},{"led_set", "(I)I" , (void*)led_set},{"button_open", "()I" , (void*)button_open},{"button_close", "()I" , (void*)button_close},{"button_get", "([B)I" , (void*)button_get},}; /* * Register several native methods for one class. */ //noticestatic const char *classPathName = "com/example/button_test/ButtonTest";static int registerNatives(JNIEnv* env){ jclass clazz; clazz = env->FindClass(classPathName); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'", classPathName); return JNI_FALSE; }else{ LOGI("find class sucess"); } if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) { LOGE("RegisterNatives failed for '%s'", classPathName); return JNI_FALSE; } return JNI_TRUE;}/* * **This is called by the VM when the shared library is first loaded. * */jint JNI_OnLoad(JavaVM* vm, void* reserved){ jint result = -1; JNIEnv* env = NULL; LOGI("JNI_OnLoad"); if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed"); goto bail; } if (registerNatives(env) != JNI_TRUE) { LOGE("ERROR: registerNatives failed"); goto bail; } result = JNI_VERSION_1_4;bail: return result;}</span><span style="font-size:18px;"> </span>Button_test.h
<span style="font-size:14px;">#include <jni.h>#ifndef _Included_button_test#define _Included_button_test#ifdef __cplusplusextern "C" {#endifjint led_open(JNIEnv *env, jobject thiz);jint led_close(JNIEnv *env, jobject thiz);jint led_set(JNIEnv *env, jobject thiz,int stat);jint button_open(JNIEnv *env, jobject thiz);jint button_close(JNIEnv *env, jobject thiz);jint button_get(JNIEnv *env, jobject thiz,jbyteArray state);#ifdef __cplusplus}#endif#endif</span><span style="font-size:18px;"></span>
Android.mk
<span style="font-size:14px;">LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := engLOCAL_SRC_FILES:= button_test.cppLOCAL_SHARED_LIBRARIES := libutils libhardware libdl LOCAL_C_INCLUDES += $(JNI_H_INCLUDE) LOCAL_MODULE:= libButtonTestLOCAL_PRELINK_MODULE := falseinclude $(BUILD_SHARED_LIBRARY)</span>
2.2 源碼簡單的分析
先回想一下之前測試led的程式,可知道,led主要調用三個方法,open、write、close,同button也為三個,open、read、close。
現在知道native需要給上層app提供6個方法,那怎麼去實現了?
這裡需要知道jni(java native interface),想詳細瞭解其機制,可看《深入理解android卷》第二章(若需要一些資料,可以留言)。這裡主要是如何去做,具體的原理性東西,需要你事後去學習。簡單來說,jni就是在native(c、c++)和java之間的一座橋樑。同時,用時一定要注意代碼的嚴謹性,容易出錯。jni有靜態註冊和動態註冊的兩種方式,這裡用的是動態註冊。
button_test.cpp
首先從後面看起:
JNI_OnLoad:動態註冊才用到,在java 調用system.loadlibrary方法時,就會調用JNI_OnLoad,在這個函數裡面,主要調用registerNatives方法。
registerNatives:1. clazz = env->FindClass(classPathName) 這個是關聯native和java的class,很主要,路徑錯誤的話,在java載入so之後,會出現找不到native方法的錯誤。
2. env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0 這個主要是註冊函數的關聯關係,可以串連為建立了一直從native方法到java class方法的一直雙向映射。
JNINativeMethod:這個就是方法映射表,這裡得注意參數和返回值的簽名資訊。看到這,基本的架構已經出來了。
button_test.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
這裡因為是c++的代碼,所以考慮到與C的相容問題,故加上這一部分。
Android.mk
上一部分已經介紹過了,這裡 include $(BUILD_SHARED_LIBRARY),就是把模組編譯成動態共用程式庫。
那麼,你在android環境下或者ndk下編譯,會產生libButtonTest.so.
3 APP的編寫
這裡主要有兩部分,一部分匯入native函數,一部分調用native方法。
ButtonTest.java
package com.example.button_test;import android.content.Context;import android.util.Log;public class ButtonTest {static{ System.loadLibrary("ButtonTest");}public native int led_open();public native int led_close();public native int led_set(int state);public native int button_open();public native int button_close();public native int button_get(byte state[]);}這裡System.loadLibrary("ButtonTest"),不需要寫成libButtonTest.so。下面那些就是映射表裡面的native函數,注意參數、返回值的一致。
package com.example.button_test; 與jni中的static const char *classPathName = "com/example/button_test/ButtonTest" 一致。
MainActivity.java
package com.example.button_test;import android.support.v7.app.ActionBarActivity;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import com.example.button_test.ButtonTest;public class MainActivity extends ActionBarActivity {ButtonTest Test=new ButtonTest(); // notice byte state[]={0,0};int flag=1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Test.led_open(); Test.button_open(); for(;;) { Test.button_get(state); if(state[1] == 1 || state[0] == 1) { Test.led_set(1); flag++; if(flag>20) break; } else { Test.led_set(0); flag++; if(flag>20) break; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } Test.button_close(); Test.led_close(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }這裡需要對eclipse的瞭解,如何建立一個android的項目。
1.import com.example.button_test.ButtonTest; 這裡匯入ButtonTest到本地
2.ButtonTest Test=new ButtonTest(); 定義一個ButtonTest的class。
3. 在函數中調用native方法即可。
備忘:這整個過程中,不可能一次性寫好,這裡主要用到列印的調試手段,對於一些比較難解決的問題,可能就需要藉助一些工具。整個流程:
1.把兩個驅動編號,把ko push到android裝置中,insmod載入兩個驅動,並更改其裝置節點的許可權,chmod 777 /dev/led && chmod 777 dev/button ,不然jni中的open方法,會開啟失敗。
2.編寫jni庫。把產生的so庫push到system/lib下,當然,你也可以放在你eclipse的項目libs/armeabi/下,一般建議放在工程項目下。
3.編寫應用程式。運行應用程式,可以看出,按下按鍵燈亮,鬆開燈滅。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
初入android驅動之字元裝置(三)