来源-作者@WeiKo90 &:https://blog.csdn.net/WeiKo90/article/details/77512135
建议读者阅读原文,确保获得完整的信息
1.I2C是什么
①I2C总线(Inter-Integrated Circuit)是由Philips公司开发的一种简单、双向二线制同步串行总线。由SCL和SDA两根线即可连接于总线上的器件之间传送信息。
②SCL为时钟线,SDA为数据线,在时钟线SCL控制的时钟信号下,SDA进行数据的传送。
③SDA上传送的每个字节必须为8位,每个字节后跟一个响应位。数据的传输是一位位进行的,其首先传输的是最高位(MSB)。
2.I2C时序图及程序
①起始信号和终止信号
1)起始信号:SCL高电平时,SDA线由高电平向低电平的变化表示起始信号,总线就被占用。
2)终止信号:SCL高电平时,SDA线由低电平向高电平的变化表示终止信号,总线处于控线。
具体参考程序如下:
/******************************************************************************* * 函 数 名 : I2C_Start() * 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿 * 输 入 : 无 * 输 出 : 无 * 备 注 : 起始之后I2C_SDA和I2C_SCL都为0,只有SCL=0时,SDA才允许高低电平变化。 *******************************************************************************/ void I2C_Start() { I2C_SDA = 1; I2C_SCL = 1; I2C_Delay_us(5);//建立时间是I2C_SDA保持时间>4.7us I2C_SDA = 0; I2C_Delay_us(4);//保持时间是>4us I2C_SCL = 0; } /******************************************************************************* * 函 数 名 : I2C_Stop() * 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿 * 输 入 : 无 * 输 出 : 无 * 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲 *******************************************************************************/ void I2C_Stop() { I2C_SDA = 0; I2C_Delay_us(4); I2C_SCL = 1; I2C_Delay_us(4); I2C_SDA = 1; }
②I2C发送字节和读取字节
1)SCL为低电平时,SDA处于数据转换的状态,此时数据状态是不稳定的也是不确定的。
2)SCL为高电平时,SDA上传输的数据稳定,但传输数据可能是高电平,也可能是低电平。
3)发送字节时,先拉低SCL,根据要发送的字节的当前正在处理的某一位的高低电平来转换给SDA线。转换完成后,拉高SCL,进行数据传输。
4)读取字节时,先拉高SCL,判断SDA上的传输数据是高电平还是低电平并赋值给相应的变量,再拉低SCL。
具体参考程序如下:
/******************************************************************************* * 函 数 名 : I2C_SendByte(unsigned char byt) * 函数功能 : 发送一个字节 * 输 入 : byt:发送的字节 * 输 出 : 无 * 备 注 : 无 *******************************************************************************/ void I2C_SendByte(unsigned char byt) { unsigned char i; I2C_SCL = 0; for(i = 0;i < 8;i++) { if(byt&0x80) //从最高位开始判断发送的字节byt是0还是1来判断I2C_SDA是0还是1 { I2C_SDA = 1; } else { I2C_SDA = 0; } byt <<= 1; //判断完一位后,那么要左移一位,让下一位继续判断,一个字节8位,所以一共是要判断8次 delay_us(2); I2C_SCL = 1; delay_us(2); I2C_SCL = 0; } I2C_SCL = 0; } /******************************************************************************* * 函 数 名 : unsigned char IIC_Read_Byte(unsigned char ack) * 函数功能 : 读一个字节 * 输 入 : ack(0或1) * 输 出 : 读到的一个字节dat * 备 注 : 无 *******************************************************************************/ unsigned char IIC_Read_Byte(unsigned char ack) { unsigned char i,dat = 0; I2C_SCL = 0; for(i = 0;i < 8;i++ ) { IIC_SCL = 1; dat <<= 1; if(I2C_SDA) //根据I2C_SDA的高低来一位一位赋值给dat { dat |= 0x01; } delay_us(1); I2C_SCL = 0; } IIC_Ack(ack); //如果只接收1个字节的数据,则发送非应答信号1, //然后产生stop信号,告诉从机单片机停止接收数据,也就是不用再发了 //如果要接收多个字节数据,则接收完一个字节数据后要发送应答信号0, //告诉从机要继续发给单片机 return dat; }
③I2C主机等待应答和产生应答
等待应答和产生应答类似于读字节和写字节,不同的是读和写字节是一个字节8位,因此函数内部进行了8次循环,而等待应答和产生应答只需处理一位数据,所以不需要循环,其本质是差不多的。
/******************************************************************************* * 函 数 名 : I2C_Wait_Ack() * 函数功能 : 等待应答:即等待从设备把I2C_SDA拉低 * 输 入 : 无 * 输 出 : 0或1 * 备 注 : 0表示应答发送失败或非应答,1表示接收到应答 *******************************************************************************/ unsigned char I2C_Wait_Ack(void) { unsigned char acktime; I2C_SCL = 1; delay_us(1); while(I2C_SDA) //等待应答,即等待从设备把I2C_SDA拉低 { acktime++; if(acktime>200) //如果超过200us没有应答,则发送失败,或者为非应答,表示接受结束 { I2C_SCL = 0; delay_us(1); return 0; } } delay_us(1); I2C_SCL = 0; return 1; } /******************************************************************************* * 函 数 名 : IIC_Ack(unsigned char ackbit) * 函数功能 : 产生应答 * 输 入 : 0或1 * 输 出 : 无 * 备 注 : 0表示产生应答,1表示不产生应答 *******************************************************************************/ void IIC_Ack(unsigned char ackbit) { I2C_SCL = 0; I2C_SDA = ackbit; delay_us(2); I2C_SCL = 1; }
3.主从机I2C通信过程
①主机发送过程
1)主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始。
2)主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W组成。(此时 R/W=0)
3)相对应的从机收到命令字节后向主机回馈应答信号 ACK。(ACK=0)
4)主机收到从机的应答信号后开始发送第一个字节的数据。
5)从机收到数据后返回一个应答信号 ACK。
6)主机收到应答信号后再发送下一个数据字节。
7)当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信。
1)主机发送启动信号后,接着发送命令字节。(其中 R/W=1)
2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据。
3)主机收到数据后向从机反馈一个应答信号。
4)从机收到应答信号后再向主机发送下一个数据 。
5)当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ASK=1 的非应答信号后便停止发送。
6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信。
/******************************************************************************* * 函 数 名 : write_eeprom(unsigned char add,unsigned char val) * 函数功能 : 向eeprom写一个字节 * 输 入 : eeprom中某个地址和要写入的数据字节 * 输 出 : 无 * 备 注 : 无 *******************************************************************************/ void write_eeprom(unsigned char add,unsigned char val) { I2C_Start(); I2C_SendByte(0xa0); I2C_Wait_Ack(); I2C_SendByte(add); I2C_Wait_Ack(); I2C_SendByte(val); I2C_Wait_Ack(); I2C_Stop(); } /******************************************************************************* * 函 数 名 : unsigned char read_eeprom(unsigned char add) * 函数功能 : 从eeprom读一个字节 * 输 入 : eeprom中的某个地址 * 输 出 : 选中地址里面存储的数据 * 备 注 : 无 *******************************************************************************/ unsigned char read_eeprom(unsigned char add) { unsigned char da; I2C_Start(); I2C_SendByte(0xa0); I2C_Wait_Ack(); I2C_SendByte(add); I2C_Wait_Ack(); I2C_Start(); I2C_SendByte(0xa1); I2C_Wait_Ack(); da = IIC_Read_Byte(); I2C_Stop(); return da; }