【车载开发系列】IIC总线协议时序图

【车载开发系列】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上所传输每一位数据都会占用一个时钟周期。

  • 主机接收到从机的应答信号之后,

    1. 如果第8位读写位是0,即主机向从机写数据,接下来继续由主机占用SDA,向从机传输数据,期间主机每传送8位数据,从机就会产生一个应答信号ACK,由主机接收。
    2. 如果第8位读写位是1 ,即从机向主机写数据,接下来继续由从机占用SDA,向主机传输数据,期间从机每传送8位数据,主机就会产生一个应答信号ACK,由从机接收。
  • 每传输8位数据,都会产生ACK信号,除了以下三种情况外;

    1. 从机不能响应主机发送的从机地址
      例如从机正忙而无法响应IIC总线的操作,或者这个地址没有对应的从机,那么在第9个SCL周期内SDA线就没有被拉低,即没有ACK信号。
      这时,由主机发送一个停止信号终止传输,或者重新发送一个START信号开始新的传输。
    2. 从机无法接收更多数据
      如果从机无法接收更多的数据,即主机发送的数据超过从机的数据接收能力时,从机不会发出ACK信号,这时,由主机发出一个停止信号终止传输或者重新发送一个START信号开始新的传输。
    3. 主机接收最后一个字节
      主机接收器接收到最后一个字节后,也不会发出ACK信号。于是从机发送器释放SDA线,以允许主机发出停止信号结束传输。

在这里插入图片描述

### 模拟 IIC 协议时序的方法及实现 #### 方法概述 IIC(Inter-Integrated Circuit)总线是一种同步串行半双工通信总线,广泛应用于嵌入式系统中的短距离通信。为了更好地理解和掌握其工作原理,可以通过软件方式模拟 IIC 的时序[^1]。 通过手动控制 SDA(数据线)和 SCL(时钟线),可以精确地遵循 IIC 协议的时序要求完成启动、停止、发送地址、读写数据以及应答/非应答信号的操作[^2]。 --- #### 软件模拟的关键步骤 以下是基于通用微控制器(如 STM32 或其他单片机)实现 IIC 总线协议的核心逻辑: 1. **初始化 GPIO** 配置用于模拟 SDA 和 SCL 的两个 GPIO 引脚为开漏输出模式,并设置上拉电阻以确保高电平状态正常[^4]。 ```c void IIC_GPIO_Init() { // 初始化 SDA 和 SCL 为开漏输出模式并启用上拉 GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // 假设使用 PA 口 GPIO_InitStruct.Pin = GPIO_PIN_SDA | GPIO_PIN_SCL; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 2. **定义基础操作函数** 实现对 SDA 和 SCL 的高低电平切换功能: ```c #define SDA_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_SDA, GPIO_PIN_SET) #define SDA_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_SDA, GPIO_PIN_RESET) #define SCL_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_SCL, GPIO_PIN_SET) #define SCL_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_SCL, GPIO_PIN_RESET) void delay_us(uint16_t us) { // 使用硬件定时器或其他方法延时若干微秒 } ``` 3. **生成起始条件** 在 SCL 保持高电平时,SDA 由高变低表示起始位。 ```c void IIC_Start() { SDA_HIGH(); delay_us(5); // 确保稳定时间 SCL_HIGH(); delay_us(5); SDA_LOW(); delay_us(5); SCL_LOW(); } ``` 4. **生成停止条件** 在 SCL 保持高电平时,SDA 由低变高表示停止位。 ```c void IIC_Stop() { SDA_LOW(); delay_us(5); SCL_HIGH(); delay_us(5); SDA_HIGH(); delay_us(5); } ``` 5. **发送字节** 将一个字节逐位发送至从设备,每发送一位需等待从设备返回 ACK/NACK。 ```c uint8_t IIC_Send_Byte(uint8_t byte) { uint8_t i, ack; for (i = 0; i < 8; i++) { SCL_LOW(); delay_us(5); if (byte & 0x80) { SDA_HIGH(); } else { SDA_LOW(); } delay_us(5); SCL_HIGH(); delay_us(5); byte <<= 1; } // 获取从设备的 ACK SDA_HIGH(); // 放手让从设备决定 ACK SCL_HIGH(); delay_us(5); ack = !HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_SDA); // 如果 SDA 是低,则收到 ACK SCL_LOW(); return ack; } ``` 6. **接收字节** 主设备释放 SDA 控制权,允许从设备驱动该线路传输数据。 ```c uint8_t IIC_Receive_Byte(uint8_t send_ack) { uint8_t i, data = 0; SDA_HIGH(); // 主设备放手,准备接收数据 for (i = 0; i < 8; i++) { SCL_HIGH(); delay_us(5); data = (data << 1) | HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_SDA); SCL_LOW(); delay_us(5); } if (send_ack) { SDA_LOW(); // 发送 ACK } else { SDA_HIGH(); // 不发 ACK 表示结束 } SCL_HIGH(); delay_us(5); SCL_LOW(); return data; } ``` 7. **主流程调用** 结合上述子程序构建完整的 IIC 数据交互过程。 ```c void IIC_Master_Transmit(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t length) { IIC_Start(); IIC_Send_Byte((slave_addr << 1) | 0); // 写命令 IIC_Send_Byte(reg_addr); // 寄存器地址 for (uint8_t i = 0; i < length; i++) { IIC_Send_Byte(data[i]); } IIC_Stop(); } ``` --- #### 注意事项 - 所有延迟均需满足 IIC 协议标准的时间参数要求[^3]。 - 对于高速模式下的应用,可能需要优化代码效率或采用 DMA 方式减少 CPU 干预。 - 若目标器件支持硬件 IIC 功能,优先考虑利用内置外设而非完全依赖软件模拟。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的横打

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值