I2C的协议内容在这里就不大费周章的描述,主要记录一些自己在实际应用过程中觉得有助于调试一些方法。
一、介绍I2C 的几个比较有用的tool
(1)i2cdetect
i2cdetect的主要功能就是I2C设备查询,它用于扫描I2C总线上的设备。它输出一个表,其中包含指定总线上检测到的设备的列表。
该命令的常用格式为:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具体参数的含义如下:
|
-y |
取消交互模式。默认情况下,i2cdetect将等待用户的确认, 当使用此标志时,它将直接执行操作。 |
|---|---|
|
-a |
强制扫描非规则地址。一般不推荐。 |
|
-q |
使用SMBus“快速写入”命令进行探测。一般不推荐。 |
|
-r |
使用SMBus“接收字节”命令进行探测。一般不推荐。 |
|
-F |
显示适配器实现的功能列表并退出。 |
|
-V |
显示I2C工具的版本并推出。 |
|
-l |
显示已经在系统中使用的I2C总线。 |
|
i2cbus |
表示要扫描的I2C总线的编号或名称。 |
|
first last |
表示要扫描的从设备地址范围。 |
该功能的常用方式:
第一,先通过i2cdetect -l查看当前系统中的I2C的总线情况:

第二,若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入i2cdetect -y 1:(其中"–"表示地址被探测到了,但没有芯片应答; "UU"因为这个地址目前正在被一个驱动程序使用,探测被省略;而16进制的地址号60,1e和50则表示发现了一个外部片选从地址为0x60,0x1e(AP3216)和0x50(eeprom)的外设芯片。


第三,查询I2C总线1 (I2C -1)的功能,命令为i2cdetect -F 1:

(2)i2cget
i2cget的主要功能是获取I2C外设某一寄存器的内容。该命令的常用格式为:
i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具体参数的含义如下:
|
-f |
强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问 已经在内核驱动程序控制下的设备。 |
|---|---|
|
-y |
取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此 标志时,它将直接执行操作。 |
|
-a |
允许在0x00 - 0x07和0x78 - 0x7f之间使用地址。一般不推荐。 |
|
i2cbus |
表示要扫描的I2C总线的编号或名称。这个数字应该与i2cdetect -l列出 的总线之一相对应。 |
|
chip-address |
要操作的外设从地址。 |
|
data-address |
被查看外设的寄存器地址。 |
|
mode |
显示数据的方式: b (read byte data, default) w (read word data) c (write byte/read byte) |
下面是完成读取0总线上从地址为0x50的外设的0x10寄存器的数据,命令为:
i2cget -y -f 1 0x50 0x10

(3)i2cset
i2cset的主要功能是通过I2C总线设置设备中某寄存器的值。该命令的常用格式为:
i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] …[mode]
具体参数的含义如下:
|
-f |
强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
|---|---|
|
-r |
在写入值之后立即读取它,并将结果与写入的值进行比较。 |
|
-y |
取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
|
-V |
显示I2C工具的版本并推出。 |
|
i2cbus |
表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
|
-m mask |
如果指定mask参数,那么描述哪些value位将是实际写入data-addres的。掩码中设置为1的位将从值中取出,而设置为0的位将从数据地址中读取,从而由操作保存。 |
|
mode |
b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。 W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成向0总线上从地址为0x50的eeprom的0x10寄存器写入0x55,命令为:
i2cset -y -f 1 0x50 0x10 0x55
然后用i2cget读取1总线上从地址为0x50的eeprom的0x10寄存器的数据,
命令为:i2cget -y -f 1 0x50 0x10
遇到有写保护的外设,i2cget会提示 i2cset: write failed: Permission denied
(4)i2cdump
i2cdump的主要功能查看I2C从设备器件所有寄存器的值。 该命令的常用格式为:i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。具体参数的含义如下:
|
-f |
强制访问设备,即使它已经很忙。 默认情况下,i2cget将拒绝访问已经在内核驱动程序控制下的设备。 |
|---|---|
|
-r |
限制正在访问的寄存器范围。 此选项仅在模式b,w,c和W中可用。对于模式W,first必须是偶数,last必须是奇数。 |
|
-y |
取消交互模式。默认情况下,i2cdetect将等待用户的确认,当使用此标志时,它将直接执行操作。 |
|
-V |
显示I2C工具的版本并推出。 |
|
i2cbus |
表示要扫描的I2C总线的编号或名称。这个数字应该对应于i2cdetect -l列出的总线之一。 |
|
first last |
表示要扫描的从设备地址范围。 |
|
mode |
b: 单个字节 w:16位字 s:SMBus模块 i:I2C模块的读取大小 c: 连续读取所有字节,对于具有地址自动递增功能的芯片(如EEPROM)非常有用。W与 w类似,只是读命令只能在偶数寄存器地址上发出;这也是主要用于EEPROM的。 |
下面是完成读取0总线上从地址为0x50的eeprom的数据,命令为:
i2cdump -f -y 1 0x50

