当前位置:   article > 正文

Android驱动程序开发实例精讲-0_Android系统HAL驱动开发经典案例详解(基于Android4.0)_cmd = atoi(argv[2]);

cmd = atoi(argv[2]);

Android系统HAL驱动开发经典案例详解(基于Android4.0)

 

目的:通过学习一个LED点灯的简单功能,掌握Linux驱动程序与HAL硬件抽象层之间的调用方法,同时掌握JNI层的编写思想,学会使用Eclipse编写Android应用程序,深入体会Android HAL架构。本章内容主要参考文献:《Android深度探索(卷1) HAL与驱动开发》、《TQ210开发板Android_HAL_LED_V1.2.pdf》

目录:

一、架构分析

1.1 功能介绍

1.2 HAL架构

1.3 接口定义

二、驱动程序

2.1 驱动功能简介

2.2 驱动源码分析

2.3 利用可执行文件测试驱动

2.3.1 源文件

2.3.2 利用交叉编译器编译

2.3.3 使用原生C程序测试驱动

三、HAL硬件抽象层

3.1 HAL简介

3.2 HAL层源码与分析

3.3 HAL源码编译

四、Jni/Service服务层

4.1 直接使用JNI调用驱动程序

4.2 JNI/Service程序代码

五、APP应用程序层

5.1 activity_main.xml

5.2 LedServer.java

5.3 MainActivity.java

 

一、架构分析

1.1 功能介绍

Linux版本:3.0.8

Android版本:4.0

开发板:FriendlyARM smart210

功能介绍:应用程序界面如下图所示,为求方便,截图来自模拟器,实际开发板界面和下图是一样的。CheckBox复选框分别对应开发板上四个LED灯,选中复选框,按动led_hal_jni按键后,相应LED就会亮起来。功能很简单,代码中几乎不会涉及任何逻辑算法,主要是为了方便理清整个程序架构,体会Android系统通过HAL硬件抽象层与实际硬件之间交互的编程思想。

 

 

1.2 HAL架构

想要点亮一个灯当然简单,可以直接烧写ARM裸机程序,也可以在跑Linux系统的板子上通过写/dev/leds这样的设备文件来达到目的。Android系统为了解决一些调用接口和版权方面的问题提出了一个HAL架构。目前最新的HAL架构是下面这个样子的。

 

 

可以看出,Android应用程序直接调用的是JNI或者Service程序库文件(.so),程序库文件是通过一个ID来定位到相应的HAL的库文件(.so),最终和硬件打交道的是HAL模块。需要注意的是,HAL及其以上层均属于应用程序范畴,也就是都运行在了用户空间,只有Linux驱动程序运行在了Linux内核空间。

 

1.3 接口定义

 

从Android HAL架构来看,一套完整的点灯的程序应该包括四个层次。为了方便程序维护,层与层之间应该尽可能的降低耦合程度,每个层对其他层开放的仅仅是一个操作接口即可。每个层的源文件位置和其向其他层提供的关键接口表述如下,这里指的关键接口实际上就是“调用函数”。各个源文件的具体内容会在下文给出。如果你能亲自动手写完整套程序再回过头来看这部分接口定义,应该能体会得更深些。

1、底层驱动。

源文件位置:$(linux_source)/drivers/char/mini210_led.c    

对应开发板上的驱动设备文件为:/dev/leds

关键接口定义:

//发送IO命令

ioctl(file_handler, cmd, arg);

//调用方法如下

./ioctl_test /dev/leds 1 2   //表示点亮第二盏灯

//发送写命令

write(fd,buf,strlen(buf));

//调用方法如下

./write_test /dev/s3c6410_leds "1111" // 点亮所有灯

这里的ioctl_test和write_test是两个可执行文件,它们分别是由ioctl_test.c和write_test.c源文件通过交叉编译器或Android原生代码编译出来的,这两个源文件是为了验证驱动程序专门写的,后文会贴出具体代码。

 

2、HAL层。

源文件位置:

//这个.c文件也可以放到其他目录

$(Android_source)/device/friendly-arm/mini210/libled/led_hal.c 

//这个.h文件最好放到指定位置,以免包含头文件的时候发生错误

$(Android_source)/hardware/libhardware/include/hardware/led_hal.h

生成模块位置:Android_source/out/target/product/mini210/lib/hw/led_hal.default.so

关键接口定义:

int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER)  //表示LED_NUMBER灯亮

{

... ...

ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);

... ...

}

int led_off(struct led_control_device *dev, int32_t LED_NUMBER)   //表示LED_NUMBER灯灭

{

... ...

ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);

... ...

}

 

3、jni/service层

源文件位置:$(Android_source)/packages/apps/led/jni/LedHalService.cpp

生成模块位置:Android_source/out/target/product/mini210/lib/libled_hal_jni.so

关键接口定义:

jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)

{

... ...

return sLedDevice->set_on(sLedDevice,number);

... ...

}

jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)

{

... ...

return sLedDevice->set_off(sLedDevice,number);

... ...

}

类名定义:

//注意这里的kClassName决定了调用该JNI模块的应用程序的包名与类名。

int register_android_server_LedService(JNIEnv *env)

