HAL库软件IIC驱动AT24C02/AT24C32:从时序解析到多模式读写实战
1. 为什么需要软件IIC?从硬件IIC的痛点说起
刚开始接触STM32的IIC通信时,我和很多初学者一样,首先尝试的是硬件IIC。毕竟STM32提供了专门的I2C外设,用起来应该很方便才对。但实际使用中,我发现硬件IIC并没有想象中那么美好。
硬件IIC最大的问题在于时序的严格性。STM32的硬件IIC对时序要求非常苛刻,稍微有点干扰或者时序不匹配就容易出现卡死、无响应的情况。特别是在多设备共享总线时,一旦某个设备没有及时响应,整个总线就可能陷入死锁状态,需要重新初始化才能恢复。
相比之下,软件IIC虽然占用CPU资源,但有着无可替代的优势。首先是灵活性,你可以完全控制时序的每个细节,根据不同的从设备调整延时时间。其次是稳定性,不会出现硬件IIC那种莫名其妙的死锁问题。最重要的是,软件IIC能让你真正理解IIC协议的本质,而不是简单地调用库函数。
我在实际项目中选择软件IIC还有一个重要原因:引脚分配的灵活性。硬件IIC的引脚是固定的,当这些引脚被其他功能占用时,软件IIC可以任意选择可用的GPIO引脚,这在PCB布局时提供了很大的便利。
2. 深入理解IIC时序:从信号层面掌握通信本质
要写好软件IIC驱动,首先必须吃透IIC的时序要求。IIC协议看起来简单,但细节很多,每个信号都有严格的时间要求。
起始信号(START)是通信的开始,当SCL为高电平时,SDA从高电平跳变到低电平。这个跳变必须在SCL高电平期间完成,而且保持时间要足够长,确保从设备能够检测到这个变化。在实际编码中,我通常会这样实现:
void iic_start(void) { IIC_SDA_H(); // 先保证SDA为高 IIC_SCL_H(); // SCL拉高 iic_delay(); // 保持一段时间 IIC_SDA_L(); // SDA拉低产生下降沿 iic_delay(); IIC_SCL_L(); // 最后拉低SCL,准备数据传输 } 停止信号(STOP)则相反,在SCL高电平期间,SDA从低电平跳变到高电平。这个信号告诉从设备通信结束,释放总线。
数据传输时有个重要规则:只有在SCL为低电平时,SDA才能改变电平状态;SCL为高电平时,SDA必须保持稳定。这是因为从设备在SCL高电平时采样SDA的数据,如果此时SDA发生变化,就可能被误认为是起始或停止信号。
应答机制是IIC协议的精髓。主机每发送完8位数据,就会在第9个时钟周期释放SDA线,等待从设备拉低SDA作为应答。如果从设备没有拉低SDA,表示未应答(NACK),主机可以根据这个判断通信是否成功。
3. GPIO配置的关键细节:为什么选择开漏输出?
在配置GPIO时,SDA线的模式选择很有讲究。推挽输出和开漏输出是两种不同的驱动方式,对IIC通信有直接影响。
推挽输出的特点是能够主动输出高电平和低电平,驱动能力强。但在IIC通信中,SDA线需要被主机和从设备共享,推挽输出在切换方向时需要重新配置GPIO模式,增加了复杂度。
开漏输出只能主动拉低电平,高电平靠外部