嵌入式接口协议(4)—IIC通讯协议(主设备)
1. IIC协议
可挂多个设备,每个IIC从设备里都有个固化的地址,只有两条线上传输的值对应IIC从设备的地址时,从设备才作出响应。
1.1 开始信号
SCL时钟保持高电平,然后SDA数据信号“由高变低”表示一个开始信号;同时从设备检测到这个开始信号就知道主控设备要发送数据了。
1.2 停止信号
SCL时钟保持高电平,然后SDA数据信号“由低变高”表示一个停止信号;同时从设备检测到这个停止信号就知道主控设备已经结束数据传输。
1.3 数据传输
SDA上传输的数据必须在SCL为高电平期间保持稳定,因为外接IIC设备在SCL为高电平期间采集数据;也因此,SDA上的数据只能在SCL为低电平期间翻转变化。
1.4 响应信号(ACK)
主设备把数据发给IIC从设备,需要IIC从设备回应一个信号给主设备,以来判断IIC从设备数据是否已经收到数据。具体过程如下:
(1)主设备发完8bit数据后就不再驱动总线了(SDA引脚设置为输入),由于SDA和SCL硬件设计时都有上拉电阻,所以此时SDA变成高电平。
(2)如果IIC从设备能接收到信号的话,接着需要在第9个时钟周期主动把SDA拉低,此时主设备检测到SDA变低,就知道从设备已经收到数据。
2. IIC通讯流程
IIC总线用7bit表示从地址,因此最多挂载的从设备数量为2^7即:128个。
(1)先发送起始位;
(2)发一个8bit数据,前7bit表示从设备地址,第8bit表示“读”或“写”标识。其中,“0”对应write,即:主控设备往IIC从设备发;“1”对应read,即:从设备往主控设备发。
(3)第9个时钟周期检测从设备回复的响应信号。
3. 以 AT24Cxx 为例
3.1 读取数据时序
(1)首先发出一个start信号,和从设备地址,R/W(1,0),回应ACK表示有这个从设备存在。
(2)主控把需要访问的IIC从设备的待读取的8bit存储地址写入,ACK回应。
(3)另一个start信号+从设备地址,最低位是高电平表示读数据,回应ACK表示有这个从设备存在。
(4)在读数据的时候,每发出一个时钟,主设备会读取SDA上的数据并存起来,那么发出8个时钟后主设备就能得到8位的数据。若想连续读就不断回应ACK信号否则就发出停止信号。
3.2 写入数据时序
(1)start信号,某个设备地址,R/W 为“写”标识;对方收到8bit地址后回应ACK;
(2)接着主设备发送需要写入的地址,从设备收到8bit地址后回应ACK;
(3)接着主控将8bit数据发给从设备,对方收到8bit数据后回应ACK;
(4)通讯完成后,主设备发送停止信号。
4. 逻辑分析仪抓到数据
5. GPIO模拟IIC协议
#define SDA 254 //定义SDA所对应的GPIO接口编号 #define SCL 255 //定义SCL所对应的GPIO接口编号 #define OUTP 1 //表示GPIO接口方向为输出 #define INP 0 //表示GPIO接口方向为输入 /* I2C起始条件 */ int i2c_start() { set_gpio_direction(SDA, OUTP); //设置SDA方向为输出 set_gpio_direction (SCL, OUTP); //设置SCL方向为输出 set_gpio_value(SDA, 1); //设置SDA为高电平 set_gpio_value(SCL, 1); //设置SCL为高电平 delay(); //延时 set_gpio_value(SDA, 0); //SCL为高电平时,SDA由高变低 delay(); } /* I2C终止条件 */ void i2c_stop() { set_gpio_value(SCL, 1); set_gpio_direction(SDA, OUTP); set_gpio_value(SDA, 0); delay(); set_gpio_value(SDA, 1); //SCL高电平时,SDA由低变高 } /* I2C读取ACK信号(写数据时使用) 返回值 :0表示ACK信号有效;非0表示ACK信号无效 */ unsigned char i2c_read_ack() { unsigned char r; set_gpio_direction(SDA, INP); //设置SDA方向为输入 set_gpio_value(SCL,0); // SCL变低 r = get_gpio_value(SDA); //读取ACK信号 delay(); set_gpio_value(SCL,1); // SCL变高 delay(); return r; } /* I2C发出ACK信号(读数据时使用) */ int i2c_send_ack() { set_gpio_direction(SDA, OUTP); //设置SDA方向为输出 set_gpio_value(SCL,0); // SCL变低 set_gpio_value(SDA, 0); //发出ACK信号 delay(); set_gpio_value(SCL,1); // SCL变高 delay(); } /* I2C字节写 */ void i2c_write_byte(unsigned char b) { int i; set_gpio_direction(SDA, OUTP); //设置SDA方向为输出 for (i=7; i>=0; i--) { set_gpio_value(SCL, 0); // SCL变低 delay(); set_gpio_value(SDA, b & (1<<i)); //从高位到低位依次准备数据进行发送 set_gpio_value(SCL, 1); // SCL变高 delay(); } 2c_read_ack(); //检查目标设备的ACK信号 } /* I2C字节读 */ unsigned char i2c_read_byte() { int i; unsigned char r = 0; set_gpio_direction(SDA, INP); //设置SDA方向为输入 for (i=7; i>=0; i--) { set_gpio_value(SCL, 0); // SCL变低 delay(); r = (r <<1) | get_gpio_value(SDA); //从高位到低位依次准备数据进行读取 set_gpio_value(SCL, 1); // SCL变高 delay(); } i2c_send_ack(); //向目标设备发送ACK信号 return r; } /* I2C读操作 addr:目标设备地址 buf:读缓冲区 len:读入字节的长度 */ void i2c_read(unsigned char addr, unsigned char* buf, int len) { int i; unsigned char t; i2c_start(); //起始条件,开始数据通信 t = (addr << 1) | 1; //低位为1,表示读数据 i2c_write_byte(t); for (i=0; i<len; i++) buf[i] = i2c_read_byte(); i2c_stop(); //终止条件,结束数据通信 } /* I2C写操作 addr:目标设备地址 buf:写缓冲区 len:写入字节的长度 */ void i2c_write (unsigned char addr, unsigned char* buf, int len) { int i; unsigned char t; i2c_start(); //起始条件,开始数据通信 t = (addr << 1) | 0; //低位为0,表示写数据 i2c_write_byte(t); for (i=0; i<len; i++) i2c_write_byte(buf[i]); i2c_stop(); //终止条件,结束数据通信 }