http://blog.csdn.net/daniel117/article/details/17464783?locationNum=2&fps=1
在Android开发和移植过程中,有时需要对某设备进行读写,但系统可能并未提供相应的服务。我们就需要自己开发硬件访问服务来控制设备。下面的例子是读写最简单的i2c设备eeprom的流程, i2c的驱动编写有两种方式,一种是利用系统提供的i2c-dev.c来实现一个i2c适配器的设备文件,然后通过在应用层操作I2C适配器来控制I2C设备;另一种是为I2C从设备独立编写一个设备驱动,不需要i2c-dev.c文件。由于前者比较简单通用性强,我们采用前者来展开。
根据android层次划分,我们照例对开发分为如下几步:
1. 添加HAL层接口模块访问设备
2. 使用JNI在应用程序框架层添加服务访问接口
3. 使用服务接口api开发应用程序
首先确认物理设备正常。根据开发板说明书获知设备挂载在/dev/i2c-1上,检测到该设备的存在,则通用设备驱动正常。
eeprom设备为at24c**系列,根据说明书获知设备从地址为0x50,准备工作完毕。
1. 编写hal层接口模块头文件iic.h
进入源码根目录下hardware/libhardware/include/hardware目录新建iic.h,代码如下:
[cpp] view plaincopy
1. #ifndef ANDROID_IIC_INTERFACE_H
2. #define ANDROID_IIC_INTERFACE_H
3. #include
4.
5. __BEGIN_DECLS
6.
7. /*定义模块ID*/
8. #define IIC_HARDWARE_MODULE_ID "iic"
9.
10. /*硬件模块结构体*/
11. struct iic_module_t {
12. struct hw_module_t common;
13. };
14.
15. /*硬件接口结构体*/
16. struct iic_device_t {
17. struct hw_device_t common;
18. int fd;
19. int (*iic_write)(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len);
20. int (*iic_read)(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len);
21. };
22.
23. __END_DECLS
24.
25. #endif
这里定义了iic_write和iic_read两个接口,头文件按照hal规范编写。
2. 编写hal层接口模块文件
进入源码根目录下hardware/libhardware/modules目录新建iic目录,并在iic目录中添加iic.c,代码如下:
[cpp] view plaincopy
1. #include
2. #include
3. #include
4. #include
5. #include
6. #include
7. #include
8. #include
9. #include
10. #include
11. #include
12. #include
13. #include
14. #include
15. #include
[cpp] view plaincopy
1. "font-size:14px;">#define DEVICE_NAME "/dev/i2c-1"
2. #define MODULE_NAME "iic"
3. #define MODULE_AUTHOR "[email protected]"
4.
5.
6. #define I2C_RETRIES 0x0701/* number of times a device address should be polled when not acknowledging */
7. #define I2C_TIMEOUT 0x0702/* set timeout in units of 10 ms */
8. #define I2C_RDWR 0x0707
9.
10.
11. /*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/
12. struct i2c_msg
13. {
14. unsigned short addr;
15. unsigned short flags;
16. #define I2C_M_TEN 0x0010
17. #define I2C_M_RD 0x0001
18. unsigned short len;
19. unsigned char *buf;
20. };
21.
22.
23. struct i2c_rdwr_ioctl_data {
24. struct i2c_msg *msgs;/* pointers to i2c_msgs */
25. int nmsgs; /* number of i2c_msgs */
26. };
27.
28.
29. /*设备打开和关闭接口*/
30. static int iic_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
31. static int iic_device_close(struct hw_device_t* device);
32.
33. /*设备访问接口*/
34. static int iic_write(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len);
35. static int iic_read(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len);
36.
37. /*模块方法表*/
38. static struct hw_module_methods_t iic_module_methods = {
39. open: iic_device_open
40. };
41.
42.
43. struct i2c_rdwr_ioctl_data iic_data;
44. int ret;
45.
46.
47. /*模块实例变量*/
48. struct iic_module_t HAL_MODULE_INFO_SYM = {
49. common: {
50. tag: HARDWARE_MODULE_TAG,
51. version_major: 1,
52. version_minor: 0,
53. id: IIC_HARDWARE_MODULE_ID,
54. name: MODULE_NAME,
55. author: MODULE_AUTHOR,
56. methods: &iic_module_methods, //实现了一个open的方法供jni层调用,从而实例化eeprom_device_t
57. }
58. };
59.
60.
61. static int iic_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device){
62. struct iic_device_t* dev;
63. dev = (struct iic_device_t*)malloc(sizeof(struct iic_device_t));
64. if(!dev) {
65. LOGE("iic Stub: failed to alloc space");
66. return -EFAULT;
67. }else{
68. LOGE("hal: alloc space succ!");
69. }
70.
71. memset(dev, 0, sizeof(struct iic_device_t));
72. dev->common.tag = HARDWARE_DEVICE_TAG;
73. dev->common.version = 0;
74. dev->common.module = (hw_module_t*)module;
75. dev->common.close = iic_device_close;
76. dev->iic_write = iic_write;
77. dev->iic_read = iic_read;
78. *device = &dev->common; //将实例化后的iic_device_t地址返回给jni层,这样jni层就可以直接调用方法了。
79.
80.
81. if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
82. LOGE("iic Stub hal: failed to open /dev/i2c-1 -- %s.", strerror(errno));
83. free(dev);
84. return -EFAULT;
85. }else{
86. LOGI("iic Stub hal: open /dev/i2c-1 successfully.");
87. iic_data.nmsgs=2;
88. iic_data.msgs=(struct i2c_msg*)malloc(iic_data.nmsgs*sizeof(struct i2c_msg));
89.
90.
91. if(!iic_data.msgs){
92. LOGE("malloc error");
93. close(dev->fd);
94. exit(1);
95. }
96. ioctl(dev->fd, I2C_TIMEOUT, 2);//设置超时时间
97. ioctl(dev->fd, I2C_RETRIES, 1);//设置重发次数
98. }
99. return 0;
100. }
101.
102.
103. static int iic_device_close(struct hw_device_t* device) {
104. struct iic_device_t* iic_device = (struct iic_device_t*)device;
105.
106. if(iic_device) {
107. close(iic_device->fd);
108. free(iic_device);
109. }
110.
111. return 0;
112. }
113.
114. static int iic_write(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len) {
115. int count = 0;
116. unsigned char data[2];
117. unsigned char bytes;
118.
119.
120. LOGI("iic Stub hal: set value %s to device.", dataBuf);
121. iic_data.nmsgs=1;
122. (iic_data.msgs[0]).len=2; //写入地址位和数据长度
123. (iic_data.msgs[0]).addr=slaveAddr;// 设备地址0x50
124. (iic_data.msgs[0]).flags=0; //write
125. (iic_data.msgs[0]).buf=(unsigned char*)malloc(2);
126. while(count
127. bytes = 0;
128. data[bytes++] = subAddr;//先写子地址
129. data[bytes] = dataBuf[count];//再写value
130. LOGI("IIC write HAL: %x,%x", data[0],data[1]);
131. (iic_data.msgs[0]).buf=data;//the data to write
132. ret=ioctl(dev->fd,I2C_RDWR,(unsigned long)&iic_data);
133. if(ret<0){
134. LOGI("IIC HAL ioctl error");
135. }
136. count++;
137. subAddr++;
138. usleep(3000);//延迟3毫秒
139. }
140. LOGI("you have write %s into iic at %x address len: %d",dataBuf, subAddr, len);
141.
142. return 0;
143. }
144.
145. static int iic_read(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len){
146. int count = 0;
147.
148. iic_data.nmsgs=1;
149.
150. (iic_data.msgs[0]).len=1;
151. (iic_data.msgs[0]).addr=slaveAddr; // 设备地址
152. (iic_data.msgs[0]).flags=I2C_M_RD;//read
153. (iic_data.msgs[0]).buf=(unsigned char*)malloc(1);
154. while(count 155. (iic_data.msgs[0]).buf= dataBuf++; 156. if(ioctl(dev->fd,I2C_RDWR,(unsigned long)&iic_data)<0){ 157. LOGE("ioctl read error"); 158. } 159. LOGI("IIC read HAL: %x", dataBuf[count]); 160. count++; 161. } 162. 163. return 0; 164. }
注意:需打开设备/dev/i2c-1权限,否则会碰到PemissionDenied错误。从源码根目录下进入system/core/rootdir目录,打开ueventd.rc添加一行:/dev/i2c-10666 root root (这里设备各开发板可能不同)
3. 在iic目录下编写android.mk进行编译
[cpp] view plaincopy
1. LOCAL_PATH := $(call my-dir)
2. include $(CLEAR_VARS)
3. LOCAL_MODULE_TAGS := optional
4. LOCAL_PRELINK_MODULE := false
5. LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
6. LOCAL_SRC_FILES := iic.c
7. LOCAL_MODULE := iic.default
8. include $(BUILD_SHARED_LIBRARY)
编译命令:mmm -Bhardware/libhardware/module/iic 编译成功会得到iic.default.so,打包进img默认会被加载。
APP应用不能直接访问HAL层,需要JNI层访问HAL模块并向上提供API接口。可以直接提供接口,但建议最好使用服务的方式提供访问。
我们先看JNI如何访问刚才的HAL模块。
进入源码根目录下的frameworks/base/service/jni目录,新建com_android_server_IICService.cpp,代码如下:
[cpp] view plaincopy
1. #include "jni.h"
2. #include "JNIHelp.h"
3. #include "android_runtime/AndroidRuntime.h"
4. #include
5. #include
6. #include
7. #include
8. #include
9.
10. namespace android
11. {
12. /*在硬件抽象层中定义的硬件访问结构体,参考
13. struct iic_device_t* iic_device = NULL;
14. /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/
15. static void iic_setVal(JNIEnv* env, jobject clazz, jstring val, jint slaveAddr, jint subAddr, jint len) {
16. const char *str = env->GetStringUTFChars(val, NULL);
17. LOGI("iic JNI: set value %s to device.", str);
18. if(!iic_device) {
19. LOGI("iic JNI: device is not open.");
20. return;
21. }
22. iic_device->iic_write(iic_device, (unsigned char*)str, slaveAddr, subAddr, len);
23. env->ReleaseStringUTFChars(val, str); //注意释放资源
24. }
25.
26. /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/
27. static jstring iic_getVal(JNIEnv* env, jobject clazz, jint slaveAddr, jint len) {
28. unsigned char* data = (unsigned char*)malloc(len);
29. iic_device->iic_read(iic_device, data, slaveAddr, len);
30. if(!iic_device) {
31. LOGI("iic JNI: device is not open.");
32. }
33. int i = 0;
34. for(;i
35. LOGI("data: %c ", data[i]);
36. }
37. //LOGI("iic JNI: get value %s from device @ %x address!", data, subAddr);
38. jstring tmp = env->NewStringUTF((const char*)data);
39. free(data);
40. data = NULL;
41. return tmp;
42. }
43.
44. /*通过硬件抽象层定义的硬件模块open接口打开硬件设备*/
45. static inline int iic_device_open(const hw_module_t* module, struct iic_device_t** device) {
46. return module->methods->open(module, IIC_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
47. }
48. /*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/
49. static jboolean iic_init(JNIEnv* env, jclass clazz) {
50. iic_module_t* module;
51.
52. LOGI("iic JNI: initializing......");
53. if(hw_get_module(IIC_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
54. LOGI("iic JNI: iic Stub found.");
55. if(iic_device_open(&(module->common), &iic_device) == 0) {
56. LOGI("eeprom JNI: iic device is opening...");
57. return 0;
58. }
59. LOGE("eeprom JNI: failed to open iic device.");
60. return -1;
61. }
62. LOGE("eeprom JNI: failed to get iic stub module.");
63. return -1;
64. }
65. /*JNI方法表*/
66. static const JNINativeMethod method_table[] = {
67. {"init_native", "()Z", (void*)iic_init},
68. {"setVal_native", "(Ljava/lang/String;III)V", (void*)iic_setVal},
69. {"getVal_native", "(III)Ljava/lang/String;", (void*)iic_getVal},
70. };
71. /*注册JNI方法*/
72. int register_android_server_IICService(JNIEnv *env) {
73. return jniRegisterNativeMethods(env, "com/android/server/IICService", method_table, NELEM(method_table));
74. }
75. };
然后需要让android启动时加载此jni模块
在同目录下修改onload.cpp:
在namespace android中添加一行 int register_android_server_IICService(JNIEnv *env);
在JNI_onLoad方法中添加一行 register_android_server_IICService(env);
在同目录下修改Android.mk:
LOCAL_SRC_FILES增加一行 com_android_server_IICService \
编译命令:mmm frameworks/base/services/jni
注意: HAL是根据iic_init中的IIC_HARDWARE_MODULE_ID加载相应模块。
然后,使用AIDL进行进程间通信,使APP能访问自定义的硬件服务。
我们需要在frameworks/base/core/Java/android/os中新建IIICService.aidl(注意是III)
packageandroid.os;
interface IIICService {
void setVal(String val, int slaveAddr, int regAddr, int len);
String getVal(int slaveAddr, int len);
}
它定义了服务的接口,接口在IICService中实现并关联到jni本地方法中。
同时我们需要修改frameworkd/base下的Android.mk编译文件,在LOCAL_SRC_FILES中增加 core/java/android/os/IIICService.aidl
编译命令: mmm frameworks/base
下面是AIDL的实现方法类:com.android.server.IICService位置为:frameworks/base/services/java/com/android/server代码如下:
[java] view plaincopy
1. package com.android.server;
2. import android.content.Context;
3. import android.os.IIICService;
4. import android.util.Slog;
5. public class IICService extends IIICService.Stub {
6. private static final String TAG = "IICService";
7. IICService() {
8. init_native();
9. }
10. public void setVal(String val,int slaveAddr, int regAddr, int len) {
11. setVal_native(val, slaveAddr, regAddr, len);
12. }
13. public String getVal(int slaveAddr,int len) {
14. return getVal_native( slaveAddr, len);
15. }
16.
17. //本地方法
18. private static native boolean init_native();
19. private static native void setVal_native(String val, int slaveAddr, int regAddr, int len);
20. private static native String getVal_native(int slaveAddr, int len);
21. };
从代码中我们可以看到它继承了IIICService.Stub,实现两个接口方法。因为硬件访问一般需要放在一个独立的线程中,这里使用了代理的方法来处理app与硬件服务的通信。
最后需要把新增的IICService服务加入到ServiceManager中,这样就可以通过ServiceManager进行调用。
修改frameworks/base/services/java/com/android/server下的SystemServer.java 在run()方法中添加
try{
Slog.i(TAG, "IIC SERVICE");
ServiceManager.addService("iic", new IICService());
}catch(Throwablee){
Slog.e(TAG, "Failure starting IIC Service", e);
}
编译命令:mmmframeworks/base/services/java
或者使用另一种形式来调用服务:如同使用binder机制绑定service一样的方法, 具体就不详细写了。
注意:有可能会编译不通过,因为这里修改了android的官方api,需要运行make update-api更新frameworks/base/api/current.xml
打包后,app就可以使用IICService接口来访问硬件了。
下一节发上app相关代码
上主要代码EEPROMActivity.java
[java] view plaincopy
1. package com.zkgd.eeprom;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.os.ServiceManager;
6. import android.os.IIICService;
7. import android.os.RemoteException;
8. import android.util.Log;
9. import android.view.View;
10. import android.view.View.OnClickListener;
11. import android.widget.Button;
12. import android.widget.EditText;
13.
14. public class EEPROMActivity extends Activity implements OnClickListener{
15. private final static String LOG_TAG = "com.zkgd.eeprom";
16.
17. private IIICService iicService = null;
18.
19. private EditText valueText = null;
20. private Button readButton = null;
21. private Button writeButton = null;
22. private Button clearButton = null;
23. int len = 1;
24. /** Called when the activity is first created. */
25. @Override
26. public void onCreate(Bundle savedInstanceState) {
27. super.onCreate(savedInstanceState);
28. setContentView(R.layout.main);
29.
30. iicService = IIICService.Stub.asInterface(
31. ServiceManager.getService("iic"));
32.
33. valueText = (EditText)findViewById(R.id.edit_value);
34. readButton = (Button)findViewById(R.id.button_read);
35. writeButton = (Button)findViewById(R.id.button_write);
36. clearButton = (Button)findViewById(R.id.button_clear);
37.
38. readButton.setOnClickListener(this);
39. writeButton.setOnClickListener(this);
40. clearButton.setOnClickListener(this);
41.
42. Log.i(LOG_TAG, "Activity Created");
43. }
44.
45. public void onClick(View v) {
46. if(v.equals(readButton)) {
47. try {
48. len = 1;
49. //在从设备中读取数据
50. String val = iicService.getVal(0x50,len);
51. valueText.setText(val);
52. } catch (RemoteException e) {
53. Log.e(LOG_TAG, "Remote Exception while reading value from device.");
54. }
55. }
56. else if(v.equals(writeButton)) {
57. try {
58. String val = valueText.getText().toString();
59. len = val.length();
60. //在从设备的子地址处开始写入数据
61. iicService.setVal(val,0x50,0x10,len);
62. } catch (RemoteException e) {
63. Log.e(LOG_TAG, "Remote Exception while writing value to device.");
64. }
65. }
66. else if(v.equals(clearButton)) {
67. String text = "";
68. valueText.setText(text);
69. }
70. }
71. }
工程eeprom放置在源码目录package/app/下
编译命令:mmm package/app/eeprom
打包,烧写固件至开发板,启动就可以看到该应用的图标了。
小结:
整个调用流程为:app<---AIDL访问服务<---JNI本地方法实现<---HALso文件<---硬件
一个问题,这种方法改动了android原生api,毕竟是访问了硬件。如果想做通用app又想使用c/c++提高效率,直接进行NDK开发,功能编译成库文件打进app应用的工程中。
另一个问题,硬件访问会遭遇到权限问题。如果做通用app,需要设备root了,然后在代码里添加权限修改操作,例如:"chmod777 "+getPackageCodePath(); "chmod 777 /dev/i2c-1";