二、在linux应用程序中操作I2C外设(读写)
首先通过前面的介绍,我们已经知道站在cpu的角度来看,操作I2C外设实际上就是通过控制cpu中挂载该I2C外设的I2C控制器,而这个I2C控制器在linux系统中被称为“I2C适配器”,这个已经在驱动简介中介绍过了。而且众所周知,在linux系统中,每一个设备都是以文件的形式存在的,所以在linux中操作I2C外设就变成了操作I2C适配器设备文件。Linux系统(也就是内核)为每个I2C适配器生成了一个主设备号为89的设备节点(次设备号为0-255),它并没有针对特定的I2C外设而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
(1)首先要确定所要操作的I2C adapter的设备文件节点
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容(在这里笔者强烈建议读者好好利用好sys文件系统)
![]()
(2)ioctl命令
这个可以参考内核源码中的include/linux/i2c-dev.h文件。下面举例说明主要的IOCTL命令:
|
I2C_SLAVE_FORCE |
设置I2C从设备地址(只有在该地址空闲的情况下成功) |
|---|---|
|
I2C_SLAVE_FORCE |
强制设置I2C从设备地址(无论内核中是否已有驱动在使用这个地址都会成功) |
|
I2C_TENBIT |
选择地址位长: 0 表示是7bit地址 ; 不等于0 就是10 bit的地址。只有适配器支持I2C_FUNC_10BIT_ADDR,这个请求才是有效的。 |
|
I2C_FUNCS |
获取适配器支持的功能,详细的可以参考文件include/linux/i2c.h |
|
I2C_RDWR |
设置为可读写 |
|
I2C_RETRIES |
设置收不到ACK时的重试次数 |
|
I2C_TIMEOUT |
设置超时的时限 |
(3)实例
第一,打开设备节点
第二,设置从设备的器件地址,通过ioctl(fd,I2C_SLAVE, 0x50),也可用I2C_SLAVE_FORCE
第三,通过write往从设备写数据,第一个字节需要写从设备的寄存器地址,比如0x30,后面再跟着实际数据;
通过read读取从设备的数据,先通过write发要读的寄存器地址给从设备,然后再read数据。
注意事项:
1、在代码段里有 ioctl(fd,I2C_TIMEOUT,100);,这是设置timeout时间,在做项目的时候有遇到如 果timeout时间不够长,会一直等待不到对方的ACK。(比如APM32F3959是 iPhone 配件模块 上的认证芯片,这是在实际项目中遇到的情况);
2、可以在代码中多次尝试write/read,因为有可能会失败,为了提高成功率和可靠性,这一点也 不能忽略。这一点也是在实际中测试出来的。 ioctl(fd,I2C_RETRIES,3);
3、在有写保护的从设备,write会提示Permission denied。
以下是写了一个简单的测试程序:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include "main_opt.h"
#define I2C_DEVICE "/dev/i2c-1"
#define EEPROM_ADDR 0x50
#define DEVICE_ADDR 0x30
#define NUM 3
int I2C_write(int fd,char inRegister,char *inWriteBuf,int inWriteLen,int inRetryCount)
{
int trynum,size,len,err;
char buf[inWriteLen + 1] = {0};
buf[0] = inRegister;
memcpy(&buf[1],inWriteBuf,inWriteLen);
len = inWriteLen + 1;
for(trynum = 1;trynum <= inRetryCount;trynum++)
{
size = write(fd,buf,len);
if (size == len)
{
err = 0;
break;
}
err = -1;
printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inWriteLen, trynum, strerror(errno) );
if (trynum == inRetryCount)
{
printf("Write Try more than %d times\n",inRetryCount);
break;;
}
usleep( 5000 );
}
return err;
}
int I2C_read(int fd,char inRegister,char *inReadBuf,int inReadLen,int inRetryCount)
{
int trynum,size,len,err;
char buf[1] = {0};
if (!inReadBuf)
{
printf("inReadBuf is NULL\n");
return -1;
}
buf[0] = inRegister;
for(trynum = 1;trynum <= inRetryCount;trynum++)
{
size = write(fd,buf,1);
if (size == 1)
{
err = 0;
break;
}
err = -1;
printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inReadLen, trynum, strerror(errno) );
if (trynum == inRetryCount)
{
printf("Write Try more than %d times\n",inRetryCount);
goto __EXIT;
}
usleep( 5000 );
}
usleep( 5000 );
for(trynum = 1;trynum <= inRetryCount;trynum++)
{
size = read(fd,inReadBuf,inReadLen);
if (size == inReadLen)
{
err = 0;
break;
}
err = -1;
printf("write register 0x%02X, %d bytes failed (try %d): %s\n", inRegister, inReadBuf, trynum, strerror(errno) );
if (trynum == inRetryCount)
{
printf("Read Try more than %d times\n",inRetryCount);
goto __EXIT;
}
usleep( 5000 );
}
__EXIT:
return err;
}
int I2C_test()
{
int fd,i,ret=0;
int size = 0;
unsigned char w_add=DEVICE_ADDR;
unsigned char rd_buf[NUM] = {0};
unsigned char wr_buf[NUM] = {0};
printf("hello,this is read_write i2c test \n");
fd =open(I2C_DEVICE, O_RDWR);
if (fd< 0)
printf("open"I2C_DEVICE"failed \n");
else
printf("open %s OK\n",I2C_DEVICE);
if (ioctl(fd,I2C_SLAVE, EEPROM_ADDR) < 0)
{
printf("set slave address failed \n");
return -1;
}
ioctl(fd,I2C_TIMEOUT,100);
ioctl(fd,I2C_RETRIES,3);
I2C_read(fd,DEVICE_ADDR,rd_buf,NUM,5);
for(i = 0; i < NUM;i++)
printf("rd_buf[%d]===%x\n",i,rd_buf[i]);
if (fd > 0)
{
close(fd);
fd = -1;
}
return 0;
}
三、寻址方式和数据传送方式
I2C总线上传送的数据时广义的,既包括地址,又包括真正的数据。
主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,‘0’表示主机发送数据给->从机,‘1’表示从机发送数据给->主机。
总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器。
I2C总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器,即一帧共有9位。
I2C每次发送数据必须是8位。
MSB固定,先发高位,再发低位。
本文介绍了Linux下I2C编程,包括常用的I2C工具i2cdetect、i2cget、i2cset和i2cdump的使用方法,并详细阐述了在Linux应用程序中进行I2C外设读写操作的步骤和注意事项,以及I2C的寻址方式和数据传送方式。
279

被折叠的 条评论
为什么被折叠?



