android驱动学习---led实验

时间:2021-09-28 13:55:50

======================== 
驱动: 
内核: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++之间能互相访问所提供的编程接口。推荐此教材(见附件):

=============================================================== 
驱动:

  1. #include <linux/module.h>               /* For module specific items */
  2. #include <linux/moduleparam.h>          /* For new moduleparam's */
  3. #include <linux/types.h>                /* For standard types (like size_t) */
  4. #include <linux/errno.h>                /* For the -ENODEV/... values */
  5. #include <linux/kernel.h>               /* For printk/panic/... */
  6. #include <linux/fs.h>                   /* For file operations */^M
  7. #include <linux/ioport.h>               /* For io-port access */
  8. #include <linux/platform_device.h>      /* For platform_driver framework */
  9. #include <linux/init.h>                 /* For __init/__exit/... */
  10. #include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  11. #include <linux/io.h>                   /* For inb/outb/... */
  12. #include <linux/gpio.h>
  13. #include <linux/device.h>
  14. #include <linux/cdev.h>
  15. #include <linux/slab.h>               /*kamlloc */
  16. //#include <asm-generic/ioctl.h>
  17. //ioctl 命令码建议规范化。
  18. #define CMD_FLAG  'i'
  19. #define LED_ON          _IOR(CMD_FLAG,0x00000000,__u32)
  20. #define LED_OFF     _IOR(CMD_FLAG,0x00000001,__u32)
  21. static int  major =0;
  22. static struct class *led_class;
  23. struct cdev_led {
  24. struct cdev cdev;
  25. }; //建议用2.6的注册方法,2.4的已经离我们越来越远了。
  26. struct cdev_led *led_dev;
  27. static int led_ioctl(struct file* filp,unsigned int cmd,unsigned long argv)
  28. {
  29. printk(KERN_INFO "entry kernel.... \n");
  30. switch(cmd)
  31. {
  32. case LED_ON:
  33. {
  34. gpio_set_value(S3C64XX_GPK(4),0);  //LED1 亮  gpkdat[4]
  35. printk(KERN_INFO "led on \n");
  36. break;
  37. }
  38. case LED_OFF:
  39. {
  40. gpio_set_value(S3C64XX_GPK(4),1);  //LED1 灭   gpkdat[4]
  41. printk(KERN_INFO "led off \n");
  42. break;
  43. }
  44. default:
  45. return -EINVAL;
  46. }
  47. return 0;
  48. }
  49. //open
  50. static int led_open(struct inode* i_node,struct file* filp)
  51. {
  52. printk(KERN_INFO "open init.... \n");
  53. int err;
  54. err = gpio_request(S3C64XX_GPK(4),"led1");  //请求gpkcon0[4]控制权。
  55. if(err<0)
  56. {
  57. printk(KERN_INFO "gpio request faile \n");
  58. return err;
  59. }
  60. gpio_direction_output(S3C64XX_GPK(4),1); // 设置gpkcon0[4] 为输出模式
  61. return 0;
  62. }
  63. //close
  64. static void led_close(struct inode* i_node,struct file* filp)
  65. {
  66. printk(KERN_INFO "close init \n");
  67. gpio_free(S3C64XX_GPK(4)); //释放
  68. return ;
  69. }
  70. /* file operations */
  71. struct file_operations fops={
  72. .owner  = THIS_MODULE,
  73. .open   = led_open,
  74. .unlocked_ioctl = led_ioctl, // 特别注意从2.6.36以后ioctl已经移除,内核里面用unlocked_ioctl和compat_ioctl. 应用层不变,仍是ioctl调用。
  75. .release= led_close,
  76. };
  77. static int __init led_init(void)
  78. {
  79. printk(KERN_INFO "init .... \n");
  80. dev_t dev_no;
  81. int result,err;
  82. err = alloc_chrdev_region(&dev_no,0,1,"my_led"); //动态申请设备号
  83. if(err<0)
  84. {
  85. printk(KERN_INFO "ERROR\n");
  86. return err;
  87. }
  88. major = MAJOR(dev_no);
  89. led_dev = kmalloc(sizeof(struct cdev_led),GFP_KERNEL);
  90. if(!led_dev)
  91. {
  92. result = -ENOMEM;
  93. goto fail_malloc;
  94. }
  95. memset(led_dev,0,sizeof(led_dev));
  96. cdev_init(&led_dev->cdev,&fops); // 初始化cdev
  97. led_dev->cdev.owner = THIS_MODULE;
  98. result = cdev_add(&led_dev->cdev,dev_no,1); //加载设备
  99. if(result <0)
  100. {   printk(KERN_INFO "error\n");
  101. goto fail_add;
  102. }
  103. led_class = class_create(THIS_MODULE,"myled");  //在sys/class下创建sysfs文件
  104. device_create(led_class,NULL,MKDEV(major,0),NULL,"myled"); //动态创建设备文件  /dev/myled, 以后不用手动创建了
  105. return 0;
  106. fail_add:
  107. kfree(led_dev);
  108. fail_malloc:
  109. unregister_chrdev_region(dev_no,1);
  110. return result;
  111. }
  112. static void __exit led_exit(void)
  113. {
  114. dev_t dev_no=MKDEV(major,0);
  115. unregister_chrdev_region(dev_no,1);
  116. cdev_del(&led_dev->cdev);
  117. kfree(led_dev);
  118. device_destroy(led_class,dev_no);
  119. class_destroy(led_class);
  120. printk(KERN_INFO "exit........ \n");
  121. }
  122. module_init(led_init);
  123. module_exit(led_exit);
  124. MODULE_AUTHOR("koliy <xxxx@163.com>");
  125. MODULE_DESCRIPTION("ARM test led");
  126. MODULE_LICENSE("GPL");

