3. 数字传感器接口(I2C):I2C协议详解、时序分析、基于STM32的温湿度传感器(SHT30)驱动开发
各位同学,咱们今天聊聊I2C。说实话,这玩意儿在嵌入式圈子里太常见了。我入行那会儿,第一个正经项目就是调I2C的温湿度传感器。当时被时序折腾得够呛,现在回想起来,其实就那么回事儿。
I2C,全称Inter-Integrated Circuit,是飞利浦公司搞出来的。它只用两根线——SCL(时钟线)和SDA(数据线),就能挂一堆设备。你想想看,省了多少IO口?
3.1 I2C协议核心要点
先说说I2C的基本概念。它是个主从架构,主机通常是MCU,从机就是传感器、存储器这些外设。每个从机都有个唯一的地址,7位或10位。SHT30用的是7位地址,默认是0x44。
我习惯把I2C通信分成几个阶段:
- 起始条件:SCL高电平时,SDA从高变低
- 地址传输:主机发送7位地址+1位读写位
- 数据收发:每字节后跟一个ACK应答位
- 停止条件:SCL高电平时,SDA从低变高
这里有个细节——应答机制。发送方每发完一个字节,会释放SDA线,接收方拉低SDA表示ACK。如果没拉低,那就是NACK,表示出问题了。我在项目中遇到过,有时候从机没准备好,就会回NACK,这时候你得重试或者报错。
3.2 时序分析——别被示波器吓到
很多新手看到时序图就头大。其实没那么复杂。I2C的时序参数就那么几个:
| 参数 | 标准模式 | 快速模式 | 说明 |
|---|---|---|---|
| SCL频率 | 100kHz | 400kHz | 别超了,有些传感器扛不住 |
| 上升时间 | ≤1000ns | ≤300ns | 跟总线电容有关 |
| 数据建立时间 | ≥250ns | ≥100ns | SCL低电平时数据要准备好 |
| 数据保持时间 | ≥0ns | ≥0ns | SCL下降沿后数据保持 |
嗯,这里要注意。上拉电阻的选择很关键。我一般用4.7kΩ,如果总线设备多或者线长,换成2.2kΩ。电阻太小,功耗大;太大,上升沿太慢,容易出时序问题。
避坑指南:我曾经在一个项目里,I2C通信时好时坏。查了两天,最后发现是上拉电阻焊错了,用了10kΩ。总线电容一大,上升沿就慢了,数据采样出错。所以,上拉电阻别马虎。
3.3 SHT30温湿度传感器驱动开发
SHT30是Sensirion家的产品,精度不错,价格也合适。它的通信协议挺标准,就是I2C。咱们一步步来。
3.3.1 硬件连接
SHT30有8个引脚,但常用的就4个:VCC、GND、SCL、SDA。ADDR引脚决定I2C地址,接GND时地址是0x44,接VCC时是0x45。我习惯接GND,省事。
连接示意图:
STM32 SHT30
PB6 (SCL) --- SCL
PB7 (SDA) --- SDA
3.3V --- VCC
GND --- GND
别忘了加两个4.7kΩ上拉电阻到VCC。有些开发板内部有上拉,但我不建议依赖它。
3.3.2 初始化I2C外设
用STM32的HAL库,初始化很简单。我一般这么配:
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz标准模式
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}
时钟速度我设100kHz,虽然SHT30支持400kHz,但100kHz更稳定。你想想看,温湿度数据又不是高速采集,没必要追求那点速度。
3.3.3 发送命令
SHT30的命令是16位的。比如测量命令0x2C06,表示开启高重复性测量。发送流程:
uint8_t cmd[2] = {0x2C, 0x06};
HAL_I2C_Master_Transmit(&hi2c1, SHT30_ADDR, cmd, 2, 100);
这里SHT30_ADDR是0x44左移1位,即0x88。为什么左移?因为HAL库的地址是8位的,读写位由函数内部处理。
个人经验:发送命令后,SHT30需要等待一段时间才能读取数据。我一般等15ms,虽然手册说最坏情况15ms,但实际10ms就够了。不过,稳妥起见,还是按手册来。
3.3.4 读取数据
测量完成后,读取6个字节:温度高字节、温度低字节、CRC、湿度高字节、湿度低字节、CRC。CRC校验可选,但我建议加上,尤其是产品级代码。
uint8_t data[6];
HAL_I2C_Master_Receive(&hi2c1, SHT30_ADDR, data, 6, 100);
// 温度值
uint16_t temp_raw = (data[0] << 8) | data[1];
float temperature = -45.0 + 175.0 * temp_raw / 65535.0;
// 湿度值
uint16_t hum_raw = (data[3] << 8) | data[4];
float humidity = 100.0 * hum_raw / 65535.0;
转换公式是SHT30手册里给的。说白了,就是把16位原始值映射到物理量程上。温度是-45到130℃,湿度是0到100%RH。
3.3.5 CRC校验
CRC校验用多项式0x31(x^8 + x^5 + x^4 + 1)。我直接贴代码:
uint8_t sht30_crc8(uint8_t *data, uint8_t len)
{
uint8_t crc = 0xFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc <<= 1;
}
}
return crc;
}
读取数据后,用这个函数校验data[0]和data[1]的CRC,再校验data[3]和data[4]的CRC。如果校验失败,我建议丢弃这组数据,重新读取。
注意:CRC校验不是必须的,但能帮你发现通信错误。我在一个工业项目里,现场干扰大,不加CRC的话,偶尔会读到离谱的数据。加了CRC后,问题就解决了。
3.4 完整驱动框架
我习惯把驱动封装成几个函数:
// sht30.h
typedef struct {
float temperature;
float humidity;
uint8_t crc_ok;
} sht30_data_t;
uint8_t sht30_init(void);
uint8_t sht30_read(sht30_data_t *data);
实现文件里,init函数负责初始化I2C和发送配置命令。read函数负责触发测量、等待、读取和解析数据。这样上层调用就很简单了:
sht30_data_t sensor_data;
if (sht30_read(&sensor_data) == 0) {
printf("温度: %.2f°C, 湿度: %.2f%%\n",
sensor_data.temperature, sensor_data.humidity);
}
嗯,这里要注意。SHT30有个特性——它支持周期测量模式,可以自动输出数据。但我个人习惯用单次测量模式,功耗更低,控制也更灵活。
3.5 常见问题与调试技巧
调试I2C时,我一般先看波形。用逻辑分析仪抓SCL和SDA,看看起始条件、地址、数据、停止条件对不对。如果没有逻辑分析仪,也可以用示波器,但触发设置麻烦点。
常见问题:
- 总线卡死:SDA一直被拉低。通常是通信过程中发生了错误,从机在等主机释放总线。解决办法:复位从机或重新初始化I2C外设。
- 地址不对:检查地址是否左移了。HAL库和LL库的地址格式不一样,别搞混。
- 数据全0或全1:大概率是硬件连接问题,或者传感器没上电。
调试技巧:我习惯在代码里加个I2C扫描函数,遍历所有地址,看看哪些设备在线。这样能快速确认硬件连接和地址是否正确。
3.6 本章知识体系
下面这张图总结了I2C驱动开发的核心逻辑:
从协议基础到硬件连接,再到驱动代码,每一步都环环相扣。说白了,I2C驱动开发就是个搭积木的过程——把每个模块搞对了,整个系统就稳了。
好了,这一章就到这儿。记住,调试I2C时别急,先看波形,再查代码。很多时候问题出在硬件上,而不是软件。
可可精品资料库