{

... ...

static const char* const kClassName = "com/example/leds/LedService";

... ...

}

 

二、驱动程序

2.1 驱动功能简介

mini210_led.c主要提供了mini210_leds_ioctl和mini210_leds_write两个接口。应用程序通过向/dev/leds发送I/O指令或写命令就可以实现控制LED灯的功能了。

2.2 驱动源码分析

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/miscdevice.h>
  4. #include <linux/fs.h>
  5. #include <linux/types.h>
  6. #include <linux/moduleparam.h>
  7. #include <linux/slab.h>
  8. #include <linux/ioctl.h>
  9. #include <linux/cdev.h>
  10. #include <linux/delay.h>
  11. #include <mach/gpio.h>
  12. #include <mach/regs-gpio.h>
  13. #include <plat/gpio-cfg.h>
  14. #include <asm/uaccess.h>
  15. #define DEVICE_NAME "leds"
  16. static unsigned char mem[4];
  17. static int led_gpios[] = {
  18. S5PV210_GPJ2(0),
  19. S5PV210_GPJ2(1),
  20. S5PV210_GPJ2(2),
  21. S5PV210_GPJ2(3),
  22. };
  23. #define LED_NUM ARRAY_SIZE(led_gpios)
  24. //mini210_leds_ioctl函数实现了发送IO控制命令
  25. static long mini210_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  26. {
  27. switch(cmd) {
  28. case 0:
  29. case 1:
  30. if (arg > LED_NUM) {
  31. return -EINVAL;
  32. }
  33. gpio_set_value(led_gpios[arg], !cmd);
  34. //printk(DEVICE_NAME": %d %d\n", arg, cmd);
  35. break;
  36. default:
  37. return -EINVAL;
  38. }
  39. return 0;
  40. }
  41. //mini210_leds_write函数实现了写文件命令
  42. ssize_t mini210_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)
  43. {
  44. int i=0;
  45. int tmp=count;
  46. memset(mem,0,4);
  47. if(count>4)
  48. {
  49. tmp=4;
  50. }
  51. if(copy_from_user(mem,buf,tmp))
  52. {
  53. return -EFAULT;
  54. }
  55. for(i=0;i<4;i++)
  56. {
  57. if(mem[i]=='1')
  58. {
  59. gpio_set_value(led_gpios[i], 0);
  60. }
  61. else if(mem[i]=='0')
  62. {
  63. gpio_set_value(led_gpios[i], 1);
  64. }
  65. }
  66. return count;
  67. }
  68. static struct file_operations mini210_led_dev_fops = {
  69. .owner = THIS_MODULE,
  70. .unlocked_ioctl = mini210_leds_ioctl,
  71. .write = mini210_leds_write,
  72. };
  73. static struct miscdevice mini210_led_dev = {
  74. .minor = MISC_DYNAMIC_MINOR,
  75. .name = DEVICE_NAME,
  76. .fops = &mini210_led_dev_fops,
  77. };
  78. static int __init mini210_led_dev_init(void) {
  79. int ret;
  80. int i;
  81. for (i = 0; i < LED_NUM; i++) {
  82. ret = gpio_request(led_gpios[i], "LED");
  83. if (ret) {
  84. printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
  85. led_gpios[i], ret);
  86. return ret;
  87. }
  88. s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
  89. gpio_set_value(led_gpios[i], 1);
  90. }
  91. ret = misc_register(&mini210_led_dev);
  92. printk(DEVICE_NAME"\tinitialized\n");
  93. return ret;
  94. }
  95. static void __exit mini210_led_dev_exit(void) {
  96. int i;
  97. for (i = 0; i < LED_NUM; i++) {
  98. gpio_free(led_gpios[i]);
  99. }
  100. misc_deregister(&mini210_led_dev);
  101. }
  102. module_init(mini210_led_dev_init);
  103. module_exit(mini210_led_dev_exit);
  104. MODULE_LICENSE("GPL");</span>


 

2.3 利用可执行文件测试驱动

 

2.3.1 源文件

首先给出两个可执行文件的源文件

//ioctl_test.c

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <sys/ioctl.h>
  5. /*
  6. * ./ioctl_test /dev/leds 1 2 //表示将第二个灯点亮
  7. */
  8. int main(int argc, char **argv)
  9. {
  10. int file_handler = 0;
  11. int cmd = 0;
  12. int arg = 0;
  13. if(argc < 4)
  14. {
  15. printf("Usage: ioctl <dev_file> <cmd> <arg>\n");
  16. return 0;
  17. }
  18. cmd = atoi(argv[2]);
  19. arg = atoi(argv[3]);
  20. printf("dev:%s\n", argv[1]);
  21. printf("cmd:%d\n", cmd);
  22. printf("arg:%d\n", arg);
  23. file_handler = open(argv[1], 0);
  24. ioctl(file_handler, cmd, arg);
  25. close(file_handler);
  26. return 0;
  27. }</span>


 

 

