1. IIC协议基础:两线制串行通信的艺术

IIC(Inter-Integrated Circuit)协议是我在嵌入式开发中最常用的通信协议之一。它由飞利浦半导体(现NXP)在1980年代开发,专门用于连接微控制器和外围设备。这么多年用下来,我觉得IIC最吸引人的地方就是它的简洁性——只需要两根线(SDA数据线和SCL时钟线)就能实现设备间的通信,这在PCB布局紧张的项目中简直是救命稻草。

实际项目中,IIC总线通常采用开漏输出结构,这意味着设备只能主动拉低线路,高电平状态需要依赖外部上拉电阻。这个设计带来了两个好处:一是实现了"线与"功能,方便多主机仲裁;二是允许不同电压的设备在同一总线上通信(只要电压在允许范围内)。

我记得第一次用STM32的IIC接口时,最困惑的就是上拉电阻的选择。标准模式(100kHz)下通常用4.7kΩ电阻,快速模式(400kHz)需要2.2kΩ,而高速模式(3.4MHz)则要小到1kΩ。如果总线长度超过100cm,每增加10cm还需要并联1kΩ电阻来补偿电容影响。这些都是实践中积累的经验,数据手册上可不会写得这么详细。

2. IIC协议帧结构深度解析

理解IIC的数据帧结构是掌握这个协议的关键。每次传输都以起始条件(START)开始:当SCL为高电平时,SDA出现下降沿。这个信号告诉所有设备:传输要开始了。然后是7位从机地址和1位读写方向位(0表示写,1表示读)。

我遇到过很多初学者在地址处理上栽跟头。IIC设备地址通常是7位的,但有些厂商会使用8位地址(包含读写位)。比如AT24C02 EEPROM的地址是0xA0(写)和0xA1(读),其实本质上还是7位地址0x50左移1位后加上读写位。

数据传输以字节为单位,每个字节后必须跟一个应答位(ACK)。主机发送完8位数据后,会释放SDA线(拉高),从机需要在第9个时钟周期将SDA拉低作为应答。如果没有收到应答(NACK),通常意味着传输出了问题。

结束传输时,主机产生停止条件(STOP):SCL为高时SDA出现上升沿。这里有个小技巧:在多次读写操作时,可以用重复起始条件(Repeated START)来代替停止条件再起始,这样能保持总线控制权,提高传输效率。

3. STM32硬件IIC配置实战

STM32的硬件IIC外设相当强大,但配置不当就容易出问题。通过CubeMX配置是最快捷的方式,但理解每个参数的含义很重要。

ClockSpeed设置通信速率,标准模式是100kHz,快速模式400kHz。DutyCycle控制时钟占空比,快速模式下有2:1和16:9两种选项。AddressingMode通常选择7位地址模式,有些老设备支持10位地址。NoStretchMode控制时钟延展,建议先禁用,等调通了再根据需求开启。

我常用的初始化代码是这样的:

I2C_HandleTypeDef hi2c1 = {
  .Instance = I2C1,
  .Init.ClockSpeed = 400000,
  .Init.DutyCycle = I2C_DUTYCYCLE_2,
  .Init.OwnAddress1 = 0x00,      // 主模式通常设为0
  .Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT,
  .Init.DualAddressMode = I2C_DUALADDRESS_DISABLE,
  .Init.GeneralCallMode = I2C_GENERALCALL_DISABLE,
  .Init.NoStretchMode = I2C_NOSTRETCH_DISABLE,
};

硬件IIC最大的优势是效率高,CPU占用少。但STM32的IIC外设曾经有个出名的bug(在F1系列中比较明显),容易出现卡死的情况。新系列已经修复了这个问题,但如果遇到异常,可以尝试先执行总线恢复序列:连续发送9个时钟脉冲,然后发送停止条件。

4. 软件模拟IIC:灵活性的代价

当硬件IIC引脚被占用或者需要更多灵活性时,软件模拟IIC(Software IIC)是个不错的选择。我用软件IIC实现过多个IIC总线,甚至在同一组引脚上分时复用。

模拟IIC的关键是精确的时序控制。每个信号变化都要配合适当的延时。比如起始条件:先将SDA拉高,延时;再将SCL拉高,延时;然后SDA拉低,延时;最后SCL拉低。

void I2C_Start(void) {
  SDA_HIGH();
  Delay_us(1);
  SCL_HIGH();
  Delay_us(1);
  SDA_LOW();
  Delay_us(1);
  SCL_LOW();
  Delay_us(1);
}

软件IIC的优点是引脚分配灵活,调试方便。缺点是CPU占用率高,速度受限制。在72MHz的STM32F103上,我通常能做到200kHz左右的稳定通信,再高就容易出错了。

5. 常见外设驱动开发实战

在实际项目中,最常接触的IIC设备包括EEPROM(如AT24C系列)、温度传感器(如LM75、SHT30)、实时时钟(如DS1307)等。每个设备都有其特定的寄存器结构和操作时序。

以AT24C02为例,写操作需要先发送设备地址(写模式),然后发送内存地址,最后发送数据。读操作稍微复杂些:先发送设备地址(写模式)和内存地址,然后发送重复起始条件和设备地址(读模式),最后读取数据。

// 读取AT24C02指定地址的数据
uint8_t AT24C02_Read(uint8_t addr) {
  uint8_t data;
  HAL_I2C_Master_Transmit(&hi2c1, 0xA0, &addr, 1, 100);
  HAL_I2C_Master_Receive(&hi2c1, 0xA1, &data, 1, 100);
  return data;
}

温度传感器通常需要更复杂的操作。比如SHT30需要先发送测量命令(0x2C06),等待测量完成,然后读取6个字节的数据(温度、湿度、CRC校验)。这些细节往往决定了项目的成败。

6. 调试技巧与常见问题解决

IIC总线调试是我觉得最有挑战性也最有成就感的部分。最常见的问题是通信完全无响应,这时候首先要检查硬件连接:电源是否正常,上拉电阻是否正确焊接,线路是否有短路或断路。

如果硬件没问题,就用逻辑分析仪或者示波器抓波形。我习惯先看起始条件是否正常:SCL高电平时SDA应该有明显的下降沿。然后检查地址字节是否正确,特别是读写位。最后看应答信号,每个字节后的第9个时钟周期,SDA应该被拉低。

地址冲突也是常见问题。有一次我同时用了AT24C02(地址0x50)和LM75(地址0x48),结果发现LM75的实际地址是0x90(7位地址左移1位后是0x90),两者并不冲突。这种地址表示方式的差异经常让人困惑。

电平不匹配问题在混合电压系统中很常见。比如3.3V的STM32与5V的器件通信时,需要电平转换电路。我常用的是TXS0108PWR这类双向电平转换芯片,简单可靠。

7. 性能优化与高级应用

当项目对通信速度有要求时,就需要考虑性能优化了。DMA传输是首选方案,能大幅降低CPU占用率。STM32的IIC外设支持DMA,配置稍微复杂但效果显著。

// 配置IIC DMA传输
HAL_I2C_Master_Transmit_DMA(&hi2c1, dev_addr, pData, size);

多主机仲裁是IIC的高级特性,允许多个主设备共享同一总线。当两个主设备同时发起传输时,仲裁机制会确保只有一个获胜。失败的主设备会检测到仲裁丢失,然后退避重试。

中断方式处理IIC通信可以提高系统响应速度。STM32的IIC提供了丰富的中断事件:传输完成、接收完成、仲裁丢失、总线错误等。合理使用这些中断能构建出高效的多任务系统。

在通信可靠性要求高的场合,还需要加入超时重传机制。我通常会在应用层实现简单的重传逻辑:如果一次传输失败,等待短暂延时后重试,最多重试3次。如果仍然失败,就执行总线恢复程序。

8. 实际项目经验分享

在我做过的一个工业温控项目中,需要同时读取8个温度点的数据。最初采用轮询方式逐个读取,整个循环需要200ms,达不到实时性要求。后来改用多路复用器(TCA9548A)扩展出8个IIC总线,并行读取,将时间缩短到30ms。

另一个项目中使用了大容量EEPROM(AT24C512),需要跨页写入。AT24C系列的页大小是64字节,如果写入数据跨页,需要分两次操作。我写了个智能写函数自动处理分页:

void EEPROM_WriteMulti(uint16_t addr, uint8_t *data, uint16_t len) {
  while (len > 0) {
    uint16_t page_remaining = 64 - (addr % 64);
    uint16_t write_size = (len < page_remaining) ? len : page_remaining;
    
    HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_16BIT, 
                      data, write_size, 100);
    
    addr += write_size;
    data += write_size;
    len -= write_size;
    HAL_Delay(5); // 等待写周期完成
  }
}

最棘手的是一次电机控制项目,IIC总线受到强烈的电磁干扰。解决方案是:改用屏蔽线缆,在IIC线路上加入RC滤波(100Ω电阻串联,100pF电容对地),并降低通信速率到100kHz。这些经验都是在实践中一点点积累起来的。

每个IIC设备都有其特性,需要仔细阅读数据手册。比如有些设备需要额外的初始化序列,有些对时序要求特别严格,有些在连续读写时需要额外的延时。掌握这些细节,才能写出稳定可靠的驱动程序。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