赞
踩
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 驱动源码分析
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <linux/kernel.h>
-
- #include <linux/module.h>
-
- #include <linux/miscdevice.h>
-
- #include <linux/fs.h>
-
- #include <linux/types.h>
-
- #include <linux/moduleparam.h>
-
- #include <linux/slab.h>
-
- #include <linux/ioctl.h>
-
- #include <linux/cdev.h>
-
- #include <linux/delay.h>
-
-
-
- #include <mach/gpio.h>
-
- #include <mach/regs-gpio.h>
-
- #include <plat/gpio-cfg.h>
-
- #include <asm/uaccess.h>
-
-
-
- #define DEVICE_NAME "leds"
-
- static unsigned char mem[4];
-
- static int led_gpios[] = {
-
- S5PV210_GPJ2(0),
-
- S5PV210_GPJ2(1),
-
- S5PV210_GPJ2(2),
-
- S5PV210_GPJ2(3),
-
- };
-
-
-
- #define LED_NUM ARRAY_SIZE(led_gpios)
-
-
-
- //mini210_leds_ioctl函数实现了发送IO控制命令
-
- static long mini210_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
-
- {
-
- switch(cmd) {
-
- case 0:
-
- case 1:
-
- if (arg > LED_NUM) {
-
- return -EINVAL;
-
- }
-
-
-
- gpio_set_value(led_gpios[arg], !cmd);
-
- //printk(DEVICE_NAME": %d %d\n", arg, cmd);
-
- break;
-
-
-
- default:
-
- return -EINVAL;
-
- }
-
-
-
- return 0;
-
- }
-
- //mini210_leds_write函数实现了写文件命令
-
- ssize_t mini210_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)
-
- {
-
- int i=0;
-
- int tmp=count;
-
- memset(mem,0,4);
-
- if(count>4)
-
- {
-
- tmp=4;
-
- }
-
- if(copy_from_user(mem,buf,tmp))
-
- {
-
- return -EFAULT;
-
- }
-
-
-
- for(i=0;i<4;i++)
-
- {
-
- if(mem[i]=='1')
-
- {
-
- gpio_set_value(led_gpios[i], 0);
-
- }
-
- else if(mem[i]=='0')
-
- {
-
- gpio_set_value(led_gpios[i], 1);
-
- }
-
- }
-
- return count;
-
- }
-
- static struct file_operations mini210_led_dev_fops = {
-
- .owner = THIS_MODULE,
-
- .unlocked_ioctl = mini210_leds_ioctl,
-
- .write = mini210_leds_write,
-
- };
-
-
-
- static struct miscdevice mini210_led_dev = {
-
- .minor = MISC_DYNAMIC_MINOR,
-
- .name = DEVICE_NAME,
-
- .fops = &mini210_led_dev_fops,
-
- };
-
-
-
- static int __init mini210_led_dev_init(void) {
-
- int ret;
-
- int i;
-
-
-
- for (i = 0; i < LED_NUM; i++) {
-
- ret = gpio_request(led_gpios[i], "LED");
-
- if (ret) {
-
- printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
-
- led_gpios[i], ret);
-
- return ret;
-
- }
-
-
-
- s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
-
- gpio_set_value(led_gpios[i], 1);
-
- }
-
-
-
- ret = misc_register(&mini210_led_dev);
-
-
-
- printk(DEVICE_NAME"\tinitialized\n");
-
-
-
- return ret;
-
- }
-
-
-
- static void __exit mini210_led_dev_exit(void) {
-
- int i;
-
-
-
- for (i = 0; i < LED_NUM; i++) {
-
- gpio_free(led_gpios[i]);
-
- }
-
-
-
- misc_deregister(&mini210_led_dev);
-
- }
-
-
-
- module_init(mini210_led_dev_init);
-
- module_exit(mini210_led_dev_exit);
-
-
-
- MODULE_LICENSE("GPL");</span>
2.3 利用可执行文件测试驱动
2.3.1 源文件
首先给出两个可执行文件的源文件
//ioctl_test.c
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <fcntl.h>
-
- #include <stdio.h>
-
- #include <stdlib.h>
-
- #include <sys/ioctl.h>
-
- /*
- * ./ioctl_test /dev/leds 1 2 //表示将第二个灯点亮
- */
-
- int main(int argc, char **argv)
-
- {
-
- int file_handler = 0;
-
- int cmd = 0;
-
- int arg = 0;
-
- if(argc < 4)
-
- {
-
- printf("Usage: ioctl <dev_file> <cmd> <arg>\n");
-
- return 0;
-
- }
-
- cmd = atoi(argv[2]);
-
- arg = atoi(argv[3]);
-
- printf("dev:%s\n", argv[1]);
-
- printf("cmd:%d\n", cmd);
-
- printf("arg:%d\n", arg);
-
- file_handler = open(argv[1], 0);
-
-
-
- ioctl(file_handler, cmd, arg);
-
- close(file_handler);
-
- return 0;
-
- }</span>
//write_test.c
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <unistd.h>
-
- #include <sys/types.h>
-
- #include <sys/stat.h>
-
- #include <fcntl.h>
-
- #include <stdio.h>
-
- #include <string.h>
-
- /*
- * ./write_test /dev/s3c6410_leds "1111" 表示四个灯全亮
- */
-
- void main(int argc, char *argv[])
-
- {
-
- int fd;
-
- char *buf;
-
- buf=argv[2];
-
- fd=open(argv[1],O_WRONLY);
-
- if(fd<0)
-
- {
-
- printf("file_open failed!\n");
-
- return;
-
- }
-
- write(fd,buf,strlen(buf));
-
- close(fd);
-
- }</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文件,内容如下:
- <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:=$(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_SRC_FILES:=ioctl_test.c
-
- LOCAL_MODULE:=ioctl_test
-
- LOCAL_MODULE_TAGS:=optional
-
- 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
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#ifndef ANDROID_LED_INTERFACE_H
-
- #define ANDROID_LED_INTERFACE_H
-
- #include <stdint.h>
-
- #include <sys/cdefs.h>
-
- #include <sys/types.h>
-
- #include <hardware/hardware.h>
-
- #include <fcntl.h>
-
- #include <errno.h>
-
- #include <math.h>
-
- #include <poll.h>
-
- #include <unistd.h>
-
- #include <dirent.h>
-
- #include <sys/select.h>
-
- #include <cutils/log.h>
-
-
-
- __BEGIN_DECLS
-
- /*下面这个ID重要的很,Jni/Service层主要通过它来寻找相应的HAL库文件(.so),所以HAL源文件的名字是可以随便改的,只要保证其中的ID值不变即可*/
-
- #define LED_HARDWARE_MODULE_ID "led_hal"
-
-
-
- /*HAL规定不能直接使用hw_module_t结构体,需要在hw_modult_t外再套一层结构体。hw_module_t结构体表示HAL模块的相关信息,成员变量可以随便起,但是hw_module_t结构体必须是led_module_t结构体的第1个成员变量的数据类型。这个东西是整个HAL的核心,后续的工作其实就是不断的完善这个module。*/
-
- struct led_module_t
-
- {
-
- struct hw_module_t common;
-
- };
-
-
-
- #define IOCTL_GPIO_ON 1
-
- #define IOCTL_GPIO_OFF 0
-
-
-
- /*led_control_device_t是自定义的hw_device_t类型的结构体,但是hw_device_t必须是该结构体的第一个成员变量,另外还定义了两个控制LED的成员函数*/
-
- struct led_control_device_t
-
- {
-
- struct hw_device_t common;
-
- int (*set_on)(struct led_control_device_t* dev, int32_t LED_NUMBER);
-
- int (*set_off)(struct led_control_device_t* dev, int32_t LED_NUMBER);
-
- };
-
- __END_DECLS
-
- #endif //ANDROID_LED_INTERFACE_H</span>
/
Led_hal.c文件是HAL层的源文件,我在每个函数前都做了注解,其编号是按照代码书写顺序标注的。也就是说,HAL代码的编写顺序并不是自上而下一行一行写出来的,而是类似于驱动程序的形式,先从接口程序开始写,然后一层一层的把用到的函数补充出来。
//Led_hal.c
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <hardware/led_hal.h>
-
- char const * const LED_DEVICE = "/dev/leds";
-
- static int fd = -1;
-
- /*4、打开设备文件,这里的open函数就是运行在了用户空间*/
-
- static int open_led()
-
- {
-
- if((fd = open(LED_DEVICE,O_RDWR))==-1)
-
- {
-
- LOGV("LED stub: open %s failed\n",LED_DEVICE);
-
- return -1;
-
- }
-
- else
-
- {LOGV("LED stub: open %s successed\n",LED_DEVICE);}
-
- return 1;
-
- }
-
- /*5、关闭设备,没什么说的*/
-
- static int close_led(struct hw_device_t *dev)
-
- {
-
- LOGV("close_light is called");
-
- if(fd!=-1)
-
- {
-
- close(fd);
-
- if(dev)
-
- free(dev);
-
- }
-
- return 0;
-
- }
-
- /*6、ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER)运行在了用户空间*/
-
- int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER)
-
- {
-
- if(fd == -1)
-
- return -1;
-
- return ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);
-
- }
-
- /*7、ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER)运行在了用户空间*/
-
- int led_off(struct led_control_device *dev, int32_t LED_NUMBER)
-
- {
-
- if(fd == -1)
-
- return -1;
-
- return ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);
-
- }
-
- /*3、初始化设备文件*/
-
- static int led_init(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
-
- {
-
- struct led_control_device_t *dev;
-
- /*为led_control_device结构体分配内存空间*/
-
- dev = (struct led_control_device_t *)malloc(sizeof(*dev));
-
- if(dev==NULL)
-
- return 0;
-
- memset(dev,0,sizeof(*dev));
-
- dev->common.tag = HARDWARE_DEVICE_TAG;
-
- dev->common.version = 0;
-
- dev->common.module = (struct hw_module_t*)module;
-
- //dev->common.close = (int (*)(struct hw_device_t *))close_led;
-
- dev->common.close = close_led;
-
- //设置打开LED的函数指针
-
- dev->set_on = led_on;
-
- //设置关闭LED的函数指针
-
- dev->set_off = led_off;
-
-
-
- *device = (struct hw_device_t *)&dev->common;
-
- if(open_led() == -1)
-
- {
-
- free(dev);
-
- dev = NULL;
-
- return -1;
-
- }
-
- return 0;
-
- }
-
- /*2、hw_module_methods_t中定义了打开设备的open函数的指针。也就是在这个函数中进行了一些列的初始化工作*/
-
- static struct hw_module_methods_t led_module_methods={
-
- open: led_init
-
- };
-
- /*1、HAL_MODULE_INFO_SYM才是HAL真正的入口,这里面最重要的两个参数是id: LED_HARDWARE_MODULE_ID和methods: &led_module_methods*/
-
- struct led_module_t HAL_MODULE_INFO_SYM={
-
- common:
-
- {
-
- tag: HARDWARE_MODULE_TAG,
-
- version_major: 1,
-
- version_minor: 0,
-
- id: LED_HARDWARE_MODULE_ID,
-
- name: "sample led hal stub",
-
- author: "xxx",
-
- methods: &led_module_methods,
-
- }
-
- };</span>
3.3 HAL源码编译
//Android.mk
- <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)
-
- # HAL module implemenation stored in
-
- # hw/<COPYPIX_HARDWARE_MODULE_ID>.<ro.board.platform>.so
-
- include $(CLEAR_VARS)
-
- LOCAL_PRELINK_MODULE := false
-
- LOCAL_SRC_FILES := led_hal.c
-
- LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
-
- #LOCAL_SHARED_LIBRARIES := liblog
-
- LOCAL_MODULE := led_hal.default
-
- #LOCAL_MODULE := led_hal.$(TARGET_BOARD_PLATFORM)
-
- LOCAL_MODULE_TAGS := optional
-
- 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
- <span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
- android:layout_width="fill_parent"
-
- android:layout_height="fill_parent"
-
- android:orientation="vertical" >
-
- <LinearLayout
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:orientation="horizontal" >
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_str_led1"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED1" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_str_led2"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED2" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_str_led3"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED3" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_str_led4"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED4" />
-
- </LinearLayout>
-
-
-
- <Button
-
-
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:onClick="onClick_write"
-
- android:text="write_test" />
-
- <LinearLayout
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:orientation="horizontal" >
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led1"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED1" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led2"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED2" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led3"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED3" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led4"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED4" />
-
- </LinearLayout>
-
-
-
- <Button
-
-
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:onClick="onClick_ioctl"
-
- android:text="ioctl_test" />
-
-
-
- <TextView
-
- android:id="@+id/textView"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="TextView" />
-
-
-
- </LinearLayout></span>
// jni/leds.c
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <string.h>
-
- #include <stdio.h>
-
- #include <jni.h>
-
- #include <fcntl.h>
-
- #include <sys/types.h>
-
- #include <sys/stat.h>
-
- #include <unistd.h>
-
- #include <stdlib.h>
-
- jstring Java_com_example_leds1_MainActivity_getText (JNIEnv* env, jobject obj)
-
- {
-
- return (*env)->NewStringUTF(env, "Test Android NDK!Test My NDK!");
-
- }
-
-
-
- char* jstring_to_pchar(JNIEnv* env, jstring str)
-
- {
-
- char* pstr = NULL;
-
- jclass clsstring = (*env)->FindClass(env, "java/lang/String");
-
- jstring strencode = (*env)->NewStringUTF(env, "utf-8");
-
- jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
-
- "(Ljava/lang/String;)[B");
-
- jbyteArray byteArray = (jbyteArray)(
-
- (*env)->CallObjectMethod(env, str, mid, strencode));
-
- jsize size = (*env)->GetArrayLength(env, byteArray);
-
- jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
-
- if (size > 0)
-
- {
-
- pstr = (char*) malloc(size);
-
- memcpy(pstr, pbyte, size);
-
- }
-
- return pstr;
-
- }
-
-
-
- void Java_com_example_leds1_MainActivity_writeLeds(JNIEnv* env, jobject thiz, jstring str)
-
- {
-
- int fd;
-
- fd = open("/dev/leds",O_RDWR);
-
- char* pstr = jstring_to_pchar(env,str);
-
- write(fd,pstr,strlen(pstr));
-
- close(fd);
-
- }
-
- void Java_com_example_leds1_MainActivity_ioctlLeds(JNIEnv* env, jobject thiz, jint cmd, jint arg)
-
- {
-
- int fd;
-
- fd = open("/dev/leds",O_RDWR);
-
- ioctl(fd,cmd,arg);
-
- close(fd);
-
- }</span>
//jni/Android.mk
- <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH := $(call my-dir)
-
-
-
- include $(CLEAR_VARS)
-
-
-
- LOCAL_MODULE := leds
-
- LOCAL_SRC_FILES := leds.c
-
-
-
- include $(BUILD_SHARED_LIBRARY) </span>
//MainActivity.java
- <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds1;
-
-
-
- import android.app.Activity;
-
- import android.os.Bundle;
-
- import android.view.Menu;
-
- import android.view.View;
-
- import android.view.MenuItem;
-
- import android.widget.TextView;
-
- import android.widget.CheckBox;
-
-
-
- public class MainActivity extends Activity {
-
- private CheckBox[] cbStrLeds = new CheckBox[4];
-
- private CheckBox[] cbCmdLeds = new CheckBox[4];
-
- TextView textView;
-
- public native String getText();//声明native 方法
-
- public native void writeLeds(String str);
-
- public native void ioctlLeds(int cmd, int arg);
-
- @Override
-
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_main);
-
- cbStrLeds[0] = (CheckBox)findViewById(R.id.checkbox_str_led1);
-
- cbStrLeds[1] = (CheckBox)findViewById(R.id.checkbox_str_led2);
-
- cbStrLeds[2] = (CheckBox)findViewById(R.id.checkbox_str_led3);
-
- cbStrLeds[3] = (CheckBox)findViewById(R.id.checkbox_str_led4);
-
-
-
- cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
-
- cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
-
- cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
-
- cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
-
-
-
-
-
- String myString = getText();//调用native方法
-
- // long z = add(2, 3);
-
- // String zstr=Long.toString(z);
-
- textView = (TextView)findViewById(R.id.textView);
-
- textView.setText(myString);
-
- }
-
- public void onClick_write(View view)
-
- {
-
-
-
- String str="";
-
- for(int i=0;i<4;i++)
-
- {
-
- if(cbStrLeds[i].isChecked())
-
- str +='1';
-
- else
-
- str +='0';
-
- }
-
- TextView textview = (TextView)findViewById(R.id.textView);
-
- textview.setText(str);
-
- writeLeds(str);
-
- }
-
- public void onClick_ioctl(View view)
-
- {
-
- TextView textview = (TextView)findViewById(R.id.textView);
-
- textview.setText("ioctl_test!");
-
- for(int i=0;i<4;i++)
-
- {
-
- if(cbCmdLeds[i].isChecked())
-
- ioctlLeds(1,i);
-
- else
-
- ioctlLeds(0,i);
-
- }
-
- }
-
-
-
- @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);
-
- }
-
- static {
-
- System.loadLibrary("leds"); //导入链接库
-
- }
-
- }</span>
需要注意的是,想要编译jni中的文件,必须在电脑上提前装好ndk,这也是一个非常客观的工程量。然后重点是,需要在命令行或者界面形式下编译jni,具体方法上网查查吧。
4.2 JNI/Service程序代码
//LedHalService.cpp
- <span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>
-
- #include <string.h>
-
-
-
- #include <assert.h>
-
- #include <jni.h>
-
- #include <hardware/led_hal.h> //用到了和HAL层同一个LED_HARDWARE_MODULE_ID = led_hal
-
-
-
- #ifdef __cplusplus
-
- extern "C" {
-
- #endif
-
-
-
- struct led_control_device_t *sLedDevice = NULL;
-
-
-
- //open the led device by hal
-
-
-
- static inline int led_control_open(struct hw_module_t *module,struct led_control_device_t **device)
-
- {
-
- return module->methods->open(module,LED_HARDWARE_MODULE_ID,(struct hw_device_t**)device);
-
- }
-
-
-
- //close the led device by hal
-
- jboolean Java_com_example_leds_MainActivity_ledClose (JNIEnv* env, jobject obj)
-
- {
-
- if(sLedDevice)
-
- {
-
- sLedDevice->common.close(&(sLedDevice->common));
-
- }
-
- return 0;
-
- }
-
- //turn on the led
-
- jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)
-
- {
-
- if(sLedDevice)
-
- {
-
- return sLedDevice->set_on(sLedDevice,number);
-
- }
-
- return false;
-
- }
-
- //turn off the led
-
- jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)
-
- {
-
- if(sLedDevice)
-
- {
-
- return sLedDevice->set_off(sLedDevice,number);
-
- }
-
- return false;
-
- }
-
-
-
- //led init
-
-
-
- jboolean Java_com_example_leds_MainActivity_ledInit (JNIEnv* env, jobject obj)
-
- {
-
- led_module_t *module;
-
- /*看到了吗,hw_get_module是调用HAL模块的核心函数,其中的LED_HARDWARE_MODULE_ID就是前文一直在提的ID号,JNI模块就是通过这个ID号找到的/system/lib/hw文件中相应的HAL共享库模块*/
-
- int err = hw_get_module(LED_HARDWARE_MODULE_ID,(hw_module_t const**)&module);
-
- if(err == 0)
-
- {
-
- if(led_control_open(&(module->common),&sLedDevice) == 0)
-
- return true;
-
- }
-
- sLedDevice = NULL;
-
- return false;
-
- }
-
-
-
- static led_control_device_t * get_device(hw_module_t* module, char const* name)
-
- {
-
- int err;
-
- hw_device_t* device;
-
- err = module->methods->open(module,name,&device);
-
- if(err == 0)
-
- {
-
- return (led_control_device_t*)device;
-
- }
-
- else
-
- {
-
- return NULL;
-
- }
-
- }
-
- /*通过JNINativeMethod数组定义了函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数,该方式属于动态调用共享库*/
-
- static JNINativeMethod method_table[]={
-
- {"led_init","()Z",(void*)Java_com_example_leds_MainActivity_ledInit},
-
- {"led_setOn","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOn},
-
- {"led_setOff","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOff},
-
- {"led_close","()Z",(void*)Java_com_example_leds_MainActivity_ledClose},
-
- };
-
- int register_android_server_LedService(JNIEnv *env)
-
- {
-
- //This sentence is very important because we will use this class to call the service
-
- static const char* const kClassName = "com/example/leds/LedService";
-
- //static const char* const kClassName = "com/example/leds";
-
- jclass clazz;
-
- /*Look up the class*/
-
- clazz = env->FindClass(kClassName);
-
- if(clazz == NULL)
-
- {
-
- return -1;
-
- }
-
- /*Register all the methods*/
-
- if(env->RegisterNatives(clazz,method_table,sizeof(method_table)/sizeof(method_table[0]))!=JNI_OK)
-
- {
-
- return -1;
-
- }
-
- /*Fill out the rest of the ID cache*/
-
- return 0;
-
- }
-
- /*系统在成功装在JNI共享库后会自动调用JNI_OnLoad函数,该函数一般用与初始化JNI模块*/
-
- jint JNI_OnLoad(JavaVM* vm, void* reserved)
-
- {
-
- JNIEnv* env = NULL;
-
- jint result = -1;
-
- if(vm->GetEnv((void**) &env,JNI_VERSION_1_4)!=JNI_OK)
-
- {
-
- return result;
-
- }
-
- /*使用下面的函数绑定JNI程序库与Java程序库。若想使用静态调用libled_hal_jni.so,则需要将下面的代码屏蔽掉*/
-
- register_android_server_LedService(env);
-
- /*返回JNI_VERSION_1_4,表明只有运行在JDK1.4及以上版本的Java程序才能调用当前的JNI模块*/
-
- return JNI_VERSION_1_4;
-
- }
-
- #ifdef __cplusplus
-
- }
-
- #endif</span>
//Android.mk
- <span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_PRELINK_MODULE :=false
-
- LOCAL_MODULE_TAGS:=optional
-
- LOCAL_SRC_FILES:=LedHalService.cpp
-
- LOCAL_MODULE:=libled_hal_jni
-
-
-
- LOCAL_SHARED_LIBRARIES := \
-
- libandroid_runtime \
-
- libcutils \
-
- libhardware \
-
- libhardware_legacy \
-
- libnativehelper \
-
- libsystem_server \
-
- libutils \
-
- libui \
-
- libsurfaceflinger_client
-
-
-
- include $(BUILD_SHARED_LIBRARY)
-
- </span>
五、APP应用程序层
关于如何在APP中调用JNI库文件,上文中4.1节有提到,不过那是在工程中建立了一个jni文件夹,生成的JNI的库文件直接保存到了当前工程的lib文件夹下。当然也可以不用在当前工程下建立jni文件夹,像4.2那样通过Android原生代码进行编译,然后直接通过绝对路径加载模块也能产生相同功能。
5.1 activity_main.xml
- <span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
- android:layout_width="fill_parent"
-
- android:layout_height="fill_parent"
-
- android:orientation="vertical" >
-
-
-
- <LinearLayout
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:orientation="horizontal" >
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led1"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED1" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led2"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED2" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led3"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED3" />
-
-
-
- <CheckBox
-
- android:id="@+id/checkbox_cmd_led4"
-
- android:layout_width="wrap_content"
-
- android:layout_height="wrap_content"
-
- android:text="LED4" />
-
- </LinearLayout>
-
-
-
- <Button
-
-
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:onClick="onClick_led"
-
- android:text="led_hal_jni" />
-
-
-
- </LinearLayout></span>
5.2 LedServer.java
- <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;
-
-
-
-
-
- public class LedService {
-
- private static LedService LedService;
-
-
-
- public static LedService getInstance()
-
- {
-
- if (LedService == null)
-
- LedService = new LedService();
-
- return LedService;
-
- }
-
-
-
- private LedService()
-
- {
-
- init();
-
- }
-
-
-
- public boolean init()
-
- {
-
- return led_init();
-
- }
-
- public boolean setOn(int led)
-
- {
-
- return led_setOn(led);
-
- }
-
- public boolean setOff(int led)
-
- {
-
- return led_setOff(led);
-
- }
-
-
-
- // native method
-
- private native boolean led_init();
-
-
-
- private native boolean led_setOn(int led);
-
-
-
- private native boolean led_setOff(int led);
-
-
-
- static
-
- {
-
- System.load("/system/lib/libled_hal_jni.so");
-
- }
-
- }</span>
5.3 MainActivity.java
- <span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;
-
-
-
- import com.example.leds.LedService;
-
- import android.app.Activity;
-
- import android.os.Bundle;
-
- import android.view.Menu;
-
- import android.view.MenuItem;
-
- import android.view.View;
-
- import android.widget.CheckBox;
-
-
-
- public class MainActivity extends Activity {
-
-
-
- private CheckBox[] cbCmdLeds = new CheckBox[4];
-
- @Override
-
- protected void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_main);
-
- cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);
-
- cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);
-
- cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);
-
- cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);
-
- /*
- LedService ledService = LedService.getInstance();
- ledService.setOn(0);
- ledService.setOff(1);
- ledService.setOn(2);
- ledService.setOff(3);
- */
-
- }
-
- public void onClick_led(View view)
-
- {
-
- LedService ledService = LedService.getInstance();
-
- for(int i=0;i<4;i++)
-
- {
-
- if(cbCmdLeds[i].isChecked())
-
- ledService.setOn(i);
-
- else
-
- ledService.setOff(i);
-
- }
-
- }
-
- @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);
-
- }
-
- }</span>
好了,关于整套程序都已经写完了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。