========================
驱动:
内核:android-kernel 2.6.36 (必须对应你的板子上内核,不然会出现insmod错误)
目的:通过android应用层用户空间访问内核空间驱动程序。
实验:Button控件,点亮和熄灭LED。
注明:android应用层访问驱动,一般有2种方法来访问到硬件驱动程序。
这里讲解个人看法,各位别介意。
1: 应用层 ---> framwork层JNI ---> 驱动c
2: 应用层 ---> framwork层JNI ---> 硬件抽象层HAL ----> 驱动c
2种方法,各有各的好处,第1种,方便与驱动调试实验,只要编译好ko文件和libxxx.so文件,放入小机就可以立即调试了。
第2种JNI方法有些改变和增加了HAl,对驱动有了封装,简单来讲,硬件驱动程序一方面分布在Linux内核中,另一方面分布在用户空间的硬件抽象层中。不要误会android把驱动分成两半了,其实android只是在驱动的调用接口上在增加一层对应接口功能来让framwork层JNI来调用。比如,驱动有read接口,HAl层就是封装read接口,做一个 hal_read接口,里面调用read接口,然后把hal_read接口给JNI调用。
明白了吗?这方面有人讲得非常好,请点击此:
http://blog.csdn.net/luoshengyang/article/details/6575988
好处就是对对厂家来说能把商业秘密隐藏起来,我们做驱动实验的话,操作会极其复杂,不过对理解android整个系统都是极其用用的,因为它从下到上涉及到了android系统的硬件驱动层,硬件抽象层,运行时库和应用程序框架层等等。
这里目前只将第1种方法的实现:在此之前,请大家了解下JNI的编程方法,JNI是一种为JAVA和C、C++之间能互相访问所提供的编程接口。推荐此教材(见附件):
===============================================================
驱动:
- #include <linux/module.h>
- #include <linux/moduleparam.h>
- #include <linux/types.h>
- #include <linux/errno.h>
- #include <linux/kernel.h>
- #include <linux/fs.h> ^M
- #include <linux/ioport.h>
- #include <linux/platform_device.h>
- #include <linux/init.h>
- #include <linux/uaccess.h>
- #include <linux/io.h>
- #include <linux/gpio.h>
- #include <linux/device.h>
- #include <linux/cdev.h>
- #include <linux/slab.h>
-
-
-
- #define CMD_FLAG 'i'
- #define LED_ON _IOR(CMD_FLAG,0x00000000,__u32)
- #define LED_OFF _IOR(CMD_FLAG,0x00000001,__u32)
-
- static int major =0;
- static struct class *led_class;
- struct cdev_led {
- struct cdev cdev;
- };
- struct cdev_led *led_dev;
-
- static int led_ioctl(struct file* filp,unsigned int cmd,unsigned long argv)
- {
- printk(KERN_INFO "entry kernel.... \n");
-
- switch(cmd)
- {
- case LED_ON:
- {
- gpio_set_value(S3C64XX_GPK(4),0);
- printk(KERN_INFO "led on \n");
- break;
- }
- case LED_OFF:
- {
- gpio_set_value(S3C64XX_GPK(4),1);
- printk(KERN_INFO "led off \n");
- break;
- }
- default:
- return -EINVAL;
- }
- return 0;
- }
-
-
-
- static int led_open(struct inode* i_node,struct file* filp)
- {
- printk(KERN_INFO "open init.... \n");
- int err;
- err = gpio_request(S3C64XX_GPK(4),"led1");
- if(err<0)
- {
- printk(KERN_INFO "gpio request faile \n");
- return err;
- }
- gpio_direction_output(S3C64XX_GPK(4),1);
-
- return 0;
- }
-
-
- static void led_close(struct inode* i_node,struct file* filp)
- {
- printk(KERN_INFO "close init \n");
- gpio_free(S3C64XX_GPK(4));
- return ;
- }
-
-
- struct file_operations fops={
- .owner = THIS_MODULE,
- .open = led_open,
- .unlocked_ioctl = led_ioctl,
- .release= led_close,
- };
-
- static int __init led_init(void)
- {
- printk(KERN_INFO "init .... \n");
- dev_t dev_no;
- int result,err;
- err = alloc_chrdev_region(&dev_no,0,1,"my_led");
- if(err<0)
- {
- printk(KERN_INFO "ERROR\n");
- return err;
- }
- major = MAJOR(dev_no);
- led_dev = kmalloc(sizeof(struct cdev_led),GFP_KERNEL);
- if(!led_dev)
- {
- result = -ENOMEM;
- goto fail_malloc;
- }
- memset(led_dev,0,sizeof(led_dev));
-
- cdev_init(&led_dev->cdev,&fops);
- led_dev->cdev.owner = THIS_MODULE;
- result = cdev_add(&led_dev->cdev,dev_no,1);
- if(result <0)
- { printk(KERN_INFO "error\n");
- goto fail_add;
- }
- led_class = class_create(THIS_MODULE,"myled");
- device_create(led_class,NULL,MKDEV(major,0),NULL,"myled");
- return 0;
- fail_add:
- kfree(led_dev);
- fail_malloc:
- unregister_chrdev_region(dev_no,1);
- return result;
-
- }
-
- static void __exit led_exit(void)
- {
- dev_t dev_no=MKDEV(major,0);
-
- unregister_chrdev_region(dev_no,1);
- cdev_del(&led_dev->cdev);
- kfree(led_dev);
- device_destroy(led_class,dev_no);
- class_destroy(led_class);
- printk(KERN_INFO "exit........ \n");
- }
- module_init(led_init);
- module_exit(led_exit);
- MODULE_AUTHOR("koliy <[email protected]>");
- MODULE_DESCRIPTION("ARM test led");
- MODULE_LICENSE("GPL");
个人建议:编写驱动最好尽量使用定义好的gpio接口:如:
gpio_direction_output(S3C64XX_GPK(4),1)
gpio_set_value(S3C64XX_GPK(4),1);
方面让人一看就明白。对于程序的规范有很大的帮助。
------------------------------------------------------------------
Makefile:
- KERN_DIR = /android/linux-2.6.36-android
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- clean:
- make -C $(KERN_DIR) M=`pwd` clean
-
- obj-m += android_led.o
: make -C :进入指定的内核目录,使用内核里面的Makefile文件 这里须指定好你的内核存放路径。
M=`pwd` modules : 表示编译当前目录为模块 此处不是单引号,是键盘上数值1键左边的按键,请注意了。
Obj-m +=android_led.o : 要编译的模块名称。此文件由内核里的Makefile生成,改好文件名相同就行。
编译:(你的驱动目录路径下)# make
生成xxx,ko 模块文件。
------------------------------------------------------------
应用层:android 控件
这方面,请安装好 eclipse 和android -sdk。不了解,请网上查找资料。
------------------------------------------------------------
这些都是简单的,下面讲解下JNI:
JNI:在工程文件目录下,新建 jni目录,里面新建led.c 文件,里面编写JNI方法。
- #include<stdio.h>
- #include<stdlib.h>
- #include<fcntl.h>
- #include<errno.h>
- #include<unistd.h>
- #include<sys/ioctl.h>
- #include<jni.h>
- #include<string.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include "android/log.h"
-
- #define CMD_FLAG 'i'
- #define LED_ON _IOR(CMD_FLAG,0x00000000,__u32)
- #define LED_OFF _IOR(CMD_FLAG,0x00000001,__u32)
-
- #define DEVICE_NAME "/dev/myled"
- int fd ;
-
- static const char *TAG="led";
- #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
- #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
- #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
-
-
-
-
-
- JNIEXPORT Jint JNICALL Java_tw_com_mini6410_led_LedControl_openled(JNIEnv* env, jclass mc)
- {
- fd = open(DEVICE_NAME,O_RDWR);
- if(fd<0)
- {
- LOGI("don't open dev");
- return -1;
- }
- else
- LOGI("open success");
- return fd;
- }
-
-
-
-
- JNIEXPORT void JNICALL Java_tw_com_mini6410_led_LedControl_closeled(JNIEnv* env, jclass mc)
- {
- LOGI("dev close");
- close(fd);
- }
-
-
- JNIEXPORT jint JNICALL Java_tw_com_mini6410_led_LedControl_ioctl
- (JNIEnv* env,jclass mc, jint a, jint b)
- { int tmp = b;
- if(tmp==1){
-
-
- LOGI("led on");
- ioctl(fd,LED_ON,NULL);
- }
- else{
-
- LOGI("led1 off");
- ioctl(fd,LED_OFF,NULL);
- }
- return 0;
- }
Java_tw_com_mini6410_led_LedControl_ioctl :
tw_com_mini6410_led_LedControl: 包名+ 类名:
在这里特别说明下,JNI的格式规范要注意的地方:
1.函数声明的格式:
因JNI会把 '_' 转换成' . ' 所以在类名和函数接口中不要出现' _ ',以免应用层调用不到JNI接口,这方面对初学者来说极其重要,所以用eclipse生成的android类文件,最好改下类名。不了解对实验的热情打击比较重。
2.JNI函数分本地方法和静态方法。
本地方法:
public native int jni(); // 不带static 声明.
对应的 JNI 函数中参数的定义有改动:
Java_xx_xx_LedControl_jni(JNIEnv*env, jobject obj)
静态方法:
public static native int jni(); // 带static 声明.
对应的 JNI 函数中参数的定义有改动:
Java_xx_xx_LedControl_jni(JNIEnv*env, jclass cls)
注意 jobject 和jclass的变动。
请一定要去了解下JNI。
---------------------------------------------------
编译:
网上有别的方法javah -jni来编译生成此 JNI接口的头文件,可以根据头文件声明好的接口来完善。
不过,本人怎么编译都无法生成,没办法,只要手打了,还要JNI格式都很规律。
由于不用命令来生成LIbxxx.so文件。所以必须要用到NDK工具。请网上下载。
在你的Ndk 目录下创建一个JNI文件目录。
把整个应用工程文件,放入JNI下。
进入 (ndk目录路径)/JNI/(应用工程文件)/jni/
新建 Android.mk 文件
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- TARGET_PLATFORM := android-3
- LOCAL_MODULE := led
- LOCAL_SRC_FILES := led.c
- LOCAL_LDLIBS := -llog
-
- include $(BUILD_SHARED_LIBRARY)
-------------------------------------
-----
回到 (ndk目录路径)/JNI/(应用工程文件)/ 路径下
输入命令 : ../../ndk-build
会生成 libs 和obj 2个文件。 Libled.so文件放在 libs /armeabi/ 下
==========================================================
实验:
好了所有的需要的文件已经完成,现在开始实验了。再此之前,请确认自己的
开发板里的android里加入好了busybox工具。因需要insmod rmmod 等命令。
没有的话,请网上查找教材。
Adb shell
#Mkdir mydev
#exit
Adb push d:\android_led.ko /mydev
Adb shell
#cd mydev
#chmod 777 android_led.ko
#insmod android_led.ko
#lsmod //查看是否加载上了。卸载命令 rmmod android_led 不要加.ko
# cat /proc/devices //也可以查看设备号和设备名。
#ls -l /dev/myled //同样。
此时myled 权限需要修改。
#chmod 777 /dev/myled
----------
Libled.so: JNI所生成的so文件
Adb push d:\libled.so /system/lib/hw/ 一定要放在这里,应用层才能加载上
#chmod 766 /system/lib/hw/libled.so
--------------
好了,用eclipse把应用apk安装到开发板上,启动开看效果。或者用Sd开也可以。
这里说下,可能会出现的问题,当应用调用JNI接口时,会提示找不到native的接口。
仔细检查,方法格式多对,就是掉用不到,可以重新编写下JNI接口,和换接口名。对于这种问题,实在是让人调试到崩溃。复杂点的话,最好在jni方法里面指定好class。
这方面最好了解下JNI资料。
在开始时总会遇到很多问题,请多使用网络。