个人建议:编写驱动最好尽量使用定义好的gpio接口:如: 
gpio_direction_output(S3C64XX_GPK(4),1) 
gpio_set_value(S3C64XX_GPK(4),1); 
方面让人一看就明白。对于程序的规范有很大的帮助。

------------------------------------------------------------------ 
Makefile:

  1. KERN_DIR = /android/linux-2.6.36-android
  2. all:
  3. make -C $(KERN_DIR) M=`pwd` modules
  4. clean:
  5. make -C $(KERN_DIR) M=`pwd` clean
  6. 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。不了解,请网上查找资料。

  1. public class LedControl extends Activity implements OnClickListener {
  2. private Button led1_on;
  3. private Button led1_off;
  4. public int led_on = 1;
  5. public int led_off = 2;
  6. public int fd =0;
  7. static {
  8. try{
  9. Log.i("JNI","try to load libled.so");
  10. System.loadLibrary("led");
  11. //加载本地库,也就是JNI生成的libxxx.so文件,下面再说。
  12. }catch (UnsatisfiedLinkError ule){
  13. Log.e("JNI","WARNING: Could not load libled.so");
  14. }
  15. }
  16. //JNI 接口,为JAVA能跟C 交互。Java 怎么使用这些接口,请了解下资料。这点很重要,这里只是简单的方法。
  17. public static native int openled();
  18. public static native void closeled();
  19. public static native int ioctl(int led_num,int on_off);
  20. /** Called when the activity is first created. */
  21. @Override
  22. public void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.main);
  25. fd =this.openled(); // 调用JNI 接口来打开/dev/myled 设备 ,注意前面要加个对象。
  26. if(fd<0)
  27. {
  28. setTitle("open device faile!");
  29. finish();
  30. }else{
  31. setTitle("open device success!");
  32. }
  33. findById();
  34. setOnClickListener();
  35. }
  36. private void findById(){
  37. led1_on = (Button)findViewById(R.id.bt_led1);
  38. led1_off=(Button)findViewById(R.id.bt_led2);
  39. }
  40. private void setOnClickListener(){
  41. led1_on.setOnClickListener(this);
  42. led1_off.setOnClickListener(this);
  43. }
  44. @Override
  45. public void onClick(View v) {
  46. // TODO Auto-generated method stub
  47. switch(v.getId()){
  48. case R.id.bt_led1: {
  49. this.ioctl(1,led_on); // led1  on
  50. break;
  51. }
  52. case R.id.bt_led2:{
  53. this.ioctl(1,led_off);  // led1 off
  54. break;
  55. }
  56. }
  57. }
  58. protected void onDestroy(){
  59. super.onDestroy();
  60. this.closeled();
  61. }
  62. }
  63. 布局:
  64. <?xml version="1.0" encoding="utf-8"?>
  65. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  66. android:orientation="vertical"
  67. android:layout_width="fill_parent"
  68. android:layout_height="fill_parent"
  69. >
  70. <TextView
  71. android:id="@+id/position"
  72. android:layout_centerInParent="true"
  73. android:layout_width="wrap_content"
  74. android:layout_height="wrap_content"
  75. android:textSize="25sp"
  76. android:text=" led "
  77. />
  78. <Button
  79. android:id="@+id/bt_led1"
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content"
  82. android:textSize="18sp"
  83. android:text="LED1 on"
  84. android:layout_toLeftOf="@+id/position"
  85. android:layout_centerHorizontal="true"
  86. android:layout_alignTop="@+id/position"
  87. />
  88. <Button
  89. android:id="@+id/bt_led2"
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:textSize="18sp"
  93. android:text="LED1 off"
  94. android:layout_toRightOf="@+id/position"
  95. android:layout_alignTop="@+id/position"
  96. />
  97. </RelativeLayout>

