您的位置:首页 > 移动开发 > Android开发

Android平台读写i2c设备开发笔记

2016-04-27 11:07 176 查看
转自: http://blog.csdn.net/rickbeyond/article/details/7839892
Android开发和移植过程中,有时需要对某设备进行读写,但系统可能并未提供相应的服务。我们就需要自己开发硬件访问服务来控制设备。下面的例子是读写最简单的i2c设备eeprom的流程,
i2c的驱动编写有两种方式,一种是利用系统提供的i2c-dev.c来实现一个i2c适配器的设备文件,然后通过在应用层操作I2C适配器来控制I2C设备;另一种是为I2C从设备独立编写一个设备驱动,不需要i2c-dev.c文件。由于前者比较简单通用性强,我们采用前者来展开。

根据android层次划分,我们照例对开发分为如下几步:

1. 添加HAL层接口模块访问设备

2. 使用JNI在应用程序框架层添加服务访问接口

3. 使用服务接口api开发应用程序

一. 添加HAL层接口模块访问设备

首先确认物理设备正常。根据开发板说明书获知设备挂载在/dev/i2c-1上,检测到该设备的存在,则通用设备驱动正常。

eeprom设备为at24c**系列,根据说明书获知设备从地址为0x50,准备工作完毕。

1. 编写hal层接口模块头文件iic.h

进入源码根目录下hardware/libhardware/include/hardware目录新建iic.h,代码如下:

[cpp] view
plain copy

#ifndef ANDROID_IIC_INTERFACE_H

#define ANDROID_IIC_INTERFACE_H

#include <hardware/hardware.h>

__BEGIN_DECLS

/*定义模块ID*/

#define IIC_HARDWARE_MODULE_ID "iic"

/*硬件模块结构体*/

struct iic_module_t {

struct hw_module_t common;

};

/*硬件接口结构体*/

struct iic_device_t {

struct hw_device_t common;

int fd;

int (*iic_write)(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len);

int (*iic_read)(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len);

};

__END_DECLS

#endif

这里定义了iic_write和iic_read两个接口,头文件按照hal规范编写。

2. 编写hal层接口模块文件

进入源码根目录下hardware/libhardware/modules目录新建iic目录,并在iic目录中添加iic.c,代码如下:

[cpp] view
plain copy

#include <hardware/hardware.h>

#include <hardware/iic.h>

#include <fcntl.h>

#include <errno.h>

#include <cutils/log.h>

#include <cutils/atomic.h>

#include <stdio.h>

#include<linux/i2c.h>

#include<linux/i2c-dev.h>

#include <stdlib.h>

#include <linux/types.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ioctl.h>

#include <string.h>

[cpp] view
plain copy

<span style="font-size:14px;">#define DEVICE_NAME "/dev/i2c-1"

#define MODULE_NAME "iic"

#define MODULE_AUTHOR "mfayz@sohu.com"

#define I2C_RETRIES 0x0701/* number of times a device address should be polled when not acknowledging */

#define I2C_TIMEOUT 0x0702/* set timeout in units of 10 ms */

#define I2C_RDWR 0x0707

/*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/

struct i2c_msg

{

unsigned short addr;

unsigned short flags;

#define I2C_M_TEN 0x0010

#define I2C_M_RD 0x0001

unsigned short len;

unsigned char *buf;

};

struct i2c_rdwr_ioctl_data {

struct i2c_msg *msgs;/* pointers to i2c_msgs */

int nmsgs; /* number of i2c_msgs */

};

/*设备打开和关闭接口*/

static int iic_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);

static int iic_device_close(struct hw_device_t* device);

/*设备访问接口*/

static int iic_write(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len);

static int iic_read(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len);

/*模块方法表*/

static struct hw_module_methods_t iic_module_methods = {

open: iic_device_open

};

struct i2c_rdwr_ioctl_data iic_data;

int ret;

/*模块实例变量*/

struct iic_module_t HAL_MODULE_INFO_SYM = {

common: {

tag: HARDWARE_MODULE_TAG,

version_major: 1,

version_minor: 0,

id: IIC_HARDWARE_MODULE_ID,

name: MODULE_NAME,

author: MODULE_AUTHOR,

methods: &iic_module_methods, //实现了一个open的方法供jni层调用,从而实例化eeprom_device_t

}

};

static int iic_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device){

struct iic_device_t* dev;

dev = (struct iic_device_t*)malloc(sizeof(struct iic_device_t));

if(!dev) {

LOGE("iic Stub: failed to alloc space");

return -EFAULT;

}else{

LOGE("hal: alloc space succ!");

}

memset(dev, 0, sizeof(struct iic_device_t));

dev->common.tag = HARDWARE_DEVICE_TAG;

dev->common.version = 0;

dev->common.module = (hw_module_t*)module;

dev->common.close = iic_device_close;

dev->iic_write = iic_write;

dev->iic_read = iic_read;

*device = &dev->common; //将实例化后的iic_device_t地址返回给jni层,这样jni层就可以直接调用方法了。

if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {

LOGE("iic Stub hal: failed to open /dev/i2c-1 -- %s.", strerror(errno));

free(dev);

return -EFAULT;

}else{

LOGI("iic Stub hal: open /dev/i2c-1 successfully.");

iic_data.nmsgs=2;

iic_data.msgs=(struct i2c_msg*)malloc(iic_data.nmsgs*sizeof(struct i2c_msg));

if(!iic_data.msgs){

LOGE("malloc error");

close(dev->fd);

exit(1);

}

ioctl(dev->fd, I2C_TIMEOUT, 2);//设置超时时间

ioctl(dev->fd, I2C_RETRIES, 1);//设置重发次数

}

return 0;

}

