回想当初在大学玩51单片机的时候,实验室的老师第一个任务,就是设计一个基于51单片机的LED流水灯设计,并实现几种样式。第二个任务,就是设计一个基于51单片机的按键控制LED流水灯样式的设计。需要自己设计硬件图、画protel电路图,并设计出PCB,实现keil和proteus的联调,然后焊接电路板,实现其功能。那时候什么都不懂,秉这一股冲劲,各种百度、看书,那时候郭天祥的51单片机视频超火,所以那时候基本以他的书和视频学得,牛人,膜拜。
所以,这主要讲关于按键最简单的字符驱动,通过设置连接该引脚为输入,并读取其数据寄存器,来判断按键按下,然后在整体的讲述android的应用如何调用led和按键的字符设备,并实现简单的功能测试(按键按下,led亮,按键松开,led灭)。下一节,也是最后一节,主要讲字符设备中一些关于定时器、中断、竞争一些机制。废话有点多,进入主题:
1. 按键最简单的驱动程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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");
关于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
#define LOG_TAG "RfidSerialPort"
#include "utils/Log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "button_test.h"
#include
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_led error: %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);
else
write(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_button error: %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.
*/
//notice
static 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;
}
Button_test.h
#include
#ifndef _Included_button_test
#define _Included_button_test
#ifdef __cplusplus
extern "C" {
#endif
jint 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
Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES:= \
button_test.cpp
LOCAL_SHARED_LIBRARIES := \
libutils \
libhardware \
libdl
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE)
LOCAL_MODULE:= libButtonTest
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
先回想一下之前测试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.编写应用程序。运行应用程序,可以看出,按下按键灯亮,松开灯灭。