//write_test.c

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. /*
  8. * ./write_test /dev/s3c6410_leds "1111" 表示四个灯全亮
  9. */
  10. void main(int argc, char *argv[])
  11. {
  12. int fd;
  13. char *buf;
  14. buf=argv[2];
  15. fd=open(argv[1],O_WRONLY);
  16. if(fd<0)
  17. {
  18. printf("file_open failed!\n");
  19. return;
  20. }
  21. write(fd,buf,strlen(buf));
  22. close(fd);
  23. }</span>


源文件的内容比较简单,不再深入讨论。Android系统有两种编译可执行程序的方法,下面分别介绍。

2.3.2 利用交叉编译器编译

这种方法比较容易实现。直接在命令行模式下编译即可。需要注意的是最好加上静态编译选项“-static”。

Pc:#arm-linux-gcc -static ioctl_test.c -o ioctl_test

Pc:#arm-linux-gcc -static write_test.c -o write_test

然后利用adb命令将可执行文件上传到开发板

Pc:# adb push ioctl_test write_test /data/local

 

进入开发板的命令行终端

Pc:# adb shell

然后就可以执行可执行文件了

Phone#: cd /data/local

Phone:# ./ioctl_test /dev/leds 1 2

Phone:#./write_test /dev/leds “1111”

 

2.3.3 使用原生C程序测试驱动

这种方式更加地道,其编译方式是通过Android源代码直接编译的。所使用的工具就是Android系统自带的编译器以及一些Android系统的头文件,所以编译之前一定确保自己的电脑上已经成功编译了一套Android系统。至于如何编译Android系统,网上有很多教程,难点就是编译的过程中会出现一些莫名其妙的错误,往往是少安装了某些库造成的,耐着性子上网查查一般就能解决。

接下来以ioctl_test为例讲解如何使用原生C程序测试驱动。首先一定要在Android系统源代码目录或子目录下为ioctl_test应用程序单独建立一个文件夹,作为其主目录。然后在ioctrl_test.c的主目录下新建一个Android.mk文件,内容如下:

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:=$(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_SRC_FILES:=ioctl_test.c
  4. LOCAL_MODULE:=ioctl_test
  5. LOCAL_MODULE_TAGS:=optional
  6. include $(BUILD_EXECUTABLE)</span>


Android.mk其实就相当与Android系统中的Makefile文档,就是给出了一些编译选项,需要执行mm命令或mmm命令进行编译。关于Android.mk的书写规则,网上有很多,看看就知道了。

在使用mm命令之前,一定确保首先在Android系统源代码下执行:

Pc:# source ./build/envsetup.sh  执行后会出现类似下面的信息。

including device/friendly-arm/mini210/vendorsetup.sh

including device/moto/stingray/vendorsetup.sh

including device/moto/wingray/vendorsetup.sh

including device/samsung/crespo4g/vendorsetup.sh

including device/samsung/crespo/vendorsetup.sh

including device/samsung/maguro/vendorsetup.sh

including device/samsung/toro/vendorsetup.sh

including device/samsung/tuna/vendorsetup.sh

including device/ti/panda/vendorsetup.sh

including sdk/bash_completion/adb.bash

 

这个envsetup.sh就是包含自定义的vendorsetup.sh和初始化mm等命令用的(插一句题外话,如果你想从官方的Android源码定制适合自己开发板的Android系统的话,通常需要自己写一个新的vendorsetup.sh文件)。注意的是,每次启动一个新的Linux命令终端,好像都要重新运行source ./build/envsetup.sh,否则mm命令不能用。

然后执行lunch

Pc:# lunch  

You're building on Linux

 

Lunch menu... pick a combo:

     1. full-eng

     2. full_x86-eng

     3. vbox_x86-eng

     4. full_mini210-userdebug

     5. full_stingray-userdebug

     6. full_wingray-userdebug

     7. full_crespo4g-userdebug

     8. full_crespo-userdebug

     9. full_maguro-userdebug

     10. full_toro-userdebug

     11. full_tuna-userdebug

     12. full_panda-eng

 

Which would you like? [full-eng] 4

在这里选择4。同样的,每次运行完source ./build/envsetup.sh之后也要重新运行lunch命令,否则系统仍会默认以full-eng的方式进行编译。当你编译ioctl_test.c这样的Android应用程序的时候就会出现类似下面的错误。

make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

make: *** No rule to make target `out/target/product/generic/obj/lib/crtbegin_dynamic.o', needed by `out/target/product/generic/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test'.  Stop.

make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

一定要选择full_mini210-userdebug。然后会出现下面的信息。

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=4.0.3

TARGET_PRODUCT=full_mini210

TARGET_BUILD_VARIANT=userdebug

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

TARGET_ARCH_VARIANT=armv7-a-neon

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=IML74K

============================================

 

好了,最后然后进入到ioctl_test.c的主目录中执行:

Pc:#mm

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=4.0.3

TARGET_PRODUCT=full_mini210

TARGET_BUILD_VARIANT=userdebug

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

TARGET_ARCH_VARIANT=armv7-a-neon

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=IML74K

============================================

make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

target thumb C: ioctl_test <= /work/MySystem/3_Android_resource/android-4.0.3_r1/ioctl_test/ioctl_test.c

target Executable: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test)

target Symbolic: ioctl_test (out/target/product/mini210/symbols/system/bin/ioctl_test)

target Strip: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/ioctl_test)

Install: out/target/product/mini210/system/bin/ioctl_test

make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

从编译结果可以看出,编译出的可执行文件ioctl_test被放在了out/target/product/mini210/system/bin/文件下。按照2.3.2的方法将其上传到开发板后即可使用。

 

三、HAL硬件抽象层

3.1 HAL简介

Google为Android加入HAL主要有如下目的。

Ø 统一硬件的调用接口。由于HAL有标准的调用接口,可以利用HAL屏蔽Linux驱动复杂、不统一的接口。

Ø 解决了GPL版权问题。Android系统基于Apache Licence2.0协议。可以让那些不想开源的驱动作者屏蔽源代码。这是基于GPL开源协议的Linux系统所不允许的。所以Linux毫不客气的将Android系统开除族籍了。

Ø 访问用户空间资源。对于有些硬件,可能需要访问一些用户空间的资源,或在内核空间不方便完成的工作以及特殊需要。在这种情况下,可以利用位于用户空间的HAL代码来辅助Linux驱动完成一些工作。

在编写HAL层程序的时候要时刻注意HAL是运行在用户空间的,这有助于理解HAL层的设计思想。HAL(硬件抽象层)的诞生意味着Android系统被Linux大家族给剔除了。原因其实就是HAL层可以不开源。

3.2 HAL层源码与分析

撰写HAL程序关键之处在于3个重要的结构体。分别是描述HAL模块的hw_module_t结构体;描述HAL设备的hw_device_t结构体;描述模块入口函数的hw_module_methods_t结构体。

首先给出头文件led_hal.h

//led_hal.h

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#ifndef ANDROID_LED_INTERFACE_H
  2. #define ANDROID_LED_INTERFACE_H
  3. #include <stdint.h>
  4. #include <sys/cdefs.h>
  5. #include <sys/types.h>
  6. #include <hardware/hardware.h>
  7. #include <fcntl.h>
  8. #include <errno.h>
  9. #include <math.h>
  10. #include <poll.h>
  11. #include <unistd.h>
  12. #include <dirent.h>
  13. #include <sys/select.h>
  14. #include <cutils/log.h>
  15. __BEGIN_DECLS
  16. /*下面这个ID重要的很,Jni/Service层主要通过它来寻找相应的HAL库文件(.so),所以HAL源文件的名字是可以随便改的,只要保证其中的ID值不变即可*/
  17. #define LED_HARDWARE_MODULE_ID "led_hal"
  18. /*HAL规定不能直接使用hw_module_t结构体,需要在hw_modult_t外再套一层结构体。hw_module_t结构体表示HAL模块的相关信息,成员变量可以随便起,但是hw_module_t结构体必须是led_module_t结构体的第1个成员变量的数据类型。这个东西是整个HAL的核心,后续的工作其实就是不断的完善这个module。*/
  19. struct led_module_t
  20. {
  21. struct hw_module_t common;
  22. };
  23. #define IOCTL_GPIO_ON 1
  24. #define IOCTL_GPIO_OFF 0
  25. /*led_control_device_t是自定义的hw_device_t类型的结构体,但是hw_device_t必须是该结构体的第一个成员变量,另外还定义了两个控制LED的成员函数*/
  26. struct led_control_device_t
  27. {
  28. struct hw_device_t common;
  29. int (*set_on)(struct led_control_device_t* dev, int32_t LED_NUMBER);
  30. int (*set_off)(struct led_control_device_t* dev, int32_t LED_NUMBER);
  31. };
  32. __END_DECLS
  33. #endif //ANDROID_LED_INTERFACE_H</span>


/

Led_hal.c文件是HAL层的源文件,我在每个函数前都做了注解,其编号是按照代码书写顺序标注的。也就是说,HAL代码的编写顺序并不是自上而下一行一行写出来的,而是类似于驱动程序的形式,先从接口程序开始写,然后一层一层的把用到的函数补充出来。

 

//Led_hal.c

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <hardware/led_hal.h>
  2. char const * const LED_DEVICE = "/dev/leds";
  3. static int fd = -1;
  4. /*4、打开设备文件,这里的open函数就是运行在了用户空间*/
  5. static int open_led()
  6. {
  7. if((fd = open(LED_DEVICE,O_RDWR))==-1)
  8. {
  9. LOGV("LED stub: open %s failed\n",LED_DEVICE);
  10. return -1;
  11. }
  12. else
  13. {LOGV("LED stub: open %s successed\n",LED_DEVICE);}
  14. return 1;
  15. }
  16. /*5、关闭设备,没什么说的*/
  17. static int close_led(struct hw_device_t *dev)
  18. {
  19. LOGV("close_light is called");
  20. if(fd!=-1)
  21. {
  22. close(fd);
  23. if(dev)
  24. free(dev);
  25. }
  26. return 0;
  27. }
  28. /*6、ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER)运行在了用户空间*/
  29. int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER)
  30. {
  31. if(fd == -1)
  32. return -1;
  33. return ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);
  34. }
  35. /*7、ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER)运行在了用户空间*/
  36. int led_off(struct led_control_device *dev, int32_t LED_NUMBER)
  37. {
  38. if(fd == -1)
  39. return -1;
  40. return ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);
  41. }
  42. /*3、初始化设备文件*/
  43. static int led_init(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
  44. {
  45. struct led_control_device_t *dev;
  46. /*为led_control_device结构体分配内存空间*/
  47. dev = (struct led_control_device_t *)malloc(sizeof(*dev));
  48. if(dev==NULL)
  49. return 0;
  50. memset(dev,0,sizeof(*dev));
  51. dev->common.tag = HARDWARE_DEVICE_TAG;
  52. dev->common.version = 0;
  53. dev->common.module = (struct hw_module_t*)module;
  54. //dev->common.close = (int (*)(struct hw_device_t *))close_led;
  55. dev->common.close = close_led;
  56. //设置打开LED的函数指针
  57. dev->set_on = led_on;
  58. //设置关闭LED的函数指针
  59. dev->set_off = led_off;
  60. *device = (struct hw_device_t *)&dev->common;
  61. if(open_led() == -1)
  62. {
  63. free(dev);
  64. dev = NULL;
  65. return -1;
  66. }
  67. return 0;
  68. }
  69. /*2、hw_module_methods_t中定义了打开设备的open函数的指针。也就是在这个函数中进行了一些列的初始化工作*/
  70. static struct hw_module_methods_t led_module_methods={
  71. open: led_init
  72. };
  73. /*1、HAL_MODULE_INFO_SYM才是HAL真正的入口,这里面最重要的两个参数是id: LED_HARDWARE_MODULE_ID和methods: &led_module_methods*/
  74. struct led_module_t HAL_MODULE_INFO_SYM={
  75. common:
  76. {
  77. tag: HARDWARE_MODULE_TAG,
  78. version_major: 1,
  79. version_minor: 0,
  80. id: LED_HARDWARE_MODULE_ID,
  81. name: "sample led hal stub",
  82. author: "xxx",
  83. methods: &led_module_methods,
  84. }
  85. };</span>


 

 

3.3 HAL源码编译

//Android.mk

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)
  2. # HAL module implemenation stored in
  3. # hw/<COPYPIX_HARDWARE_MODULE_ID>.<ro.board.platform>.so
  4. include $(CLEAR_VARS)
  5. LOCAL_PRELINK_MODULE := false
  6. LOCAL_SRC_FILES := led_hal.c
  7. LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
  8. #LOCAL_SHARED_LIBRARIES := liblog
  9. LOCAL_MODULE := led_hal.default
  10. #LOCAL_MODULE := led_hal.$(TARGET_BOARD_PLATFORM)
  11. LOCAL_MODULE_TAGS := optional
  12. include $(BUILD_SHARED_LIBRARY)</span>

编译出来的led_hal.default.so模块保存在了$(Android_source)/out/target/produce/mini210/system/lib/hw中。将之上传到开发板中相应的/system/lib/hw文件夹中即可。

 

四、Jni/Service服务层

 

这一层的主要任务就是调用HAL层的程序库,并为上层应用程序层提供调用接口。

4.1 直接使用JNI调用驱动程序

在Java中是可以写C/C++代码的,虽然并不是100%的C/C++语言,但只需要改动一下数据类型的标志并注意一些新定义的用法即可。我们常把C/C++代码称之为原生代码,如果不是特别讲究叫法的话,在Java体系中还可以勉为其难的称之为JNI或NDK,其实JNI和NDK意义并不同,首先介绍一下JNI和NDK的区别。

JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。

 

NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

所以可以简单的理解为JNI是接口,NDK是工具。

在涉及到操作硬件的思路上,我们其实可以直接通过Java程序去调用驱动程序,Java程序提供了File.write(str)这样的写函数操作类,所以可以直接向/dev/leds设备驱动文件中写数据。不过Java并没有提供ioctl这样的发送IO控制命令的函数。如果就想发送IO控制命令该怎么办?可以通过JNI层来调用C/C++语言实现。

先给出完成后的操作界面。

 

然后直接贴出各个源文件。

//activity_main.xml

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6. <LinearLayout
  7. android:layout_width="fill_parent"
  8. android:layout_height="wrap_content"
  9. android:orientation="horizontal" >
  10. <CheckBox
  11. android:id="@+id/checkbox_str_led1"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:text="LED1" />
  15. <CheckBox
  16. android:id="@+id/checkbox_str_led2"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="LED2" />
  20. <CheckBox
  21. android:id="@+id/checkbox_str_led3"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:text="LED3" />
  25. <CheckBox
  26. android:id="@+id/checkbox_str_led4"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:text="LED4" />
  30. </LinearLayout>
  31. <Button
  32. android:layout_width="fill_parent"
  33. android:layout_height="wrap_content"
  34. android:onClick="onClick_write"
  35. android:text="write_test" />
  36. <LinearLayout
  37. android:layout_width="fill_parent"
  38. android:layout_height="wrap_content"
  39. android:orientation="horizontal" >
  40. <CheckBox
  41. android:id="@+id/checkbox_cmd_led1"
  42. android:layout_width="wrap_content"
  43. android:layout_height="wrap_content"
  44. android:text="LED1" />
  45. <CheckBox
  46. android:id="@+id/checkbox_cmd_led2"
  47. android:layout_width="wrap_content"
  48. android:layout_height="wrap_content"
  49. android:text="LED2" />
  50. <CheckBox
  51. android:id="@+id/checkbox_cmd_led3"
  52. android:layout_width="wrap_content"
  53. android:layout_height="wrap_content"
  54. android:text="LED3" />
  55. <CheckBox
  56. android:id="@+id/checkbox_cmd_led4"
  57. android:layout_width="wrap_content"
  58. android:layout_height="wrap_content"
  59. android:text="LED4" />
  60. </LinearLayout>
  61. <Button
  62. android:layout_width="fill_parent"
  63. android:layout_height="wrap_content"
  64. android:onClick="onClick_ioctl"
  65. android:text="ioctl_test" />
  66. <TextView
  67. android:id="@+id/textView"
  68. android:layout_width="wrap_content"
  69. android:layout_height="wrap_content"
  70. android:text="TextView" />
  71. </LinearLayout></span>


 

