浅析Linux下IIC总线的使用

发布于:2023-01-04 ⋅ 阅读:(694) ⋅ 点赞:(0)

前言:

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设备驱动主要包括如下几个部分:

  1. 定义i2c_driver数据结构
  2. 注册i2c_driver
  3. 实现设备访问

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;

}

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到