4. I2C总线与传感器驱动:I2C时序与寻址、EEPROM读写、温湿度传感器SHT30驱动移植
I2C总线,说白了就是两根线搞定一堆外设。SCL(时钟线)和SDA(数据线),挂多少个设备都行,只要地址不冲突。我刚开始用I2C时总觉得它慢,后来发现很多传感器、存储芯片都靠它,省引脚才是硬道理。
这一章我们不讲虚的。先搞懂I2C的时序和寻址,然后用EEPROM练手读写,最后移植SHT30温湿度传感器驱动。嗯,都是实战干货。
4.1 I2C总线协议基础
I2C是主从结构。STM32做主设备,外设做从设备。每次通信都由主设备发起,从设备只能响应。
4.1.1 物理层与电气特性
- SCL:时钟线,由主设备控制
- SDA:数据线,双向传输
- 上拉电阻:一般4.7kΩ,总线电容大时用2.2kΩ
- 标准模式:100kHz;快速模式:400kHz
4.1.2 时序与起始/停止条件
I2C的时序其实很简单。记住两个关键点:
- 起始条件:SCL高电平时,SDA从高变低
- 停止条件:SCL高电平时,SDA从低变高
- 数据传输:SCL低电平时改变SDA,高电平时采样
为什么会这样设计?说白了就是为了让从设备能准确识别通信的开始和结束。我曾经在项目里因为起始条件没处理好,导致总线卡死,后来加了超时复位才解决。
4.1.3 寻址与应答机制
每个I2C从设备都有7位或10位地址。STM32常用7位地址。主设备发送地址后,从设备必须回复ACK(应答)才能继续。
| 地址位 | 说明 |
|---|---|
| 7位地址 | 高7位,如0x44(SHT30地址) |
| 读写位 | 第8位:0=写,1=读 |
| ACK/NACK | 第9位:从设备拉低SDA为ACK |
4.2 EEPROM读写实战
EEPROM是I2C最经典的应用之一。AT24C02,2Kbit容量,256字节,够存配置参数。我们用它来练手读写。
4.2.1 AT24C02的寻址与页写
AT24C02的地址由硬件引脚A0、A1、A2决定。一般接地,地址为0x50(7位)。写操作分字节写和页写,页写一次最多16字节。
// AT24C02写一个字节
uint8_t I2C_WriteByte(uint8_t dev_addr, uint8_t mem_addr, uint8_t data) {
// 发送起始条件
I2C_Start();
// 发送设备地址+写位
I2C_SendByte(dev_addr << 1 | 0);
if (!I2C_WaitAck()) return 1;
// 发送存储器地址
I2C_SendByte(mem_addr);
if (!I2C_WaitAck()) return 1;
// 发送数据
I2C_SendByte(data);
if (!I2C_WaitAck()) return 1;
// 发送停止条件
I2C_Stop();
return 0;
}
4.2.2 连续读取与随机读取
读EEPROM有两种方式:
- 当前地址读:直接读当前指针位置
- 随机地址读:先写地址,再重新起始读
连续读时,每读一个字节主设备要回复ACK,直到最后一个字节回复NACK。嗯,这个细节容易忘。
// AT24C02连续读多个字节
void I2C_ReadMulti(uint8_t dev_addr, uint8_t mem_addr, uint8_t *buf, uint8_t len) {
I2C_Start();
I2C_SendByte(dev_addr << 1 | 0); // 写地址
I2C_WaitAck();
I2C_SendByte(mem_addr); // 写存储器地址
I2C_WaitAck();
I2C_Start(); // 重新起始
I2C_SendByte(dev_addr << 1 | 1); // 读地址
I2C_WaitAck();
for (uint8_t i = 0; i < len; i++) {
buf[i] = I2C_ReadByte();
if (i == len - 1)
I2C_SendNack(); // 最后回复NACK
else
I2C_SendAck();
}
I2C_Stop();
}
4.3 SHT30温湿度传感器驱动移植
SHT30是Sensirion的经典数字温湿度传感器。精度高、体积小、I2C接口。我做过一个环境监测项目,用了20多个SHT30,稳定性不错。
4.3.1 SHT30的I2C地址与命令集
SHT30的7位地址固定为0x44(ADDR引脚接GND)或0x45(接VCC)。常用命令:
| 命令 | 代码(16位) | 说明 |
|---|---|---|
| 测量(高重复性) | 0x2C06 | 时钟拉伸使能 |
| 测量(低重复性) | 0x2C10 | 快速模式 |
| 读取状态寄存器 | 0xF32D | 检查设备状态 |
| 软复位 | 0x30A2 | 复位传感器 |
4.3.2 驱动移植步骤
移植SHT30驱动,说白了就是封装I2C读写函数。我习惯分三层:
- 硬件层:I2C起始、停止、收发字节
- 驱动层:SHT30初始化、触发测量、读取数据
- 应用层:温湿度计算、数据滤波
// SHT30触发测量(高重复性)
uint8_t SHT30_StartMeasure(void) {
uint8_t cmd[2] = {0x2C, 0x06};
I2C_Start();
I2C_SendByte(SHT30_ADDR << 1 | 0);
if (!I2C_WaitAck()) return 1;
I2C_SendByte(cmd[0]);
I2C_WaitAck();
I2C_SendByte(cmd[1]);
I2C_WaitAck();
I2C_Stop();
return 0;
}
// 读取温湿度数据
uint8_t SHT30_ReadData(float *temp, float *hum) {
uint8_t buf[6];
I2C_Start();
I2C_SendByte(SHT30_ADDR << 1 | 1);
I2C_WaitAck();
for (uint8_t i = 0; i < 6; i++) {
buf[i] = I2C_ReadByte();
if (i == 5) I2C_SendNack();
else I2C_SendAck();
}
I2C_Stop();
// 计算温湿度
uint16_t raw_temp = (buf[0] << 8) | buf[1];
uint16_t raw_hum = (buf[3] << 8) | buf[4];
*temp = -45.0f + 175.0f * raw_temp / 65535.0f;
*hum = 100.0f * raw_hum / 65535.0f;
return 0;
}
4.4 知识体系结构图
下面这张图把本章的核心逻辑串起来了。从I2C总线到EEPROM,再到SHT30,层层递进。
4.5 调试技巧与常见问题
I2C调试说难不难,说简单也容易卡住。我总结几个常见问题:
- 总线卡死:SDA被拉低不放。解决办法:模拟9个时钟脉冲复位从设备
- 无ACK响应:地址错误或设备未上电。用逻辑分析仪看波形
- 数据错位:字节顺序搞反。SHT30数据是大端,注意高低字节
- 测量值跳变:电源噪声或布线过长。加100nF去耦电容
这一章的内容就到这里。I2C总线是嵌入式开发的基本功,EEPROM和SHT30是两个典型应用。你动手写一遍代码,跑通波形,比看十遍书都管用。