// jni/leds.c

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <string.h>
  2. #include <stdio.h>
  3. #include <jni.h>
  4. #include <fcntl.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <unistd.h>
  8. #include <stdlib.h>
  9. jstring Java_com_example_leds1_MainActivity_getText (JNIEnv* env, jobject obj)
  10. {
  11. return (*env)->NewStringUTF(env, "Test Android NDK!Test My NDK!");
  12. }
  13. char* jstring_to_pchar(JNIEnv* env, jstring str)
  14. {
  15. char* pstr = NULL;
  16. jclass clsstring = (*env)->FindClass(env, "java/lang/String");
  17. jstring strencode = (*env)->NewStringUTF(env, "utf-8");
  18. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
  19. "(Ljava/lang/String;)[B");
  20. jbyteArray byteArray = (jbyteArray)(
  21. (*env)->CallObjectMethod(env, str, mid, strencode));
  22. jsize size = (*env)->GetArrayLength(env, byteArray);
  23. jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
  24. if (size > 0)
  25. {
  26. pstr = (char*) malloc(size);
  27. memcpy(pstr, pbyte, size);
  28. }
  29. return pstr;
  30. }
  31. void Java_com_example_leds1_MainActivity_writeLeds(JNIEnv* env, jobject thiz, jstring str)
  32. {
  33. int fd;
  34. fd = open("/dev/leds",O_RDWR);
  35. char* pstr = jstring_to_pchar(env,str);
  36. write(fd,pstr,strlen(pstr));
  37. close(fd);
  38. }
  39. void Java_com_example_leds1_MainActivity_ioctlLeds(JNIEnv* env, jobject thiz, jint cmd, jint arg)
  40. {
  41. int fd;
  42. fd = open("/dev/leds",O_RDWR);
  43. ioctl(fd,cmd,arg);
  44. close(fd);
  45. }</span>

 

//jni/Android.mk

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE := leds
  4. LOCAL_SRC_FILES := leds.c
  5. include $(BUILD_SHARED_LIBRARY) </span>


 

//MainActivity.java

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds1;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.Menu;
  5. import android.view.View;
  6. import android.view.MenuItem;
  7. import android.widget.TextView;
  8. import android.widget.CheckBox;
  9. public class MainActivity extends Activity {
  10. private CheckBox[] cbStrLeds = new CheckBox[4];
  11. private CheckBox[] cbCmdLeds = new CheckBox[4];
  12. TextView textView;
  13. public native String getText();//声明native 方法
  14. public native void writeLeds(String str);
  15. public native void ioctlLeds(int cmd, int arg);
  16. @Override
  17. public void onCreate(Bundle savedInstanceState) {
  18. super.onCreate(savedInstanceState);
  19. setContentView(R.layout.activity_main);
  20. cbStrLeds[0] = (CheckBox)findViewById(R.id.checkbox_str_led1);
  21. cbStrLeds[1] = (CheckBox)findViewById(R.id.checkbox_str_led2);
  22. cbStrLeds[2] = (CheckBox)findViewById(R.id.checkbox_str_led3);
  23. cbStrLeds[3] = (CheckBox)findViewById(R.id.checkbox_str_led4);
  24. cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
  25. cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
  26. cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
  27. cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
  28. String myString = getText();//调用native方法
  29. // long z = add(2, 3);
  30. // String zstr=Long.toString(z);
  31. textView = (TextView)findViewById(R.id.textView);
  32. textView.setText(myString);
  33. }
  34. public void onClick_write(View view)
  35. {
  36. String str="";
  37. for(int i=0;i<4;i++)
  38. {
  39. if(cbStrLeds[i].isChecked())
  40. str +='1';
  41. else
  42. str +='0';
  43. }
  44. TextView textview = (TextView)findViewById(R.id.textView);
  45. textview.setText(str);
  46. writeLeds(str);
  47. }
  48. public void onClick_ioctl(View view)
  49. {
  50. TextView textview = (TextView)findViewById(R.id.textView);
  51. textview.setText("ioctl_test!");
  52. for(int i=0;i<4;i++)
  53. {
  54. if(cbCmdLeds[i].isChecked())
  55. ioctlLeds(1,i);
  56. else
  57. ioctlLeds(0,i);
  58. }
  59. }
  60. @Override
  61. public boolean onCreateOptionsMenu(Menu menu) {
  62. // Inflate the menu; this adds items to the action bar if it is present.
  63. getMenuInflater().inflate(R.menu.main, menu);
  64. return true;
  65. }
  66. @Override
  67. public boolean onOptionsItemSelected(MenuItem item) {
  68. // Handle action bar item clicks here. The action bar will
  69. // automatically handle clicks on the Home/Up button, so long
  70. // as you specify a parent activity in AndroidManifest.xml.
  71. int id = item.getItemId();
  72. if (id == R.id.action_settings) {
  73. return true;
  74. }
  75. return super.onOptionsItemSelected(item);
  76. }
  77. static {
  78. System.loadLibrary("leds"); //导入链接库
  79. }
  80. }</span>


