在Android手机中通过i2c读取芯片寄存器(含i2c-tools)解析
在android手机中通过i2c读取芯片寄存器(含i2c-tools)解析。
需求:同事甩来一个某品牌的手机,android,需要把里面某个芯片的寄存器配置参数值读出来。
折腾了两天(坑爹的网络以及……xxx墙),记录一下,以备今后查询
思路:
在android下运行一个c语言程序 用这个程序去访问i2c bus 利用linux i2c bus driver直接访问芯片,读取出内容
1 在android下运行c语言程序
在android下运行c语言程序其实就是配置一个交叉编译环境,用正确的编译器把c语言程序编译出来,然后放到手机里面运行就可以,如果有linux功底的话这个应该不难,关键是找到合适的编译器。
这里使用的编译器是: arm-none-linux-gnueabi-gcc来进行编译,我不知道是不是android针对linux kernel有特殊的库还是什么原因,网上搜出来的大家都是用的这个编译器。一般在linux底下配置交叉编译的时候主要使用的arm-linux-gcc,我觉得这个编译器应该也可以,本来想试试的,结果无奈apt-get不下来……(此处省略一万字……),过几天等我把arm-linux-gcc的环境配好了来验证一下再来更新这里吧。
这里还有一点忘记说了,首先你得把手机root掉,然后在电脑上正确配置adb shell,这个东西的话网上太多了随便搜搜就可以。
配置交叉编译环境和在手机上运行程序下面这个链接里面已经写的非常详细了。
开发环境最好在linux系统下配置arm-none-linux-gnueabi-gcc好像也有windows版的,但是好像不太好用……这里强烈推荐win10 自带的linux bash,做嵌入式开发的时候比好用多了。
这里有两点要注意一下,1、文件不能放在sdcard上面运行,要拷贝到系统目录底下;2、编译的时候要加-static编译选项
ok,现在有一个可以在android下运行的c程序了
2 访问i2c dev
首先要找到你的目标设备挂载在哪里,这个问题如果熟悉linux fs的同学应该好办,这里还是借用别人的一个说明,已经写的比较详细了,其实还有别的方式可以查看到,这里也不在一一列举。
找到目标device之后,使用linux设备的老方法,open,close,ioctrl,read,write走起~
一般来说第一步,open dev
#define chip "/dev/i2c-xxx" int fd = open(chip,o_rdwr); if(-1 == fd) { printf("open faild \r\n"); return 0; } printf("open success fd = 0x%08x \r\n", fd);
这里我的设备是挂载i2c-2上面的。
这一步如果成功了,基本上就不会有什么大问题了~
接下来就是读写:
i2c的读写可以通过两种方式实现,ioctrl和read、write,但这两种方式最后在linux kernel调用的都是同一个函数:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) {}
有兴趣的同学可以下载个linux kernel看看,没兴趣的话可以直接无视。
两种方法,先说read、write这种方式:
unsigned char sendbuf[1] = { reg_chip_id }; unsigned char readbuf[1] = { 0x00 }; if (ioctl(fd, i2c_slave_force, chip_addr_0) < 0) { /* 设置芯片地址 */ printf("oictl:set slave address failed\n"); goto __end; } printf("oictl:set slave address success \r\n"); if(write(fd,sendbuf,1) < 0) { printf("write failed \r\n"); goto __end; } printf("write success 1 \r\n"); if((ret = read(fd, readbuf, 1)) < 0) { printf("read failed \r\n"); goto __end; } printf("readbuf = 0x%02x , ret = %d \r\n", readbuf[0], ret);
代码结构什么的大家就别纠结了……这个只是拿来读一下数据的,不是工程使用的代码,所以……看看意思就行了……
这里的代码是要读一个寄存器,其中chip_addr_0表示设备的i2c地址,reg_chip_id表示寄存器的地址。
第一步先设置client的地址,调用ioctl(fd, i2c_slave_force, chip_addr_0)函数。
其中i2c_slave_force的意义,linux kernel中是这样解释的:
#define i2c_slave 0x0703 /* use this slave address */ #define i2c_slave_force 0x0706 /* use this slave address, even if it is already in use by a driver! */
意思就是,如果该设备已经被占用了(有其他进程已经在使用这个设备),你用i2c_slave设置client是不行的,如果你用i2c_slave_force设置client那么无论如何都可以设置成功(前提是你的配置参数都是正确的……)。
这里设置client是干什么?在read和write时就用到了:
一般i2c的读时序:发送设备地址,rw位置0 -> 发送寄存器地址 -> 发送设备地址,rw位置1 -> 接收返回的数据。
但是这里可以看到实际的读代码中仅仅write了一个寄存器地址,然后就read了,我们并没有显示的发送设备地址以及rw位,其实这些工作都被write和read函数替我们完成了,在write和read函数中,直接拿client的地址值加上rw位进行了发送,所以我们只用根据芯片的datasheet来write、read需要发送和接收的数据。
/** * i2c_master_send - issue a single i2c message in master transmit mode * @client: handle to slave device * @buf: data that will be written to the slave * @count: how many bytes to write, must be less than 64k since msg.len is u16 * * returns negative errno, or else the number of bytes written. */ int i2c_master_send(const struct i2c_client *client, const char *buf, int count) { int ret; struct i2c_adapter *adap = client->adapter; struct i2c_msg msg; msg.addr = client->addr; msg.flags = client->flags & i2c_m_ten; msg.len = count; msg.buf = (char *)buf; ret = i2c_transfer(adap, &msg, 1); /* * if everything went ok (i.e. 1 msg transmitted), return #bytes * transmitted, else error code. */ return (ret == 1) ? count : ret; }
这个是linux kernel中write所调用的函数的,有兴趣的同学看看就会明白设备i2c地址和rw位是怎么被填上去的了。read的就不贴了……
ok,write和read的方式说完了,再来说说通过ioctrl来读写i2c。
unsigned char outbuf[1]; struct i2c_rdwr_ioctl_data packets; struct i2c_msg messages[2]; outbuf[0] = reg_chip_id; messages[0].addr = chip_addr_0; messages[0].flags = 0; messages[0].len = 1; messages[0].buf = outbuf; /* the data will get returned in this structure */ messages[1].addr = chip_addr_0; messages[1].flags = 1/* | i2c_m_nostart*/; messages[1].len = 1; messages[1].buf = outbuf; /* send the request to the kernel and get the result back */ packets.msgs = messages; packets.nmsgs = 2; if((ret = ioctl(fd, i2c_rdwr, &packets)) < 0) { printf("unable to send data , ret = %d \r\n", ret); close(fd); return -1; } printf("outbuf = 0x%02x \r\n", outbuf[0]);
这里面messages和packets是什么这里就不展开说了,其实可以对比一下上面贴出来的kernel里面的
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
函数,其实都是先填充
struct i2c_adapter *adap = client->adapter; struct i2c_msg msg;
这样两个结构体,然后一个调用
ret = i2c_transfer(adap, &msg, 1);
另一个调用
ret = ioctl(fd, i2c_rdwr, &packets)
完成最后的数据发送。
其实,ioctl(fd, i2c_rdwr, &packets)里面也调用了i2c_transfer(adap, &msg, 1),所以,真正完成最后数据发送的函数应该是:
i2c_transfer(adap, &msg, 1)
至于i2c_transfer是什么行为,有兴趣的同学继续去看linux kernel吧,他调用的是master_xfer函数,这个函数是针对不同芯片的i2c控制器编写特定的驱动方法,通过注册的方式赋值给master_xfer函数……更具体的这里就不再展开了
3 读取需要的数据
这个就不说了,对前面的read、write或者ioctrl进行封装,然后根据datasheet找到要读写的寄存器,进行操作就行了。
最后……不要忘了close()呀……
贴个完整的代码吧
#include #include #include #include #include #include #include #include #define chip "/dev/i2c-xx" #define chip_addr_0 (0xxx) #define chip_addr_1 (0xxx) #define chip_addr_2 (0xxx) #define chip_addr_3 (0xxx) #define reg_chip_id 0x00 int main() { int fd = open(chip,o_rdwr); if(-1 == fd) { printf("open faild \r\n"); return -1; } printf("open success fd = 0x%08x \r\n", fd); if (ioctl(fd, i2c_slave_force, chip_addr_0) < 0) { /* 设置芯片地址 */ printf("oictl:set slave address failed\n"); close(fd); return -1; } printf("oictl:set slave address success \r\n"); int ret = 0; #if 0 unsigned char outbuf[1]; struct i2c_rdwr_ioctl_data packets; struct i2c_msg messages[2]; outbuf[0] = reg_chip_id; messages[0].addr = chip_addr_0; messages[0].flags = 0; messages[0].len = 1; messages[0].buf = outbuf; /* the data will get returned in this structure */ messages[1].addr = chip_addr_0; messages[1].flags = 1/* | i2c_m_nostart*/; messages[1].len = 1; messages[1].buf = outbuf; /* send the request to the kernel and get the result back */ packets.msgs = messages; packets.nmsgs = 2; if((ret = ioctl(fd, i2c_rdwr, &packets)) < 0) { printf("unable to send data , ret = %d \r\n", ret); close(fd); return -1; } printf("outbuf = 0x%02x \r\n", outbuf[0]); #else unsigned char sendbuf[1] = { reg_chip_id }; unsigned char readbuf[1] = { 0x00 }; if(write(fd,sendbuf,1) < 0) { printf("write failed \r\n"); goto __end; } printf("write success 1 \r\n"); if((ret = read(fd, readbuf, 1)) < 0) { printf("read failed \r\n"); goto __end; } printf("readbuf = 0x%02x , ret = %d \r\n", readbuf[0], ret); #endif __end : close(fd); return 0; }
为了避免暴露读的是什么东西,这里把所有涉及到地址的位置都换成了xxx……然后自己写一个makefile或者直接用命令进行编译都可以,makefile就不贴了,makefile实在是写的难看……包括这个代码,看看意思就行了,不是工程使用的,不要纠结编码模式、编码规范、编码思想以及代码结构……
ok~说了这么多,以上这些全是野路子……来给大家说一个官方做法吧……(我知道看到这里有些同学想杀人了……)
下载一个i2c-tools,然后……用arm-none-linux-gnueabi-gcc编译器编译成执行文件,然后一样的方法下载到手机里面,运行……
这个玩意怎么用?我看网上有人贴出来android下怎么使用i2c-tools的帖子,写的很好,但是我用不了!!!……为什么?因为我没有android的源码,没有android的开发环境……怎么办?生活总得继续下去呀……
好吧,这里简单的说一下方法吧,懂的人自然懂,不懂得人尽量看懂吧……
其实i2c-tools就是一个c语言的文件,加个makefile就可以跟之前讲的方法一样编译、运行了。这里的makefile源码里面其实有一个,但是要改一改,关键是要把编译器设置成arm-none-linux-gnueabi-gcc。不过我不是这样弄的,因为我只需要里面的一个命令,所以……我就自己写了一个makefile,然后放手机里面运行就行了。makefile我就不贴了,实在写的丑……把源码打开看看应该就知道makefile要怎么写了……
好了,就酱紫了……
对了……最后有一点~非常非常非常重要~!!!!!被这个问题困扰了一上午……设备open,ioctrl都没问题,但是就是死活读不出东西,怎么着都返回-1……
原因:芯片被休眠了……由于手机里面的电源管理的原因,当没某个芯片没什么事的时候,会把该芯片休眠掉,所以如果是针对手机的话,一定要注意这个问题,不要以为手机开机了片子就上电了……
嗯,over……
上一篇: 上川岛有什么好玩的地方 上川岛旅游攻略
下一篇: 海寿岛在哪里 海寿岛有什么好玩