【车载开发系列】IIC总线协议时序图
【车载开发系列】IIC总线协议时序图
- 【车载开发系列】IIC总线协议时序图
- 一、前言
- 二、IIC硬件软件实现
- 1)使用I2C控制器实现
- 2)使用GPIO通过软件模拟实现
- 三、I2C协议标准代码
- 1)起始信号
- 2)停止信号
- 3)发送一个字节
- 4)读取一个字节
- 5)ACK应答
- 6)NACK应答
- 7)ACK应答信号
- 二、向IIC总线写数据
- 三、向IIC总线读数据
- 四、总结
一、前言
- IIC协议是一种具有自动寻址、高低速设备同步和仲裁等功能的高性能串行总线,它是一个真正的多主机总线,支持一对多(一主多从)、多对多传输(多主多从)。
- 它是各种总线协议中使用信号线最少的,只需要两根线便可以实现功能。
- 连在IIC总线上的每个器件都有一个唯一的地址识别。其中高四位A7-A4是从机器件的固定编址,出厂时就已给定;A3-A1是从机器件的引脚地址,通过接地接电源来形成地址。
术语 | 描述 |
---|---|
发送器 | 发送数据到总线的器件 |
接收器 | 从总线接收数据的器件 |
主机 | 初始化总线的数据,传输并产生允许传输的时钟信号和起始终止发送的器件 |
从机 | 被主机寻址的器件 |
多主机 | 同时有多于一个主机尝试控制总线,但不破坏报文 |
仲裁 | 是一个在有多个主机同时尝试控制总线,但只允许其中一个控制总线并使报文不被破坏 |
同步 | 两个或多个器件同步时钟信号的过程 |
二、IIC硬件软件实现
- I2C有两种实现方式:一种是GPIO软件模拟,另一种是直接使用I2C硬件。
- 时钟线完全由主机控制。而从机只有对SDA的(短暂)控制权。通常使用I2C通信,SCL和SDA线上都会接一个上拉电阻,要么是在外设的内部,要么是在外面可以直接通过外设的硬件接线图看到。
- 硬件连接图如下
1)使用I2C控制器实现
就是使用芯片上的I2C外设,也就是硬件I2C,它有相应的I2C驱动电路,有专用的IIC引脚,效率更高,写代码会相对简单,只要调用I2C的控制函数即可,不需要用代码去控制SCL、SDA的各种高低电平变化来实现I2C协议,只需要将I2C协议中的可变部分(如:从设备地址、传输数据等等)通过函数传参给控制器,控制器自动按照I2C协议实现传输,但是如果出现问题,就只能通过示波器看波形找问题。
2)使用GPIO通过软件模拟实现
软件模拟I2C比较重要,因为软件模拟的整个流程比较清晰,哪里出来bug,很快能找到问题,模拟一遍会对I2C通信协议更加熟悉。
如果芯片上没有IIC控制器,或者控制接口不够用了,通过使用任意IO口去模拟实现IIC通信协议,手动写代码去控制IO口的电平变化,模拟IIC协议的时序,实现IIC的信号和数据传输。
三、I2C协议标准代码
1)起始信号
起始信号:当 SCL 线是高电平时,SDA线从高电平向低电平切换。
void I2C_Start(void){
// 总线空闲, SCL和SDA输出高
I2C_SDA_High(); //SDA=1
I2C_SCL_High(); //SCL=1
I2C_Delay();
I2C_SDA_Low(); // SDA由高变低
I2C_Delay();
I2C_SCL_Low(); // 拉低SCL开始传输数据
I2C_Delay();
}
2)停止信号
终止信号:SCL为高电平时,SDA由低向高跳变。
void I2C_Stop(void){
I2C_SDA_Low(); //SDA=0 把数据线设置为输出模式
I2C_SCL_High(); // 拉高时钟线
I2C_Delay();
I2C_SDA_High(); // SDA由低变高
I2C_Delay();
}
3)发送一个字节
CPU向I2C总线设备发送一个字节(8bit)数据
uint8 I2C_SendByte(uint8_t Byte){
uint8_t i;
/* 先发送高位字节 */
for(i = 0 ; i < 8 ; i++){
if(Byte & 0x80){
I2C_SDA_High();
}
else{
I2C_SDA_Low();
}
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
if(i == 7){
I2C_SDA_High();/* 释放SDA总线 */
}
Byte <<= 1;/* 左移一位 */
I2C_Delay();
}
}
4)读取一个字节
CPU从I2C总线设备上读取一个字节(8bit数据)
uint8_t I2C_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 先读取最高位即bit7 */
value = 0;
for(i = 0 ; i < 8 ; i++)
{
value <<= 1;
I2C_SCL_High();
I2C_Delay();
if(I2C_SDA_READ())
{
value++;
}
I2C_SCL_Low();
I2C_Delay();
}
return value;
}
5)ACK应答
CPU产生一个ACK信号
当SDA是低电平为有效应答(ACK),表示对方接收成功;
void I2C_Ack(void)
{
I2C_SDA_Low(); //SDA=0 当SDA是低电平为有效应答(ACK),表示对方接收成功;
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low(); // 拉低SCL开始传输数据
I2C_Delay();
I2C_SDA_High(); //SCL_Low之后,SDA=0->1
}
6)NACK应答
CPU产生一个非ACK信号
当SDA是高电平为无效应答(NACK),表示对方没有接收成功。
void I2C_NoAck(void)
{
I2C_SDA_High(); //SDA=1 当SDA是高电平为无效应答(NACK),表示对方没有接收成功。
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();// 拉低SCL开始传输数据
I2C_Delay();
}
7)ACK应答信号
CPU产生一个时钟,并读取器件的ACK应答信号
发送数据需要等待接收方的应答:
// 等待ACK 1-无效 0-有效
uint8_t I2C_WaitToAck(void)
{
uint8_t redata;
I2C_SDA_High();// 拉高数据线(设置为输入)
I2C_Delay();
I2C_SCL_High();// 拉高时钟线
I2C_Delay();
if(I2C_SDA_READ())
{
redata = 1; //ACK应答无效
}
else
{
redata = 0; //ACK应答有效
}
I2C_SCL_Low(); // 拉低SCL开始传输数据
I2C_Delay();
return redata;
}
二、向IIC总线写数据
首先我们先来看一下写数据的时序图,如下图所示:
将上图中的写数据时序图进行分解,经分解后如下图所示:
- 第一步,发送一个起始信号。
- 第二步,发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写,这里是写。
- 第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
- 第四步,发送寄存器地址,8bit数据。
- 第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
- 第六步,发送一个数据,8bit数据。
- 第七步,产生一个ACK应答信号,此应答信号为从机器件产生的应答信号。
- 第八步,发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
- 第九步,既可以发送一个应答信号,也可以发送一个无应答信号,均有从机器件产生。
- 第十步,发送一个停止信号。
uint8_t I2C_WriteBytes(void)
{
I2C_Start(); //1
I2C_SendByte(Slaver_Addr | 0); //2
I2C_WaitToAck(); //3
I2C_SendByte(Reg_Addr); //4
I2C_WaitToAck(); //5
I2C_SendByte(data); //6
I2C_WaitToAck(); //7
I2C_SendByte(crc); //8
I2C_WaitToAck(); //9
I2C_Stop(); //10
}
三、向IIC总线读数据
读数据的时序图如下图所示:
读数据的时序图经分解后如下图所示:
- 第一步,发送一个起始信号。
- 第二步,发送7bit从机地址,此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
- 第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
- 第四步,发送寄存器地址。
- 第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
- 第六步,再次发送一个起始信号。
- 第七步,发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
- 第八步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
- 第九步,读取一个字节(8bit)的数据。
- 第十步,产生一个ACK应答信号,此应答信号为CPU产生。
- 第十一步,读取一个CRC校验码。
- 第十二步,产生一个NACK信号。此无应答信号由CPU产生。
- 第十三步,产生一个停止信号。
uint8_t I2C_ReadBytes(void)
{
uint8_t data;
uint8_t crc;
I2C_Start(); //1
I2C_SendByte(Slaver_Addr | 0); //2
I2C_WaitToAck(); //3
I2C_SendByte(Reg_Addr); //4
I2C_WaitToAck(); //5
I2C_Start(); //6
I2C_SendByte(Slaver_Addr | 1); //7 1-读
I2C_WaitToAck(); //8
data=I2C_ReadByte(); //9
I2C_Ack(); //10
crc=I2C_ReadByte(); //11
I2C_NoAck(); //12
I2C_Stop(); //13
}
四、总结
-
每次向SDA发送一位数据,都需要在SCL高电平时保持,所以SDA上所传输每一位数据都会占用一个时钟周期。
-
主机接收到从机的应答信号之后,
- 如果第8位读写位是0,即主机向从机写数据,接下来继续由主机占用SDA,向从机传输数据,期间主机每传送8位数据,从机就会产生一个应答信号ACK,由主机接收。
- 如果第8位读写位是1 ,即从机向主机写数据,接下来继续由从机占用SDA,向主机传输数据,期间从机每传送8位数据,主机就会产生一个应答信号ACK,由从机接收。
-
每传输8位数据,都会产生ACK信号,除了以下三种情况外;
- 从机不能响应主机发送的从机地址
例如从机正忙而无法响应IIC总线的操作,或者这个地址没有对应的从机,那么在第9个SCL周期内SDA线就没有被拉低,即没有ACK信号。
这时,由主机发送一个停止信号终止传输,或者重新发送一个START信号开始新的传输。 - 从机无法接收更多数据
如果从机无法接收更多的数据,即主机发送的数据超过从机的数据接收能力时,从机不会发出ACK信号,这时,由主机发出一个停止信号终止传输或者重新发送一个START信号开始新的传输。 - 主机接收最后一个字节
主机接收器接收到最后一个字节后,也不会发出ACK信号。于是从机发送器释放SDA线,以允许主机发出停止信号结束传输。
- 从机不能响应主机发送的从机地址