需要注意的是,想要编译jni中的文件,必须在电脑上提前装好ndk,这也是一个非常客观的工程量。然后重点是,需要在命令行或者界面形式下编译jni,具体方法上网查查吧。

 

 

4.2 JNI/Service程序代码

//LedHalService.cpp

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>
  2. #include <string.h>
  3. #include <assert.h>
  4. #include <jni.h>
  5. #include <hardware/led_hal.h> //用到了和HAL层同一个LED_HARDWARE_MODULE_ID = led_hal
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. struct led_control_device_t *sLedDevice = NULL;
  10. //open the led device by hal
  11. static inline int led_control_open(struct hw_module_t *module,struct led_control_device_t **device)
  12. {
  13. return module->methods->open(module,LED_HARDWARE_MODULE_ID,(struct hw_device_t**)device);
  14. }
  15. //close the led device by hal
  16. jboolean Java_com_example_leds_MainActivity_ledClose (JNIEnv* env, jobject obj)
  17. {
  18. if(sLedDevice)
  19. {
  20. sLedDevice->common.close(&(sLedDevice->common));
  21. }
  22. return 0;
  23. }
  24. //turn on the led
  25. jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)
  26. {
  27. if(sLedDevice)
  28. {
  29. return sLedDevice->set_on(sLedDevice,number);
  30. }
  31. return false;
  32. }
  33. //turn off the led
  34. jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)
  35. {
  36. if(sLedDevice)
  37. {
  38. return sLedDevice->set_off(sLedDevice,number);
  39. }
  40. return false;
  41. }
  42. //led init
  43. jboolean Java_com_example_leds_MainActivity_ledInit (JNIEnv* env, jobject obj)
  44. {
  45. led_module_t *module;
  46. /*看到了吗,hw_get_module是调用HAL模块的核心函数,其中的LED_HARDWARE_MODULE_ID就是前文一直在提的ID号,JNI模块就是通过这个ID号找到的/system/lib/hw文件中相应的HAL共享库模块*/
  47. int err = hw_get_module(LED_HARDWARE_MODULE_ID,(hw_module_t const**)&module);
  48. if(err == 0)
  49. {
  50. if(led_control_open(&(module->common),&sLedDevice) == 0)
  51. return true;
  52. }
  53. sLedDevice = NULL;
  54. return false;
  55. }
  56. static led_control_device_t * get_device(hw_module_t* module, char const* name)
  57. {
  58. int err;
  59. hw_device_t* device;
  60. err = module->methods->open(module,name,&device);
  61. if(err == 0)
  62. {
  63. return (led_control_device_t*)device;
  64. }
  65. else
  66. {
  67. return NULL;
  68. }
  69. }
  70. /*通过JNINativeMethod数组定义了函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数,该方式属于动态调用共享库*/
  71. static JNINativeMethod method_table[]={
  72. {"led_init","()Z",(void*)Java_com_example_leds_MainActivity_ledInit},
  73. {"led_setOn","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOn},
  74. {"led_setOff","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOff},
  75. {"led_close","()Z",(void*)Java_com_example_leds_MainActivity_ledClose},
  76. };
  77. int register_android_server_LedService(JNIEnv *env)
  78. {
  79. //This sentence is very important because we will use this class to call the service
  80. static const char* const kClassName = "com/example/leds/LedService";
  81. //static const char* const kClassName = "com/example/leds";
  82. jclass clazz;
  83. /*Look up the class*/
  84. clazz = env->FindClass(kClassName);
  85. if(clazz == NULL)
  86. {
  87. return -1;
  88. }
  89. /*Register all the methods*/
  90. if(env->RegisterNatives(clazz,method_table,sizeof(method_table)/sizeof(method_table[0]))!=JNI_OK)
  91. {
  92. return -1;
  93. }
  94. /*Fill out the rest of the ID cache*/
  95. return 0;
  96. }
  97. /*系统在成功装在JNI共享库后会自动调用JNI_OnLoad函数,该函数一般用与初始化JNI模块*/
  98. jint JNI_OnLoad(JavaVM* vm, void* reserved)
  99. {
  100. JNIEnv* env = NULL;
  101. jint result = -1;
  102. if(vm->GetEnv((void**) &env,JNI_VERSION_1_4)!=JNI_OK)
  103. {
  104. return result;
  105. }
  106. /*使用下面的函数绑定JNI程序库与Java程序库。若想使用静态调用libled_hal_jni.so,则需要将下面的代码屏蔽掉*/
  107. register_android_server_LedService(env);
  108. /*返回JNI_VERSION_1_4,表明只有运行在JDK1.4及以上版本的Java程序才能调用当前的JNI模块*/
  109. return JNI_VERSION_1_4;
  110. }
  111. #ifdef __cplusplus
  112. }
  113. #endif</span>
 

//Android.mk

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_PRELINK_MODULE :=false
  4. LOCAL_MODULE_TAGS:=optional
  5. LOCAL_SRC_FILES:=LedHalService.cpp
  6. LOCAL_MODULE:=libled_hal_jni
  7. LOCAL_SHARED_LIBRARIES := \
  8. libandroid_runtime \
  9. libcutils \
  10. libhardware \
  11. libhardware_legacy \
  12. libnativehelper \
  13. libsystem_server \
  14. libutils \
  15. libui \
  16. libsurfaceflinger_client
  17. include $(BUILD_SHARED_LIBRARY)
  18. </span>


 

五、APP应用程序层

关于如何在APP中调用JNI库文件,上文中4.1节有提到,不过那是在工程中建立了一个jni文件夹,生成的JNI的库文件直接保存到了当前工程的lib文件夹下。当然也可以不用在当前工程下建立jni文件夹,像4.2那样通过Android原生代码进行编译,然后直接通过绝对路径加载模块也能产生相同功能。

 

5.1 activity_main.xml

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical" >
  6. <LinearLayout
  7. android:layout_width="fill_parent"
  8. android:layout_height="wrap_content"
  9. android:orientation="horizontal" >
  10. <CheckBox
  11. android:id="@+id/checkbox_cmd_led1"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:text="LED1" />
  15. <CheckBox
  16. android:id="@+id/checkbox_cmd_led2"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="LED2" />
  20. <CheckBox
  21. android:id="@+id/checkbox_cmd_led3"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:text="LED3" />
  25. <CheckBox
  26. android:id="@+id/checkbox_cmd_led4"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:text="LED4" />
  30. </LinearLayout>
  31. <Button
  32. android:layout_width="fill_parent"
  33. android:layout_height="wrap_content"
  34. android:onClick="onClick_led"
  35. android:text="led_hal_jni" />
  36. </LinearLayout></span>


5.2 LedServer.java

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;
  2. public class LedService {
  3. private static LedService LedService;
  4. public static LedService getInstance()
  5. {
  6. if (LedService == null)
  7. LedService = new LedService();
  8. return LedService;
  9. }
  10. private LedService()
  11. {
  12. init();
  13. }
  14. public boolean init()
  15. {
  16. return led_init();
  17. }
  18. public boolean setOn(int led)
  19. {
  20. return led_setOn(led);
  21. }
  22. public boolean setOff(int led)
  23. {
  24. return led_setOff(led);
  25. }
  26. // native method
  27. private native boolean led_init();
  28. private native boolean led_setOn(int led);
  29. private native boolean led_setOff(int led);
  30. static
  31. {
  32. System.load("/system/lib/libled_hal_jni.so");
  33. }
  34. }</span>


 

5.3 MainActivity.java

  1. <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;
  2. import com.example.leds.LedService;
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.Menu;
  6. import android.view.MenuItem;
  7. import android.view.View;
  8. import android.widget.CheckBox;
  9. public class MainActivity extends Activity {
  10. private CheckBox[] cbCmdLeds = new CheckBox[4];
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. setContentView(R.layout.activity_main);
  15. cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
  16. cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
  17. cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
  18. cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
  19. /*
  20. LedService ledService = LedService.getInstance();
  21. ledService.setOn(0);
  22. ledService.setOff(1);
  23. ledService.setOn(2);
  24. ledService.setOff(3);
  25. */
  26. }
  27. public void onClick_led(View view)
  28. {
  29. LedService ledService = LedService.getInstance();
  30. for(int i=0;i<4;i++)
  31. {
  32. if(cbCmdLeds[i].isChecked())
  33. ledService.setOn(i);
  34. else
  35. ledService.setOff(i);
  36. }
  37. }
  38. @Override
  39. public boolean onCreateOptionsMenu(Menu menu) {
  40. // Inflate the menu; this adds items to the action bar if it is present.
  41. getMenuInflater().inflate(R.menu.main, menu);
  42. return true;
  43. }
  44. @Override
  45. public boolean onOptionsItemSelected(MenuItem item) {
  46. // Handle action bar item clicks here. The action bar will
  47. // automatically handle clicks on the Home/Up button, so long
  48. // as you specify a parent activity in AndroidManifest.xml.
  49. int id = item.getItemId();
  50. if (id == R.id.action_settings) {
  51. return true;
  52. }
  53. return super.onOptionsItemSelected(item);
  54. }
  55. }</span>


 

好了,关于整套程序都已经写完了。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/663853
推荐阅读
相关标签
  

闽ICP备14008679号