IIC
Inter-Integrated Circuit
I2C(IIC)属于两线式串行总线,由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线,属于一主多从(一个主设备(Master),多个从设备(Slave))的总线结构,总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备。
与SPI的单主设备不同,IIC 是多主设备的总线,IIC没有物理的芯片选择信号线,没有仲裁逻辑电路,只使用两条信号线—— ‘serial data’ (SDA) 和 ‘serial clock’ (SCL)。IIC协议规定:
- 第一,每一支IIC设备都有一个唯一的七位设备地址;
- 第二,数据帧大小为8位的字节;
- 第三,数据(帧)中的某些数据位用于控制通信的开始、停止、方向(读写)和应答机制。
IIC 数据传输速率有标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。
物理实现上,IIC 总线由两根信号线和一根地线组成,两根信号线都是双向传输的。
一、主设备与从设备的一般通信过程
1. 主设备给从设备发送/写入数据:
1. 主设备发送起始(START)信号
2. 主设备发送设备地址到从设备
3. 等待从设备响应(ACK)
4. 主设备发送数据到从设备,一般发送的每个字节数据后会跟着等待接收来自从设备的响应(ACK)
5. 数据发送完毕,主设备发送停止(STOP)信号终止传输
2. 主设备从从设备接收/读取数据
1. 设备发送起始(START)信号
2. 主设备发送设备地址到从设备
3. 等待从设备响应(ACK)
4. 主设备接收来自从设备的数据,一般接收的每个字节数据后会跟着向从设备发送一个响应(ACK)
5. 一般接收到最后一个数据后会发送一个无效响应(NACK),然后主设备发送停止(STOP)信号终止传输
注:具体通信过程需视具体时序图而定
二、通信协议
0)IIC总线必须上拉电阻,默认电平都是高电平。
1)从设备地址,高位先发送,共7位,补充一位读写位,读高电平,写低电平。默认电平是读电平(高电平),防止误写。
2)正确响应是低电平,错误响应或无响应是高电平,防止误判(或误响应)。
3)起始信号和结束信号都由主设备发起。起始信号:SCL保持高电平,SDA由高变到低。结束信号:SCL保持高电平,SDA由低变高。
4)时钟信号SCL为高电平期间,数据线SDA上的数据必须保持稳定。数据位:SCL低电平变化。控制位(起始和停止):SCL高电平变化。
三、10位设备地址
任何IIC设备都有一个7位地址,理论上,现实中只能有127种不同的IIC设备。实际上,已有IIC的设备种类远远多于这个限制,在一条总线上出现相同的地址的IIC设备的概率相当高。为了突破这个限制,很多设备使用了双重地址——7位地址加引脚地址(external configuration pins)。IIC 标准也预知了这种限制,提出10位的地址方案。
10位的地址方案对 IIC协议的影响有两点:
- 第一,地址帧为两个字节长,原来的是一个字节;
- 第二,第一个字节前五位最高有效位用作10位地址标识,约定是“11110”。
除了10位地址标识,标准还预留了一些地址码用作其它用途,如下表:
四、应用编程
linux IIC编程有两种方式:1)文件操作,read/write;2)使用构造i2c_msg结构体的方式并利用ioctl的方式读写。
如下从网上拷贝的示例为ioctl方式:
#include <stdio.h> #include <linux/types.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.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 e2prom_data; unsigned int fd; unsigned int slave_address, reg_address,value; //slave_address为eeprom的地址,reg_address为eeprom中存储单元的地址,范围0x0--0xFFFFFFFF,value为你要写进eeprom的值 int ret; if (argc < 5){ printf("Usage:n%s /dev/i2c/x start_addr reg_addr valuen",argv[0]); return 0; } fd = open(argv[1], O_RDWR); if (!fd){ printf("Error on opening the device filen"); return 0; } sscanf(argv[2], "%x", &slave_address); sscanf(argv[3], "%x", ®_address); sscanf(argv[4], "%x", &value); e2prom_data.nmsgs = 2;//因为都时序要两次,所以设为2 e2prom_data.msgs = (struct i2c_msg *)malloc(e2prom_data.nmsgs * sizeof(struct i2c_msg)); if (!e2prom_data.msgs){ printf("Memory alloc errorn"); close(fd); return 0; } ioctl(fd, I2C_TIMEOUT, 2);//设置超时时间 ioctl(fd, I2C_RETRIES, 1);//设置重发次数 e2prom_data.nmsgs = 1; e2prom_data.msgs[0].len = 2;//信息长度为2,看写时序,eeprom的地址不算的,因为付给了addr,而len是指buf中的值的个数 e2prom_data.msgs[0].addr = slave_address; e2prom_data.msgs[0].flags = 0;//写命令 e2prom_data.msgs[0].buf = (unsigned char*)malloc(2); e2prom_data.msgs[0].buf[0] = reg_address;//信息值1 eeprom中存储单元的地址,即你要往哪写 e2prom_data.msgs[0].buf[1] = value;//信息值2,即你要写什么 ret = ioctl (fd, I2C_RDWR, (unsigned long)&e2prom_data);//好了 ,写进去吧 if (ret < 0){ printf ("ioctl write errorn"); } printf("you have write x into e2prom at x addressn",value,reg_address); sleep(1); e2prom_data.nmsgs = 2;//读时序要两次过程,要发两次I2C消息 e2prom_data.msgs[0].len = 1;//信息长度为1,第一次只写要读的eeprom中存储单元的地址 e2prom_data.msgs[0].addr = slave_address; e2prom_data.msgs[0].flags = 0;//写命令,看读时序理解 e2prom_data.msgs[0].buf[0] = reg_address;//信息值 e2prom_data.msgs[1].len = 1; e2prom_data.msgs[1].addr = slave_address; e2prom_data.msgs[1].flags = I2C_M_RD;//读命令 e2prom_data.msgs[1].buf = (unsigned char*)malloc(1); e2prom_data.msgs[1].buf[0] = 0;//先清空要读的缓冲区 ret = ioctl (fd, I2C_RDWR, (unsigned long)&e2prom_data);//好了,读吧 if (ret < 0){ printf ("ioctl read errorn"); } printf("read x from e2prom address xn",e2prom_data.msgs[1].buf[0], reg_address); close(fd); return 0; }
参考:
3. i2c的时钟延展问题--在模拟i2c主:在主设置SCL为高后,要超时判断SCL是否为高,再发后面的时序