I2C协议的描述请网上搜索,下面将结合时序图+源代码程序一起讲解关于I2C协议中重要的几点。
1.开始和停止条件
SCL时钟电平为高:
SDA数据线由高 -> 低 为总线开始条件;
SDA数据线由低 -> 高 为总线结束条件;
(注意:开始之后将SCL变为低电平,防止误操作SDA使其通信停止,见源代码)
时序图:
源代码程序:
2.数据位传输
SCL时钟电平为低, 可以改换SDA数据线的电平,在SCL上升沿的过程将SDA数据发送出去。
(切记:请先将SCL变为低电平,再改变SDA电平状态。 主要用于I2C读写Byte函数,这两个函数网上很多人写的不规范,引用需注意,在下面我会举例说明)
时序图:
发送一位“高”数据流程:
SCL_LOW时钟低 -> SDA_HIGH数据 -> SCL_HIGH时钟高
3.应答位信息
I2C是以字节(8位)的方式进行传输,总线上每传输完1字节之后会有一个应答信号,主器件(主机)需要产生对应的一个额外时钟。
应答位产生及接收:
1.在(主机)写数据的时候是从机应答(给主机),主机检测;
2.在(主机)读数据的时候是主机应答(给从机),从机检测;
(这里可以借助I2C读写函数一起理解)
1.时序图(主机写,从机应答,主机读取应答):
2.时序图(主机读,主机产生应答):
4.I2C写一字节
这里说的I2C写,是主机往从机接入1Byte的数据;
“写”要求按照上面的“数据为传输”来操作:在SCL时钟为低电平时准备好,待SCL为高电平时发送出去。
写完一字节(8位)之后,读取从机的应答位:
若为0,表示从机应答,可以继续下一步操作;
若为1,表示从机非应答,不能进行下一步操作。
注意:
I2C写一字节不是EEPROM写一字节(需要区分开来)。
“简洁版”没有对应答信号做出检测判断,需要检测应答信号,可参考“综合版”
写一字节时序(前面8位数据 + 最后1为应答):
源代码程序:
I2C写数据(网上常见几种不规范写法 - 或许整个I2C驱动能通信成功,但各个函数之间依赖关系很强,不便理解,也不是标准的函数):
1.首先将SCL置高:
void I2C_WriteByte(uint8_t Data)
{
uint8_t cnt;
for(cnt=0; cnt<8; cnt++)
{
I2C_SCL_HIGH;
if(Data & 0x80)
I2C_SDA_HIGH;
else
I2C_SDA_LOW;
Data <<= 1;
I2C_SCL_LOW;
}
I2C_GetAck();
}
这种程序的写法有一个致命的地方(有可能停止,或重新开始I2C通信):
首先将SCL置高:
A.若之前SDA是低电平,第一位写入高电平,将停止I2C通信。
B.若之前SDA是高电平,第一位写入低电平,将重新开始I2C通信。
2.写完8位数据之后,未将SCL置低(也就是SCL保持高电平状态):
由于写完8位数据之后,将要读取应答信号,也就是要SDA将从输出状态变为输入状态。
这个时候SCL为高,如果SDA最后一位是低且SDA是开漏模式,需要将SDA释放,也就是要将SDA置位高,那么,这个时候就进行了一个停止操作。
3.时序混乱:
void I2C_WriteByte(uint8_t Data)
{
uint8_t cnt;
I2C_SCL_HIGH;
for(cnt=0; cnt<8; cnt++)
{
if(Data & 0x80)
I2C_SDA_HIGH;
else
I2C_SDA_LOW;
Data <<= 1;
I2C_SCL_LOW;
I2C_SCL_HIGH;
}
I2C_GetAck();
}
多种问题的例子,有可能产生以下问题:
A.有可能多写1位数据;
B.有可能停止I2C通信;
C.有可能重新开始I2C通信。
5.I2C读一字节
I2C的读一字节函数,其实和“写一字节”类似,只是数据传输方向相反,应答的方向也是相反。
读完一字节(8位)之后,由主机产生应答(或非应答)位:
若产生应答,表示可以继续读下一字节操作(从设备地址指向下一字节);
若产生非应答,表示不可以继续读下一字节操作;
网上I2C读数据程序和“写数据”类似,存在很多不标准的版本,参考时请注意。
读一字节时序(主机读取前面8位数据 + 主机产生1为非应答<连续读,主机产生应答位>):
读字节源代码程序: