IIC通信协议深度解析及STM32实战应用指南


IIC通信协议详解与STM32实战指南

引言

IIC(Inter-Integrated Circuit)是Philips公司开发的串行通信协议,广泛应用于传感器、EEPROM、RTC等低速外设的连接。本文深入解析IIC协议原理,并提供基于STM32的GPIO模拟实现方案,包含完整的代码解析和实战应用示例。


一、IIC协议核心原理

1. 物理层特性

特性 参数说明
总线构成 SCL(时钟线)+ SDA(数据线)
传输模式 半双工
最大设备数 112(7位地址)
传输速率 标准模式100kbps,快速模式400kbps
电平特性 开漏输出+上拉电阻(通常4.7KΩ)

核心优势

  • 仅需两根线即可实现多设备通信
  • 内置冲突检测和仲裁机制
  • 支持热插拔(需设备具备总线释放功能)
  • 2. 协议层详解

    数据帧结构
    [Start] + [Device Address + R/W] + [ACK] + [Data] + [ACK] + ... + [Stop]
             └─7位地址─┘ └─0:写 1:读─┘
    
    关键时序节点
    1. 起始条件:SCL高电平时,SDA从高→低跳变
    2. 停止条件:SCL高电平时,SDA从低→高跳变
    3. 数据有效性:SCL高电平期间必须保持SDA稳定
    4. 应答机制:每字节传输后接收方必须拉低SDA

    二、IIC通信代码实现(详细注释版)

    1. GPIO模拟IIC初始化

    // IIC引脚定义(以STM32F103 PA6-SCL, PA7-SDA为例)
    #define IIC_SCL_PIN    GPIO_Pin_6
    #define IIC_SDA_PIN    GPIO_Pin_7
    #define IIC_PORT       GPIOA
    
    void IIC_Init(void) {
        GPIO_InitTypeDef GPIO_InitStruct;
        
        /* 开启GPIO时钟 */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
        /* SCL和SDA配置为开漏输出模式 */
        GPIO_InitStruct.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出
        /* 为什么用开漏模式?
           允许总线"线与"特性,配合上拉电阻实现电平控制 */
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(IIC_PORT, &GPIO_InitStruct);
        
        /* 拉高总线(空闲状态) */
        IIC_SCL_H();
        IIC_SDA_H();
    }
    

    2. 基础信号函数解析

    /* 产生IIC起始信号 */
    void IIC_Start(void) {
        SDA_OUT();     // 设置SDA为输出模式
        IIC_SDA_H();
        IIC_SCL_H();
        delay_us(5);   // 保持时间>4.7us
        IIC_SDA_L();   // 下降沿触发起始条件
        delay_us(5);
        IIC_SCL_L();   // 钳住总线,准备发送数据
    }
    
    /* 产生IIC停止信号 */
    void IIC_Stop(void) {
        SDA_OUT();
        IIC_SDA_L();
        IIC_SCL_H();   // 停止条件:SCL高时SDA上升
        delay_us(5);
        IIC_SDA_H();
        delay_us(5);
    }
    
    /* 发送一个字节(MSB First)*/
    uint8_t IIC_SendByte(uint8_t byte) {
        uint8_t ack;
        SDA_OUT();
        for(int i=0; i<8; i++) {
            IIC_SCL_L();
            delay_us(2);
            (byte & 0x80) ? IIC_SDA_H() : IIC_SDA_L();
            byte <<= 1;
            delay_us(3);
            IIC_SCL_H();      // 上升沿锁存数据
            delay_us(5);
        }
        
        /* 等待从机应答 */
        IIC_SCL_L();
        SDA_IN();       // 切换SDA为输入模式
        delay_us(2);
        IIC_SCL_H();
        ack = IIC_READ_SDA(); // 读取ACK信号(0-应答,1-无应答)
        delay_us(5);
        IIC_SCL_L();
        return ack;
    }
    
    /* 接收一个字节(带应答控制) */
    uint8_t IIC_RecvByte(uint8_t ack) {
        uint8_t data = 0;
        SDA_IN(); 
        for(int i=0; i<8; i++) {
            IIC_SCL_L();
            delay_us(5);
            IIC_SCL_H();      // 从机在SCL低电平时更新数据
            data <<= 1;
            data |= IIC_READ_SDA();
            delay_us(5);
        }
        /* 发送应答信号 */
        SDA_OUT();
        ack ? IIC_SDA_L() : IIC_SDA_H(); // 0-应答,1-非应答
        delay_us(2);
        IIC_SCL_H();
        delay_us(5);
        IIC_SCL_L();
        return data;
    }
    

    3. EEPROM读写示例(AT24C02)

    /* 写单字节到EEPROM */
    void EEPROM_Write(uint8_t addr, uint8_t data) {
        IIC_Start();
        IIC_SendByte(0xA0);     // 设备地址 + 写操作(0)
        IIC_SendByte(addr);     // 内存地址
        IIC_SendByte(data);     // 写入数据
        IIC_Stop();
        delay_ms(10);           // 等待EEPROM内部写入完成
    }
    
    /* 从EEPROM读取单字节 */
    uint8_t EEPROM_Read(uint8_t addr) {
        uint8_t data;
        IIC_Start();
        IIC_SendByte(0xA0);     // 设备地址 + 写操作
        IIC_SendByte(addr);     // 设置读地址
        IIC_Start();            // 重复起始条件
        IIC_SendByte(0xA1);     // 设备地址 + 读操作(1)
        data = IIC_RecvByte(0); // 读取数据(发送非应答)
        IIC_Stop();
        return data;
    }
    

    关键代码原理说明

    1. IIC总线特性

  • 开漏输出:必须外接上拉电阻(通常4.7KΩ),避免总线电平冲突
  • 地址格式:7位设备地址 + 1位读写方向位(0-写,1-读)
  • 2. 时序控制要点

  • 起始条件:SCL高电平时,SDA从高→低跳变
  • 停止条件:SCL高电平时,SDA从低→高跳变
  • 数据有效性:SCL高电平期间,SDA必须保持稳定
  • 应答机制:每字节传输后接收方必须拉低SDA
  • 3. EEPROM操作流程

    1. 写操作:

    2. 发送设备地址(写模式)
    3. 发送内存地址
    4. 发送数据
    5. 等待内部编程完成(典型5ms)
    6. 读操作:

    7. 发送设备地址(写模式)→设置内存地址
    8. 重复起始条件
    9. 发送设备地址(读模式)→读取数据

    常见疑问解答

    Q1: 为什么设备地址是0xA0?
    A1: AT24C02的7位地址为1010000(A0-A2接地),左移后加写操作位(0)得到0xA0。

    Q2: 如何调整通信速率?
    A2: 修改延时函数参数,标准模式(100kbps)要求SCL高低电平各≥4.7us,快速模式(400kbps)≥0.6us。

    Q3: 何时需要加上拉电阻?
    A3: 当总线电容较大或设备较多时,建议在SCL和SDA线上加4.7KΩ上拉电阻至3.3V/5V。

    Q4: 如何处理多设备冲突?
    A4: 每个IIC设备有唯一地址,总线仲裁机制会自动解决冲突,软件需检测ACK信号判断是否成功。


    三、进阶开发技巧

    1. 错误处理机制

    #define IIC_TIMEOUT   1000  // 超时阈值(单位:us)
    
    uint8_t IIC_WaitAck(void) {
        uint32_t time = 0;
        SDA_IN();
        IIC_SCL_H();
        
        while(GPIO_ReadInputDataBit(IIC_PORT, IIC_SDA_PIN)) {
            if(++time > IIC_TIMEOUT) {
                IIC_Stop();
                return 1; // 超时错误
            }
            delay_us(1);
        }
        IIC_SCL_L();
        return 0;
    }
    

    2. 总线扫描工具

    void IIC_Scanner(void) {
        printf("Scanning IIC devices...\n");
        
        for(uint8_t addr=0x08; addr<0x78; addr++) {
            IIC_Start();
            uint8_t ack = IIC_SendByte(addr << 1);
            IIC_Stop();
            
            if(!ack) {
                printf("Device found at 0x%02X\n", addr);
            }
            delay_ms(10);
        }
    }
    

    四、常见问题排查指南

    1. 典型故障现象

  • 总线锁死:检查SCL/SDA是否被意外拉低,尝试发送虚假时钟
  • 地址无响应:确认设备地址是否正确(含地址引脚电平)
  • 数据错位:检查时序延时是否符合设备要求
  • 2. 调试建议

    1. 用示波器捕获总线波形,验证时序参数
    2. 在起始信号后添加LED指示,确认通信触发
    3. 逐步提升速率测试(从10kHz开始)
    4. 检查上拉电阻值(计算公式:Rp < (Vdd – Vol)/Iol)

    结语

    IIC协议凭借其简洁的硬件设计和灵活的多设备管理能力,在嵌入式领域占据重要地位。通过GPIO模拟实现,开发者可以深入理解协议细节,但在量产项目中建议使用硬件IIC外设以获得更好的稳定性。实际开发中需特别注意总线负载能力和时序参数的匹配。

    延伸学习建议

  • 研究IIC总线仲裁机制
  • 探索DMA在高速模式下的应用
  • 了解SMBus协议扩展特性
  • 作者:FightingLod

    物联沃分享整理
    物联沃-IOTWORD物联网 » IIC通信协议深度解析及STM32实战应用指南

    发表回复