IIC协议详解,附单片机软件模拟源码

I2C协议


(一)物理层

img

1. 原理

I2C 总线,分别由SDA(串行数据线)SCL(串行时钟线)上拉电阻组成。

通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平

img

2. 总体特征

​ 连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。

​ I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这 个地址来确定与哪个器件进行通信。

​ I2C 总线上数据的传输速率在标准模式下可达 100kbit/s ,在快速模式下可达 400kbit/s ,在高速模式下可达 3.4Mbit/s ,连接到总线的接口数量只由总线电容是 400pF 的限制决定。

​ I2C 总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。


(二)协议层

1. 起始和停止条件

tMOAJI.png

I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。

起始和结束信号总是由主设备产生

img

  • 总线在空闲状态 时,SCL和SDA都保持着高电平,
  • 当SCL为高而SDA由高到低的跳变,表示产生一个起始条件
  • 当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件
  • 在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;
  • 而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。

2. 数据有效性

SDA线上的数据必须在SCL的高电平时保持稳定。

SDA线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。

tMRB0f.png

发送到 SDA 线上的每个字节必须为 8 位,每次传输可以发送的字节数量不受限制 ,每个字节后必须跟一个响应位 ,首先传输的是数据的最高位(见下图MSB )

如果从机要完成一些其他功能后(如中断)才能接收或发送下一个完整的数据字节 ,可以使时钟线 SCL 保持低电平迫使主机进入等待状态 ,当从机准备好接收下一个数据字节时释放时钟线 SCL, 数据传输继续。

tMhU1J.png

3. 响应

数据传输必须带响应,相关的响应SCL时钟脉冲由主机产生,在响应的时钟脉冲期间,发送器释放 SDA 线(输出高阻态使SDA线被上拉电阻拉高)。在响应的时钟脉冲期间,接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。 必须考虑建立和保持时间。

tMhFmt.png

4. 寻址

7位地址格式

tMLssS.png

起始条件 S 后 ,发送了一个从机地址SLAVE ADDRESS, 这个地址共有 7 位,紧接着的第 8 位是数据方向位[R/W], 0 表示写,1表示读。接下来的一个bit是应答位NACK/ACK,当这个帧中前面8bits发送完后,接收端获得SDA控制权,此时接收设备应该在第9个时钟脉冲之前回复一个ACK(将SDA拉低)以表示接收正常,如果接收设备没有将SDA拉低,则说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由master来决定如何处理(stop或repeated start condition)。

tM791S.png

10位地址格式

tMLSEj.png

10 位从机地址是由在起始条件 S 或重复起始条件 Sr 后的头两个字节组成。

第一个字节的头 7 位是 11110XX 的组合 ,其中:最后两位 XX 是 10 位地址的两个最高位 MSB

第一个字节的第 8 位是 R/ W 位, 决定了报文的方向 :0 表示写, 1 表示读。

如果 R/ W 位是 0 则下一个字节是 10 位从机地址剩下的 8 位;

如果 R/ W 位是 1 则下一个字节是从机发送给主机的数据。

仲裁过程

主机只能在总线空闲的时侯启动传输。两个或多个主机可能在起始条件的最小持续时间内产生一个起始条件,结果在总线上产生一个规定的起始条件。

当 SCL 线是高电平时,仲裁在 SDA 线发生。这样,在其他主机发送低电平时,发送高电平的主机将退出竞争, 因为总线上的电平与它自己的电平不相同

仲裁可以持续多位。第一个阶段是比较地址位,如果每个主机都尝试寻址相同的器件,当主机作发送器时仲裁会继续比较数据位,当主机作接收器时仲裁会继续比较响应位。

IIC总线的地址和数据信息由赢得仲裁的主机决定,因此在仲裁过程中不会丢失信息

丢失仲裁的主机可以产生时钟脉冲,直到丢失仲裁的该字节末尾。

image-20210928004638830

可以看到在起始信号的第3个时钟周期低电平时,DATA1的SDA输出高电平,而DATA2向SDA输出低电平,SDA总线保持低电平,仲裁给到DATA2,即DATA1的主机退出竞争,不再发送数据。DATA1主机赢得仲裁,SDA总线已传送的数据和DATA1发送的数据保持一致。

5. 读数据

7位寻址为例 CPU作为主接收器

tMLZb4.png

第一步,主机发送一个起始信号S。

第二步,主机发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。

第三步,从机产生一个ACK应答信号。

//第四步,主机发送寄存器地址。
//第五步,从机产生一个ACK应答信号。
//第六步,主机再次发送一个起始信号。
//第七步,主机发送7bit从机地址,即7bit+R/W。最低位为1表示读,为0表示写。
//第八步,从机产生一个ACK应答信号。

第九步,主机读取一个字节(8bit)的数据(相当于从机发送一个字节)。
第十步,CPU产生一个ACK应答信号。

//第十一步,读取一个CRC校验码。

第十二步,CPU产生一个NACK无应答信号。
第十三步,主机产生一个停止信号。

常规的使用就是第一、二、三、九、十、十二、十三步。
注释掉的是单片机集成的硬件IIC配置通信的整个完整的通讯过程。

6. 写数据

CPU作为主发送器

tMbPds.png

第一步,主机发送一个起始信号。

第二步,发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。

第三步,从机产生一个ACK应答信号。

//第四步,主机发送寄存器地址,8bit数据。
//第五步,从机产生一个ACK应答信号。

