前言:
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,一条时钟线SCL和一条数据线SDA,极大地简化对硬件资源和 PCB 板布线空间的占用,半双工通信,主从模式,支持一对多,被非常广泛地应用在 EEPROM 、实时钟、小型 LCD 等设备与 CPU 的接口中。
整体时序
I2C总线上的每个设备都有唯一地址,数据包传输时先发送地址位,接着是数据。一个地址字节由7个地址位(可以挂128个设备)和1个指示位组成(7位寻址模式),0表示写,1表示读。一般芯片手册I2C地址都是7位地址,有些与某个引脚的电平相关,主机控制最后读写位。
使用时注意配置从机地址及时钟频率,其他的起始、结束等时序等其实不太关注。
时钟信号是由主控器件产生。所有接到 IIC 总线设备上的串行数据 SDA 都接到总线的 SDA 上,各设备的时钟线 SCL 接到总线的 SCL 上。对于并联在一条总线上的每个 IIC 都有唯一的地址。
IIC 总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号 和 应答信号。空闲状态、数据的有效性、数据传输。
起始和停止
起始条件S:当SCL高电平时,SDA由高电平向低电平转换;
停止条件P:当SCL高电平时,SDA由低电平向高电平转换。
起始和停止条件一般由主机产生,总线在起始条件后处于busy的状态,在停止条件的某段时间后,总线才再次处于空闲状态。空闲时SDA和SCL上的电平都为高电平。
A先把SDA拉低,等SDA变为低电平后再把SCL拉低(以上两个动作构成了I2C的起始位),此时SDA就可以发送数据了,与此同时,SCL发送一定周期的脉冲,SDA发送数据和SCL发送脉冲的要符合的关系是:SDA必须在SCL是高电平时保持有效,在SCL是低电平时发送下一位(SCL会在上升沿对SDA进行采样)。
传输与响应
一次传8位数据,8位数据传输结束后A释放SDA,SCL再发一个脉冲(这是第九个脉冲),触发B将SDA置为低电平表示确认(该低电平称为ACK)。最后SCL先变为高电平,SDA再变为高电平(以上两个动作称为结束标志),如果B没有将SDA置为0 ,则A停止发送下一帧数据。
Linux I2C架构
Linux的I2C构架分为三个部分:
1)I2C core框架
提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器;
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。I2C core框架具体实现在/drivers/i2c目录下的i2c-core.c和i2c-dev.c
2) I2C总线驱动
定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结构进行描述。经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。I2C总线驱动具体实现在/drivers/i2c目录下busses文件夹。
platform是linux虚拟的总线,称为platform总线,相应的设备称为platform_device,相应的驱动称为platform_driver。i2c总线对应一个设备,即i2c_adapter结构。
3) I2C 设备驱动
对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。其中主要包含i2c_driver和i2c_client数据结构,i2c_driver结构对应一套具体的驱动方法,例如:probe、remove、suspend等。i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动根据硬件具体情况填充。I2C 设备驱动具体实现放在在/drivers/i2c目录下chips文件夹。
关键数据结构
在i2c.h头文件中定义了i2c_adapter、i2c_algorithm、i2c_driver和i2c_client 4个比较关键的数据结构。
1)i2c_algorithm对应一套通信方法。
用来实现具体的收发算法,通过其中的收发函数会调用具体的硬件收发操作。关键函数 master_xfer() 用于产生 I2C 访问周期需要的信号,以 i2c_msg (即 I2C 消息)为单位。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
//i2c传输函数指针
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data * data); //smbus传输函数指针
u32 (*functionality) (struct i2c_adapter *); //返回适配器支持功能
};
struct i2c_msg {
__u16 addr; /* 设备地址 */
__u16 flags; /* 标志 */
__u16 len; /* 消息长度 */
__u8 *buf; /* 消息数据 */
};
2)i2c_adapter
用来定义总线上的每一个物理适配器,i2c_algorithm 对应一套通信方法,每一个adapter都需要i2c_algorithm中提供的通信函数来控制适配器的访问周期,缺少 i2c_algorithm 的 i2c_adapter 什么也做不了,因此在i2c_adapter中包含i2c_algorithm指针。i2c_algorithm的关键函数master_xfer用于产生I2C访问信号,以i2c_msg为单位。
struct i2c_adapter {
struct module *owner; //所属模块
unsigned int id; //algorithm类型,定义在i2c-id.h以I2C_ALGO_开始
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo;void *algo_data; //algorithm数据
int (*client_register)(struct i2c_client *); //client注册时调用
int (*client_unregister)(struct i2c_client *);
u8 level; /* nesting level for lockdep */*
struct mutex bus_lock;
struct mutex clist_lock;
int timeout; /* in jiffies */*
int retries;
struct device dev; /* 适配器设备 */ *
int nr;
struct list_head clients; /* DEPRECATED */*
char name[48];
struct completion dev_released;
};
struct i2c_msg {
__u16 addr; /* 从机地址 */*
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */*
#define I2C_M_RD 0x0001 /* read data, from slave to master */*
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */*
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */*
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */*
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */*
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */*
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
3) i2c_driver结构体
i2c_driver 对应一套驱动方法。i2c_driver 与 i2c_client 发生关联的时刻在 i2c_driver 的 attach_adapter() 函数被运行时。attach_adapter() 会探测物理设备,当确定一个 client 存在时,把该 client 使用的 i2c_client 数据结构的 adapter 指针指向对应的 i2c_adapter。
driver 指针指向该 i2c_driver ,并会调用 i2c_adapter 的 client_register() 函数。相反的过程发生在 i2c_driver 的 detach_client() 函数被调用的时候。
struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter
int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter
int (*detach_client)(struct i2c_client *) __deprecated; //脱离i2c_ client
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
//类似ioctl
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
/* Device detection callback for automatic device creation */
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
- (1) 设备与驱动程序匹配之后,会调用该probe接口完成设备的初始化和注册。
- (2) 卸载设备驱动时,会调用该接口完成设备注销和相关资源的释放。
- (3) Linux设备驱动抽象结构。
- (4) i2c设备id表,驱动程序中根据具体的设备ID来区分不同的设备。以此来达到兼容同种类型,不同型号的设备。
struct device_driver结构
struct device_driver {
const char *name;
const struct of_device_id *of_match_table;
};
- (1) 设备驱动名称,Linux内核未支持DeviceTree之前,设备和驱动程序需要根据name进行匹配。
- (2) 设备和驱动匹配类型表,设备驱动程序需要定义其支持的设备类型,并初始化该ofmatchtable。
4)i2c_client结构体
i2c_client 对应于真实的物理设备,每个 I2C 设备都需要一个 i2c_client 来描述。i2c_client 一般被包含在 i2c 字符设备的私有信息结构体中。
i2c_driver 与 i2c_client 发生关联的时刻在 i2c_driver 的 attach_adapter() 函数被运行时。attach_adapter() 会探测物理设备,当确定一个 client 存在时,把该 client 使用的 i2c_client 数据结构的 adapter 指针指向对应的 i2c_adapter 。
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 低7位为芯片地址 */
char name[I2C_NAME_SIZE]; //设备名
struct i2c_adapter *adapter; /*依附i2c_adapter */
struct i2c_driver *driver; /*依附i2c_ driver */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head list; /* DEPRECATED */
struct list_head detected;
struct completion released;
};
I2C 驱动核心API
// 增加/删除i2c_adapter
Int i2c_add_adapter(struct i2c_adapter *adap);
Int i2c_del_adapter(struct i2c_adapter *adap);
// 增加/删除i2c_driver
Int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
Int i2c_del_driver(struct i2c_driver *driver);
// i2c_client依附和脱离
Int i2c_attach_client(struct i2c_client *client);
Int i2c_detach_client(struct i2c_client *client);
// i2c传送\发送\接收
Int i2c_transfer(struct i2c_adaper *adap,struct i2c_msg *msgs,int num);
Int i2c_master_send(struct i2c_client *client,const char *buf,int count);
Int i2c_master_recv(struct i2c_client *client,const char *buf,int count);
probe
设备与驱动程序匹配之后,会调用该probe接口完成设备的初始化和注册。
- 设备初始化
具体到每个I2C设备芯片,一般都会有一些参数,I2C设备驱动程序会将这些参数封装成结构,然后,在设备初始化阶段完成这些参数的初始化设置。对于设备的初始化配置,一般来源于设备的DTS配置。 - 设备注册
每个I2C设备最终都会绑定到一种具体的Linux设备上,比如,RTC设备,EEROM设备,IIO设备等。设备注册完成的任务就是将该I2C设备通过具体的设备注册接口注册到系统中。举个例子,比如编写的这个I2C驱动,用于驱动一个RTC设备,那就需要调用devm_rtc_device_register接口进行设备注册。
I2C驱动的编写
(1)提供 I2C 适配器的硬件驱动,探测、初始化 I2C 适配器(如申请 I2C 的 I/O 地址和中断号)、驱动 CPU 控制的 I2C 适配器从硬件上产生各种信号以及处理 I2C 中断等。
(2)提供 I2C 适配器的 algorithm ,用具体适配器的 xxx_xfer() 函数填充 i2c_algorithm 的 master_xfer 指针,并把 i2c_algorithm 指针赋值给 i2c_adapter 的 指针。
(3)实现 I2C 设备驱动与 i2c_driver 接口,用具体设备 yyy 的 yyy_attach_adapter() 函数指针、 yyy_detach_client() 函数指针和 yyy_command() 函数指针的赋值给 i2c_driver 的 attach_adapter 、 detach_adapter 和 detach_client 指针。
(4)实现 I2C 设备驱动的文件操作接口,即实现具体设备 yyy 的 yyy_read() 、 yyy_write() 和 yyy_ioctl() 函数等。
上述工作中 1 、 2 属于 I2C 总线驱动, 3 、 4 属于 I2C 设备驱动。
一个完整的I2C设备驱动主要包括如下几个部分:
- 定义i2c_driver数据结构
- 注册i2c_driver
- 实现设备访问
DTS配置
设备驱动程序编写完成之后,具体设备需要在DTS中定义其挂接的适配器,并配置设备的通信地址等信息,下面就是一个典型的I2C设备配置信息。
&i2c1 { clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
rx8010:rtc@32 {
compatible = "epson,rx8010";
status = "okay";
reg = <0x32>;
};
};
- rx8010设备挂接在i2c1适配器下。
- rx8010的通信地址为0x32。
- rx8010的驱动程序兼容字段为“epson,rx8010”,对应到上面所讲的ofmatchtable中的设备兼容性。
- clock-frequency表示I2C通信时钟为100KHz
I2C应用开发
I2C设备文件
每个I2C适配器在/dev目录下都有一个对应的设备文件,我们通过这个设备文件直接访问挂接在该适配器之下的设备。I2C总线的设备文件通常为/dev/i2c-n(n=0、1、2……),每个设备文件对应一组I2C总线。应用程序通过这些设备文件可以操作I2C总线上的任何从机器件。
I2C编程接口
1、打开设备
在操作I2C总线时,先调用open()函数打开I2C设备获得文件描述符。
int fd;fd = open("/dev/i2c-0", O_RDWR); if (fd < 0) { perror("open i2c-1 \n");}
2、关闭设备
当操作完成后,调用close()函数关闭设备:
close(fd);
3、配置设备
当应用程序操作I2C总线上的从机器件时,必须先调用ioctl()函数设置从机地址和从机地址的长度。
- 设置从机地址
设置从机地址是使用I2C_SLAVE命令,其定义为:
#define I2C_SLAVE 0x0703
该命令的参数为从机地址右移一位。设置从机地址为0xA0的示例代码为:
if (ioctl(GiFd, I2C_SLAVE, 0xA0>> 1)< 0) { perror("set slave address fail\n");}
注意:地址需要右移一位,是因为地址的Bit0是读写控制位,在驱动中会将从机地址命令参数左移一位,并补上读写控制位。
- 设置地址长度
设置从机地址的长度是使用I2C_TENBIT命令,其定义为:
#define I2C_TENBIT 0x0704
该命令的参数可选择为:1表示设置从机地址长度为10位;0表示设置从机地址长度为8位。设置从机地址长度为10位的示例代码为:
ioctl(fd, I2C_TENBIT, 1);
该命令是不会返回错误的。
如果不设置地址长度,则默认为8位地址。
4、发送数据
应用程序调用write()函数可以向I2C总线发送数据。
int len;char buf[] = "hello";len = write(fd, buf, sizeof(buf));if (len < 0) { printf("send data faile"); exit(-1);}
write()函数调用成功后,返回成功发送数据的长度。在write()函数调用时,数据发送过程如下:
- 主机在I2C总线发送始起信号(S),然后发送从机地址(slave addr);
- 从机成功接收到属于自己的从机地址后,返回应答信号(ACK);
- 主机接收到应答信号后,把buf缓冲区中的数据逐个在I2C总线发送;
- 从机每成功接收到一个从主机发来的数据都返回应答信号;
- 当主机的数据发送完毕后,在I2C上发送结束信号(P)。
5、接收数据应用程序调用read()函数可以在I2C总线接收数据。
char buf[10];int len;len = read(fd, buf, 10);if (len < 0){ printf("read i2c data faile"); exit(-1);}
read()函数调用时,数据接收过程如下:
- 主机在I2C总线发送始起信号(S),然后发送从机地址(slave addr);
- 从机成功接收到属于自己的从机地址后,返回应答信号(ACK);
- 主机接收到应答信号后,准备接收从机发来的数据;
- 从机把数据逐个向主机发送;
- 主机每成功接收到一个在从机发来的数据都返回应答信号;
- 当主机接收到最后一个数据时并返回应答信号,而是I2C上发送结束信号(P)。
编程范例
FM24C02A是I2C接口的EEPROM芯片。FM24C02A是2Kb(256字节)的EEPROM,分为32个页,每页8字节。
1、FM24C02A的操作
- 从机寻址
当接收到起始信号后,FM24C02A需要一个8位的从机地址来启动一次读/写操作
从机地址前4位的值固定不变,第2、3、4位的值分别由FM24C02A的A0、A1、A2引脚的输入电平决定(高电平为1,低电平为0)。从机地址的第8位为读/写启动选择位(R/W):1为启动读操作;1为启动写操作。
- 字节写
字节写操作为每次在FM24C02A内部储存器的指定地址写入1个字节的数据。主机先发送起始信号和从机地址(R/W位为0)。在接收到FM24C02A返回的应答信号后,主机发送需要写入的数据地址(1个字节),然后发送需要写入的数据。在收到FM24C02A返回的应答信号后,主机发送结束信号。
- 页写
FM24C02A支持在一次写操作中连续写入一页的数据(8个字节)。页写操作的启动方式和字节写操作类似,只是主机发送了第1个字节的数据后并不是马上停止,而是继续发送剩余的7个字节的数据。FM24C02A在每接收到主机发来的1个数据都返回1个应答信号。当主机的所有数据都发送完毕后,主机发送结束信号。每当FM24C02A接收到主机发来的1个数据时,数据地址的低三位加1,而高五位不会变化,保持存储器的页地址不变。当内部产生的数据地址达到页边界时,数据地址将会翻转,接下来的数据的写入地址将置为同一页的最小地址。所以若有超过8个字节数据写入FM24C02A,数据地址将回到最先写入的地址,先前写入的数据将被覆盖。
- 当前地址读
FM24C02A的内部数据地址计数器保留最后一次访问的地址,并自动加1。只要FM24C02A处于上电状态,这个地址在操作运行期间始终有效。在读操作中,如果存储器的最后一页的最后一个字节开始读,则读下一个字节时地址将会翻转到整个储存器的最小地址。
主机发送起始信号和从机地址(R/W位为1)后,FM24C02A返回答应信号,然后向主机发送数据。这时主机接收到数据后,并不返回答应信号,而发送结束信号。
- 自由读
自由读需要通过假的字节写操作来获得数据地址。主机首先发送起始信号、从机地址和数据地址来定位需要读取的地址。当FM24C02A返回数据地址的应答信号之后,主机马上重新发送起始信号和从机地址。这时FM24C02A返回应答信号,然后发送数据。主机接收到数据后,并不返回应答信号,而发送结束信号。
- 连续读
在自由读操作中,若主机在接收了FM24C02A发来的数据后,并不发送结束信号,而是立即返回应答信号,那么FM24C02A则自动把数据地址加1,并将新数据地址的数据发送给主机。当储存器的数据地址达到最大时,数据地址将翻转到最小地址,并且继续进行连续读操作。当主机不再返回应答信号,而是发送停止信号时,FM24C02A停止发送数据。
电路原理
Demo板上的FM24C02A是连接到I2C1总线,电路图如图所示。
在该电路图中,FM24C02A的A0、A1、A2引脚电平被拉低,所以FM24C02A的从机地址为0xA0。
示例程序
通过I2C总线在FM24C02A内部储存器的0x00 ~ 0x07地址连续写入8个字节的数据,然后在这些地址中把数据读出来,最后把写入数据和读出数据进行对比,以检验程序的正确性。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#define I2C_SLAVE 0x0703
#define I2C_TENBIT 0x0704
#define I2C_ADDR 0xA0
#define DATA_LEN 8
#define I2C_DEV_NAME "/dev/i2c-1"
int main(int arg,char*args[])
{
unsigned int ret,len;
int i,flag=0;
int fd;
char tx_buf[DATA_LEN + 1]; /* 用于储存数据地址和发送数据 */
char rx_buf[DATA_LEN]; /* 用于储存接收数据 */
char addr[1]; /* 用于储存读/写的数据地址 */
addr[0] = 0; /* 数据地址设置为0 */
fd = open(I2C_DEV_NAME, O_RDWR); /* 打开I2C总线设备 */
if(fd < 0) {
printf("open %s failed\n", I2C_DEV_NAME);
return -1;
}
ret = ioctl(fd, I2C_SLAVE, I2C_ADDR >> 1); /* 设置从机地址 */
if (ret < 0) {
printf("setenv address faile ret: %x \n", ret);
return -1;
}
/* 由于没有设置从机地址长度,所以使用默认的地址长度为8 */
tx_buf[0] = addr[0]; /* 发数据时,第一个发送是数据地址 */
for (i = 1; i < DATA_LEN; i++) /* 初始化要写入的数据:0、1……7 */
tx_buf[i] = i;
len = write(fd, tx_buf, DATA_LEN + 1); /* 把数据写入到FM24C02A, */
if (len < 0) {
printf("write data faile \n");
return -1;
}
usleep(1000*100); /* 需要延迟一段时间才能完成写入EEPROM */
len = write(fd, addr, 1); /* 设置数据地址 */
if (len < 0) {
printf("write data addr faile \n");
return -1;
}
len = read(fd, rx_buf, DATA_LEN); /* 在设置的数据地址连续读入数据 */
if (len < 0) {
printf("read data faile \n");
return -1;
}
printf("read from eeprom:");
for(i = 0; i < DATA_LEN - 1; i++) { /* 对比写入数据和读取的数据 */
printf(" %x", rx_buf[i]);
if (rx_buf[i] != tx_buf[i+1]) flag = 1;
}
printf("\n");
if (!flag) { /* 如果写入/读取数据一致,打印测试成功 */
printf("eeprom write and read test sussecced!\r\n");
} else { /* 如果写入/读取数据不一致,打印测试失败 */
printf("eeprom write and read test failed!\r\n");
}
return 0;
}
注意:
有时使用ioctl函数进行读写,而非read(),write();
先写一次地址,然后再开始读数据,即分为两次消息,这个时候read(),write()函数就不能正常读写了,因为先write()地址之后总线上会有stop,之后read(),就与中间没有stop不符了,所以必须利用ioctl函数来发送两条消息,这样中间就没有stop了,发送完这两条消息才有stop。
#include<stdio.h>
#include<linux/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ioctl.h>
#include<errno.h>
#include<assert.h>
#include<string.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
int main(int argc, char** argv)
{
struct i2c_rdwr_ioctl_data work_queue;
unsigned int slave_address,reg_address,dat;
int i,ret;
unsigned char val;
unsigned int fd;
if(argc != 3)
{
printf("usage:./eeprom_ioctl address data\n");
return 0;
}
fd=open("/dev/i2c/0",O_RDWR);
if(!fd)
{
printf("error on opening the device file\n");
exit(1);
}
ioctl(fd,I2C_TIMEOUT,2);//超时时间
ioctl(fd,I2C_RETRIES,1);//重复次数
slave_address = 0x50;
reg_address = (argv[1][2]-48)<<4 | (argv[1][3]-48);
dat = (argv[2][2]-48)<<4 | (argv[2][3]-48);
work_queue.nmsgs = 2; //nmsgs决定了有多少start信号
work_queue.msgs = (struct i2c_msg *)malloc(work_queue.nmsgs * sizeof(work_queue.msgs));
if(!work_queue.msgs)
{
printf("memory alloc failed");
close(fd);
exit(1);
}
//往i2c里面写数据
printf("began to write:\n");
work_queue.nmsgs = 1;
(work_queue.msgs[0]).len = 2;//buf的长度
(work_queue.msgs[0]).flags = 0;//write
(work_queue.msgs[0]).addr = slave_address;//设备地址
(work_queue.msgs[0]).buf = (unsigned char *)malloc(2);
(work_queue.msgs[0]).buf[0] = reg_address;//写的地址
(work_queue.msgs[0]).buf[1] = dat;//要写的数据
ret = ioctl(fd, I2C_RDWR, (unsigned long)&work_queue);
if(ret < 0)
printf("error during I2C_RDWR ioctl with error code %d\n", ret);
//从i2c里面读出数据
printf("\nbegan to read:\n");
work_queue.nmsgs = 2;
//先设定一下地址
(work_queue.msgs[0]).len = 1;
(work_queue.msgs[0]).flags = 0;//write
(work_queue.msgs[0]).addr = slave_address;
(work_queue.msgs[0]).buf[0] = reg_address;
(work_queue.msgs[1]).len = 1;
(work_queue.msgs[1]).flags = I2C_M_RD;
(work_queue.msgs[1]).addr = slave_address;
(work_queue.msgs[1]).buf = (unsigned char *)malloc(1);
(work_queue.msgs[1]).buf[0] = 0;//初始化读缓冲
ret = ioctl(fd, I2C_RDWR, (unsigned long)&work_queue);
if(ret < 0)
printf("error during I2C_RDWR ioctl with error code %d\n", ret);
close(fd);
return 0;
}