------------------------------------------------------------ 
这些都是简单的,下面讲解下JNI: 
JNI:在工程文件目录下,新建 jni目录,里面新建led.c 文件,里面编写JNI方法。

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<fcntl.h>
  4. #include<errno.h>
  5. #include<unistd.h>
  6. #include<sys/ioctl.h>
  7. #include<jni.h>  // 一定要包含此文件
  8. #include<string.h>
  9. #include<sys/types.h>
  10. #include<sys/stat.h>
  11. #include "android/log.h"
  12. //驱动里的命令码.
  13. #define CMD_FLAG 'i'
  14. #define LED_ON      _IOR(CMD_FLAG,0x00000000,__u32)
  15. #define LED_OFF     _IOR(CMD_FLAG,0x00000001,__u32)
  16. #define DEVICE_NAME "/dev/myled"
  17. int fd ;
  18. static const char *TAG="led";
  19. #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
  20. #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
  21. #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
  22. /* * Class:     Linuxc
  23. * Method:    openled
  24. * Signature: ()I
  25. */
  26. JNIEXPORT Jint JNICALL Java_tw_com_mini6410_led_LedControl_openled(JNIEnv* env, jclass mc)
  27. {
  28. fd = open(DEVICE_NAME,O_RDWR);
  29. if(fd<0)
  30. {
  31. LOGI("don't open dev");
  32. return -1;
  33. }
  34. else
  35. LOGI("open success");
  36. return fd;
  37. }
  38. /* * Class:     Linuxc
  39. * Method:    clsoeled
  40. * Signature: ()V
  41. */
  42. JNIEXPORT void JNICALL Java_tw_com_mini6410_led_LedControl_closeled(JNIEnv* env, jclass mc)
  43. {
  44. LOGI("dev close");
  45. close(fd);
  46. }
  47. JNIEXPORT jint JNICALL Java_tw_com_mini6410_led_LedControl_ioctl
  48. (JNIEnv* env,jclass mc, jint a, jint b)
  49. {       int tmp = b;
  50. if(tmp==1){
  51. //int err= write(fd,&tmp,sizeof(tmp));
  52. LOGI("led on");
  53. ioctl(fd,LED_ON,NULL);
  54. }
  55. else{
  56. //int err= write(fd,&tmp,sizeof(tmp));
  57. LOGI("led1 off");
  58. ioctl(fd,LED_OFF,NULL);
  59. }
  60. return 0;
  61. }

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 文件

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. TARGET_PLATFORM := android-3
  4. LOCAL_MODULE    := led   //模块名  libled.so
  5. LOCAL_SRC_FILES := led.c   //要编译的文件。
  6. LOCAL_LDLIBS    := -llog
  7. 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资料。 
在开始时总会遇到很多问题,请多使用网络。