初入android驱动之字符设备(三)

回想当初在大学玩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)


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移动开发)