深入IIC总线:从AT24C02和MCP4017的时序图到STM32的C语言实现
·
深入IIC总线:从AT24C02和MCP4017的时序图到STM32的C语言实现
IIC总线作为嵌入式系统中广泛使用的通信协议,其底层时序与代码实现的映射关系一直是开发者深入理解的关键。本文将带您从AT24C02 EEPROM和MCP4017数字电位器的数据手册时序图出发,逐步拆解每个信号变化对应的STM32 C语言实现,让您真正掌握IIC驱动的编写精髓。
1. IIC协议核心机制解析
IIC总线由Philips公司开发,采用两根线(SCL时钟线和SDA数据线)实现主从设备间的半双工通信。其核心特征包括:
- 地址寻址机制:每个从设备有7位或10位唯一地址
- 多主从架构:支持多个主设备通过仲裁机制共享总线
- 标准速率模式:
- 标准模式:100kbps
- 快速模式:400kbps
- 高速模式:3.4Mbps
典型IIC时序包含以下关键阶段:
| 阶段 | 信号特征 | 持续时间 |
|---|---|---|
| 起始条件 | SDA在SCL高电平时拉低 | >4.7μs |
| 地址传输 | 7位地址+1位R/W方向 | 每个时钟周期 |
| 应答信号 | 第9个时钟周期SDA拉低 | >4μs |
| 数据传输 | 8位数据+1位ACK | 每个时钟周期 |
| 停止条件 | SDA在SCL高电平时拉高 | >4μs |
理解这些底层时序对调试IIC设备异常至关重要。例如,当从设备无响应时,首先应检查示波器上的ACK信号是否正常。
2. AT24C02 EEPROM时序与代码实现
AT24C02是2Kbit的串行EEPROM,其典型写操作时序如下:
[起始信号] -> [设备地址+W] -> [ACK] -> [内存地址] -> [ACK] -> [数据] -> [ACK] -> ... -> [停止信号]
对应的STM32驱动代码实现关键点:
void iic_24c02_write(unsigned char *pucBuf, unsigned char ucAddr, unsigned char ucNum) {
I2CStart(); // 产生起始条件
I2CSendByte(0xa0); // 发送设备地址+写标志
I2CWaitAck(); // 等待从机应答
I2CSendByte(ucAddr); // 发送内存地址
I2CWaitAck();
while(ucNum--) {
I2CSendByte(*pucBuf++); // 逐字节发送数据
I2CWaitAck();
}
I2CStop(); // 产生停止条件
delay1(500); // 等待写入完成
}
关键调试技巧:
- 写入后必须延迟等待内部编程完成(t_WR=5ms)
- 页写入时注意不能跨页(AT24C02页大小为8字节)
- 若连续写入超过页大小,地址将自动回绕
读操作时序更为复杂,需要"伪写入"设置地址指针:
void iic_24c02_read(unsigned char *pucBuf, unsigned char ucAddr, unsigned char ucNum) {
I2CStart();
I2CSendByte(0xa0); // 发送设备地址+写标志
I2CWaitAck();
I2CSendByte(ucAddr); // 发送要读取的内存地址
I2CWaitAck();
I2CStart(); // 重复起始条件
I2CSendByte(0xa1); // 发送设备地址+读标志
I2CWaitAck();
while(ucNum--) {
*pucBuf++ = I2CReceiveByte();
if(ucNum) I2CSendAck(); // 非最后一个字节发送ACK
else I2CSendNotAck(); // 最后一个字节发送NACK
}
I2CStop();
}
3. MCP4017数字电位器驱动实现
MCP4017是128抽头的数字电位器,其电阻值计算公式为:
Rwb = (Rab/128) × N = 0.7874kΩ × N (N=0~127)
写操作时序与代码实现:
void write_registor(uint8_t value) {
I2CStart();
I2CSendByte(0x5E); // 设备地址+写标志
I2CWaitAck();
I2CSendByte(value); // 直接发送电阻值
I2CWaitAck();
I2CStop();
}
读操作需要特别注意MCP4017的特殊时序:
uint8_t read_registor(void) {
uint8_t value;
I2CStart();
I2CSendByte(0x5F); // 设备地址+读标志
I2CWaitAck();
value = I2CReceiveByte();
I2CSendNotAck(); // 必须发送NACK终止传输
I2CStop();
return value;
}
实际应用技巧:
- 上电时电位器处于中间位置(N=64)
- 电阻变化呈线性关系,适合用作模拟量调节
- 与固定电阻分压时可计算输出电压:
Vout = Vdd × (Rwb / (Rwb + Rfixed))
4. STM32硬件IIC与软件模拟对比
在嵌入式开发中,IIC实现通常有两种方式:
| 特性 | 硬件IIC | 软件模拟IIC |
|---|---|---|
| 资源占用 | 需要专用外设 | 仅需2个GPIO |
| 性能 | 支持高速模式(400kHz) | 通常<100kHz |
| CPU占用 | 自动处理,低占用 | 需CPU持续参与 |
| 灵活性 | 固定引脚 | 任意GPIO均可 |
| 开发难度 | 需配置复杂寄存器 | 时序完全可控 |
对于蓝桥杯等竞赛环境,软件模拟IIC更具优势:
// 典型GPIO初始化代码
void I2CInit(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// SCL和SDA引脚配置为开漏输出
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始状态置高
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
常见问题解决方案:
-
无应答信号:
- 检查设备地址是否正确(含R/W位)
- 确认上拉电阻值合适(通常4.7kΩ)
- 测量SCL/SDA电压是否达到逻辑高电平
-
数据错位:
- 调整时钟延迟时间
- 检查中断是否干扰时序
- 确保供电稳定无毛刺
-
多设备冲突:
- 为每个设备分配唯一地址
- 增加总线仲裁机制
- 采用分段式通信策略
5. 综合应用实例:数据存储与模拟量控制
结合AT24C02和MCP4017实现完整嵌入式应用:
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
LCD_Init();
I2CInit();
// EEPROM数据存取
unsigned char config[5] = {0xAA,0xBB,0xCC,0xDD,0xEE};
unsigned char readback[5] = {0};
iic_24c02_write(config, 0x10, 5);
HAL_Delay(10); // 必须等待写入完成
iic_24c02_read(readback, 0x10, 5);
// 电阻网络控制
write_registor(64); // 设置中间阻值
HAL_Delay(100);
uint8_t current_val = read_registor();
// LCD显示系统状态
while (1) {
display_eeprom_data(readback);
display_resistor_value(current_val);
display_voltage(calculate_voltage(current_val));
HAL_Delay(500);
}
}
性能优化技巧:
- 对频繁访问的EEPROM数据实现缓存机制
- 电阻值变化采用渐变算法避免突变
- 使用DMA加速大数据块传输
- 实现错误重试机制提高可靠性
在调试这类复合IIC系统时,逻辑分析仪是最有效的工具,可以同时捕获SCL和SDA信号,直观显示时序关系。典型的调试步骤包括:
- 验证起始信号和停止信号的正确性
- 检查设备地址字节的准确性
- 确认每个ACK/NACK响应符合预期
- 测量数据建立时间和保持时间
- 检查停止条件后的总线空闲时间
更多推荐


所有评论(0)