第六步,主机发送一个字节(8bit)数据。
第七步,从机产生一个ACK应答信号。

//第八步,主机发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。

第九步,从机既可以发送一个应答信号,也可以发送一个无应答信号。
第十步,主机发送一个停止信号。

(三)单片机IIC通讯


1. 软件模拟

CPU与EEPROM通讯为例

相关函数:

void I2C_Delay(void);	//延时,以防单片机速度太快
void I2C_Start(void);	//起始信号
void I2C_Stop(void); 	//终止信号
u8 I2C_SendByte(u8 data);	//写一个字节
u8 I2C_ReadByte(void);	//读一个字节
void EEPROM_Write(u8 address, u8 data);	//往EEPROM的一个地址写一个数据
u8 EEPROM_Read (u8 address);	//读EEPROM的一个地址的数据

stm32软件模拟配置流程:

  1. 初始化GPIO

    • void I2c_Init(void); //初始化GPIO

    • 模拟总线SDA, SCL ,GPIO引脚开漏输出

  2. 写起始信号函数

    • void I2C_Start(void); //起始信号

    • SDA, SCL拉高 , 延时

      • SDA = 1; SCL = 1; delay();
    • SDA由高变低 , 延时

      • SDA = 0; delay();
    • SCL拉低,延时等待发送/接收

      • SCL = 0; delay();
  3. 写终止信号函数

    • void I2C_Stop(void); //终止信号

    • SCL拉低后将SDA拉低 (SCL低电平时SDA可变),延时

      • SCL = 0; SDA = 0; delay();
    • SCL拉高 , 延时

      • SCL = 1; delay();
    • SDA由低变高 , 延时

      • SDA = 1; delay();
  4. 等待应答信号函数

    • u8 I2C_WaitToAck(void); //读取器件ACK应答
  5. 产生/不产生ACK应答

    • void I2C_Ack(void);
    • void I2C_NoAck(void);
  6. 写一个字节

  7. 读一个字节

//延时
static void I2c_Delay(void)
{
    
    
    /*CPU主频72MHz
      10: SCL频率205KHz
      7:  SCL频率347KHz,高电平1.5us,低电平2.87us;
      5:  SCL频率421KHz,高电平1.25us,低电平2.375us;*/
    u8 i;
    for(i = 0; i < 10; i++);
}
//起始信号
void I2C_Start(void)
{
    
    
    I2C_SDA_High();     //SDA=1
    I2C_SCL_High();     //SCL=1
    I2C_Delay();
    I2C_SDA_Low();
    I2C_Delay();
    I2C_SCL_Low();
    I2C_Delay();
}
//终止信号
void I2C_Stop(void)
{
    
    
    I2C_SDA_Low();
    I2C_SCL_High();
    I2C_Delay();
    I2C_SDA_High();
    I2C_Delay();
}
//写一个字节
u8 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();
    }
} 
//读取一个字节
u8 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;
}
//产生一个ACK应答信号
void I2C_Ack(void)
{
    
    
    I2C_SDA_Low();
    I2C_Delay();
    I2C_SCL_High();	//cpu产生一个时钟
    I2C_Delay();
    I2C_SCL_Low();
    I2C_Delay();
 
    I2C_SDA_High();
}
//产生一个非ACK信号
void I2C_NoAck(void)
{
    
    
    I2C_SDA_High();
    I2C_Delay();
    I2C_SCL_High();	//cpu产生一个时钟
    I2C_Delay();
    I2C_SCL_Low();
    I2C_Delay();
}
//产生时钟读取器件ACK应答信号
u8 I2C_WaitToAck(void)
{
    
    
    u8 redata;
 
    I2C_SDA_High();	//CPU释放SDA总线
    I2C_Delay();
    I2C_SCL_High();	//SCL=1此时器件会返回ACK应答
    I2C_Delay();
 
    if(I2C_SDA_READ())	//CPU读取SDA口线状态
    {
    
    
        redata = 1;
    }
    else
    {
    
    
        redata = 0;
    }
    I2C_SCL_Low();
    I2C_Delay();
 
    return redata;
}  

2. 硬件IIC控制器配置(stm32)

(1)主发送器

tQADSS.png

(2)主接收器

tQAbwR.png

(3)配置流程

与EEPROM通讯为例

  1. 初始化IIC相关GPIO
  2. 配置IIC外设的工作模式
  3. 编写IIC写入EEPROM的函数
  4. 编写IIC读取EEPROM的函数
  5. 使用read函数及write函数进行读写校验
  6. 编写page write及seq read函数并校验(页写)
(4)重要函数
  1. IIC初始化结构体
typedef struct{
    
    
    uint32_t I2C_ClockSpeed;	//设置SCL时钟频率 <40,000 
    uint32_t I2C_Mode;	//工作模式 I2C模式或SMBUS模式
    uint32_t I2C_DutyCycle;	//指定时钟占空比 低/高=2/1或16/9
    uint32_t I2C_OwnAddress1;	//指定自身的I2C设备地址
    uint32_t I2C_Ack;	//使能或关闭响应(一般使能)
    uint32_t I2C_AcknowledgedAddress;	//指定地址长度 7位/10位
} I2C_InitTypeDef;

参考:

原理:《IIC总线协议中文版》

代码实现:博客园

学习:野火、正点原子、普中科技
部分图片来源于网站,侵删

修改时间:2021年09月27日

猜你喜欢

转载自blog.csdn.net/weixin_45636061/article/details/120520776