static int iic_device_close(struct hw_device_t* device) {

struct iic_device_t* iic_device = (struct iic_device_t*)device;

if(iic_device) {

close(iic_device->fd);

free(iic_device);

}

return 0;

}

static int iic_write(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, unsigned short subAddr, int len) {

int count = 0;

unsigned char data[2];

unsigned char bytes;

LOGI("iic Stub hal: set value %s to device.", dataBuf);

iic_data.nmsgs=1;

(iic_data.msgs[0]).len=2; //写入地址位和数据长度

(iic_data.msgs[0]).addr=slaveAddr;// 设备地址0x50

(iic_data.msgs[0]).flags=0; //write

(iic_data.msgs[0]).buf=(unsigned char*)malloc(2);

while(count<len){

bytes = 0;

data[bytes++] = subAddr;//先写子地址

data[bytes] = dataBuf[count];//再写value

LOGI("IIC write HAL: %x,%x", data[0],data[1]);

(iic_data.msgs[0]).buf=data;//the data to write

ret=ioctl(dev->fd,I2C_RDWR,(unsigned long)&iic_data);

if(ret<0){

LOGI("IIC HAL ioctl error");

}

count++;

subAddr++;

usleep(3000);//延迟3毫秒

}

LOGI("you have write %s into iic at %x address len: %d",dataBuf, subAddr, len);

return 0;

}

static int iic_read(struct iic_device_t* dev, unsigned char* dataBuf, unsigned short slaveAddr, int len){

int count = 0;

iic_data.nmsgs=1;

(iic_data.msgs[0]).len=1;

(iic_data.msgs[0]).addr=slaveAddr; // 设备地址

(iic_data.msgs[0]).flags=I2C_M_RD;//read

(iic_data.msgs[0]).buf=(unsigned char*)malloc(1);

while(count<len){

(iic_data.msgs[0]).buf= dataBuf++;

if(ioctl(dev->fd,I2C_RDWR,(unsigned long)&iic_data)<0){

LOGE("ioctl read error");

}

LOGI("IIC read HAL: %x", dataBuf[count]);

count++;

}

return 0;

}</span>

注意:需打开设备/dev/i2c-1权限,否则会碰到Pemission Denied错误。从源码根目录下进入system/core/rootdir目录,打开ueventd.rc 添加一行:/dev/i2c-1 0666 root root (这里设备各开发板可能不同)

3. 在iic目录下编写android.mk进行编译

[cpp] view
plain copy

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw

LOCAL_SRC_FILES := iic.c

LOCAL_MODULE := iic.default

include $(BUILD_SHARED_LIBRARY)

编译命令:mmm -B hardware/libhardware/module/iic 编译成功会得到iic.default.so,打包进img默认会被加载。

二、 使用JNI在应用程序框架层添加服务访问接口

APP应用不能直接访问HAL层,需要JNI层访问HAL模块并向上提供API接口。可以直接提供接口,但建议最好使用服务的方式提供访问。

我们先看JNI如何访问刚才的HAL模块。

进入源码根目录下的frameworks/base/service/jni目录,新建com_android_server_IICService.cpp,代码如下:

[cpp] view
plain copy

#include "jni.h"

#include "JNIHelp.h"

#include "android_runtime/AndroidRuntime.h"

#include <utils/misc.h>

#include <cutils/log.h>

#include <hardware/hardware.h>

#include <hardware/iic.h>

#include <stdio.h>

namespace android

{

/*在硬件抽象层中定义的硬件访问结构体,参考<hardware/iic.h>*/

struct iic_device_t* iic_device = NULL;

/*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/

static void iic_setVal(JNIEnv* env, jobject clazz, jstring val, jint slaveAddr, jint subAddr, jint len) {

const char *str = env->GetStringUTFChars(val, NULL);

LOGI("iic JNI: set value %s to device.", str);

if(!iic_device) {

LOGI("iic JNI: device is not open.");

return;

}

iic_device->iic_write(iic_device, (unsigned char*)str, slaveAddr, subAddr, len);

env->ReleaseStringUTFChars(val, str); //注意释放资源

}

/*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/

static jstring iic_getVal(JNIEnv* env, jobject clazz, jint slaveAddr, jint len) {

unsigned char* data = (unsigned char*)malloc(len);

iic_device->iic_read(iic_device, data, slaveAddr, len);

if(!iic_device) {

LOGI("iic JNI: device is not open.");

}

int i = 0;

for(;i<strlen((const char*)data);i++){

LOGI("data: %c ", data[i]);

}

//LOGI("iic JNI: get value %s from device @ %x address!", data, subAddr);

jstring tmp = env->NewStringUTF((const char*)data);

free(data);

data = NULL;

return tmp;

}

/*通过硬件抽象层定义的硬件模块open接口打开硬件设备*/

static inline int iic_device_open(const hw_module_t* module, struct iic_device_t** device) {

return module->methods->open(module, IIC_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

}

/*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/

static jboolean iic_init(JNIEnv* env, jclass clazz) {

iic_module_t* module;

LOGI("iic JNI: initializing......");

if(hw_get_module(IIC_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {

LOGI("iic JNI: iic Stub found.");

if(iic_device_open(&(module->common), &iic_device) == 0) {

LOGI("eeprom JNI: iic device is opening...");

return 0;

}

LOGE("eeprom JNI: failed to open iic device.");

return -1;

}

LOGE("eeprom JNI: failed to get iic stub module.");

return -1;

}

/*JNI方法表*/

static const JNINativeMethod method_table[] = {

{"init_native", "()Z", (void*)iic_init},

{"setVal_native", "(Ljava/lang/String;III)V", (void*)iic_setVal},

{"getVal_native", "(III)Ljava/lang/String;", (void*)iic_getVal},

};

/*注册JNI方法*/

int register_android_server_IICService(JNIEnv *env) {

return jniRegisterNativeMethods(env, "com/android/server/IICService", method_table, NELEM(method_table));

}

};

然后需要让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)

package android.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
plain copy

package com.android.server;

import android.content.Context;

import android.os.IIICService;

import android.util.Slog;

public class IICService extends IIICService.Stub {

private static final String TAG = "IICService";

IICService() {

init_native();

}

public void setVal(String val,int slaveAddr, int regAddr, int len) {

setVal_native(val, slaveAddr, regAddr, len);

}

public String getVal(int slaveAddr,int len) {

return getVal_native( slaveAddr, len);

}

//本地方法

private static native boolean init_native();

private static native void setVal_native(String val, int slaveAddr, int regAddr, int len);

private static native String getVal_native(int slaveAddr, int len);

};

从代码中我们可以看到它继承了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(Throwable e){

Slog.e(TAG, "Failure starting IIC Service", e);

}

编译命令:mmm frameworks/base/services/java

或者使用另一种形式来调用服务:如同使用binder机制绑定service一样的方法, 具体就不详细写了。

注意:有可能会编译不通过,因为这里修改了android的官方api, 需要运行make update-api更新frameworks/base/api/current.xml

打包后,app就可以使用IICService接口来访问硬件了。

下一节发上app相关代码

三、app调用服务接口访问硬件

上主要代码EEPROMActivity.java

[java] view
plain copy

package com.zkgd.eeprom;

import android.app.Activity;

import android.os.Bundle;

import android.os.ServiceManager;

import android.os.IIICService;

import android.os.RemoteException;

import android.util.Log;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.EditText;

public class EEPROMActivity extends Activity implements OnClickListener{

private final static String LOG_TAG = "com.zkgd.eeprom";

private IIICService iicService = null;

private EditText valueText = null;

private Button readButton = null;

private Button writeButton = null;

private Button clearButton = null;

int len = 1;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

iicService = IIICService.Stub.asInterface(

ServiceManager.getService("iic"));

valueText = (EditText)findViewById(R.id.edit_value);

readButton = (Button)findViewById(R.id.button_read);

writeButton = (Button)findViewById(R.id.button_write);

clearButton = (Button)findViewById(R.id.button_clear);

readButton.setOnClickListener(this);

writeButton.setOnClickListener(this);

clearButton.setOnClickListener(this);

Log.i(LOG_TAG, "Activity Created");

}

public void onClick(View v) {

if(v.equals(readButton)) {

try {

len = 1;

//在从设备中读取数据

String val = iicService.getVal(0x50,len);

valueText.setText(val);

} catch (RemoteException e) {

Log.e(LOG_TAG, "Remote Exception while reading value from device.");

}

}

else if(v.equals(writeButton)) {

try {

String val = valueText.getText().toString();

len = val.length();

//在从设备的子地址处开始写入数据

iicService.setVal(val,0x50,0x10,len);

} catch (RemoteException e) {

Log.e(LOG_TAG, "Remote Exception while writing value to device.");

}

}

else if(v.equals(clearButton)) {

String text = "";

valueText.setText(text);

}

}

}

工程eeprom放置在源码目录package/app/下

编译命令:mmm package/app/eeprom

打包,烧写固件至开发板,启动就可以看到该应用的图标了。

小结:

整个调用流程为:app<---AIDL访问服务<---JNI本地方法实现<---HALso文件<---硬件

一个问题,这种方法改动了Android原生api,毕竟是访问了硬件。如果想做通用app又想使用c/c++提高效率,直接进行NDK开发,功能编译成库文件打进app应用的工程中。

另一个问题,硬件访问会遭遇到权限问题。如果做通用app,需要设备root了,然后在代码里添加权限修改操作,例如:"chmod 777 "+getPackageCodePath(); "chmod 777 /dev/i2c-1";
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: