4、传感器驱动开发(I):温湿度传感器(SHT30/DHT22)数据手册解读、I2C时序分析与驱动代码实现
好,咱们今天来啃一块硬骨头——温湿度传感器驱动。说实话,嵌入式开发里,传感器驱动这块是绕不开的坎。你想想看,一个气象站要是连温湿度都测不准,那还叫气象站吗?
这节课我选了市面上最常用的两款传感器:SHT30和DHT22。一个走I2C,一个走单总线。我个人的习惯是,先啃数据手册,再动手写代码。别急着上电,先把手册看透了,能省下后面一大半调试时间。
4.1 先聊聊SHT30和DHT22的区别
这两款传感器,说白了就是两个时代的产物。DHT22是经典款,便宜、皮实,但精度和稳定性一般。SHT30是后来者,走I2C,精度高,还带校准。
| 参数 | SHT30 | DHT22 |
|---|---|---|
| 接口类型 | I2C | 单总线(One-Wire) |
| 温度精度 | ±0.3°C | ±0.5°C |
| 湿度精度 | ±2% RH | ±2-5% RH |
| 采样速率 | 最高 10 Hz | 最高 0.5 Hz |
| 工作电压 | 2.4V - 5.5V | 3.3V - 5.5V |
| 典型价格 | 约 15-25 元 | 约 5-10 元 |
我的建议:如果项目预算允许,优先选SHT30。我在一个农业大棚项目里吃过DHT22的亏——湿度一高,数据就开始飘。后来换成SHT30,稳得很。
4.2 数据手册解读——SHT30篇
拿到SHT30的数据手册,别从头翻到尾。我教你一个方法:先看引脚定义,再看通信协议,最后看寄存器。
4.2.1 引脚定义
SHT30一共8个引脚,但常用的就4个:VDD、GND、SCL、SDA。还有个ADDR引脚,用来设置I2C地址。默认地址是0x44,如果把ADDR拉高,就变成0x45。
小技巧:如果你在一条I2C总线上挂多个SHT30,可以通过ADDR引脚区分。我一般习惯用0x44作为默认地址,省事。
4.2.2 I2C时序分析
SHT30的I2C时序,说白了就是标准的I2C协议。但有几个坑要注意:
- 起始条件:SCL高电平时,SDA从高变低
- 停止条件:SCL高电平时,SDA从低变高
- 数据有效性:SCL低电平时改变SDA,SCL高电平时采样
- 应答信号:每发送完一个字节,从机拉低SDA表示应答
为什么会这样?因为I2C协议就是这么规定的。你想想看,要是没有这些时序约束,总线上的设备不就乱套了吗?
4.2.3 命令集
SHT30的命令都是16位的。最常用的就这几个:
| 命令 | 代码 | 说明 |
|---|---|---|
| 测量命令(高重复性) | 0x2400 | 默认模式,精度最高 |
| 测量命令(中重复性) | 0x240B | 平衡精度和速度 |
| 测量命令(低重复性) | 0x2416 | 速度最快,精度最低 |
| 读取状态寄存器 | 0xF32D | 用于诊断 |
| 软复位 | 0x30A2 | 复位传感器 |
注意:发送命令时,一定要先发送高字节,再发送低字节。我曾经在这里翻过车,发送顺序搞反了,传感器死活没反应。
4.3 数据手册解读——DHT22篇
DHT22的数据手册就简单多了,总共没几页。但越简单的东西,坑往往越多。
4.3.1 单总线协议
DHT22用的是单总线协议,说白了就是一根线既当时钟又当数据。主机通过拉低总线来发起通信。
时序要求是这样的:
- 起始信号:主机拉低总线至少18ms,然后释放
- 响应信号:DHT22拉低总线80μs,再拉高80μs
- 数据位'0':低电平50μs,高电平26-28μs
- 数据位'1':低电平50μs,高电平70μs
嗯,这里要注意。DHT22的时序要求非常严格,尤其是起始信号的18ms。我刚开始做的时候,用了个10ms的延时,结果数据死活读不出来。后来一查手册,才发现是延时不够。
4.3.2 数据格式
DHT22一次传输40位数据:
- 高16位:湿度数据(乘以0.1得到实际湿度)
- 低16位:温度数据(乘以0.1得到实际温度)
- 最后8位:校验和
校验方法:将前4个字节相加,取低8位,应该等于校验和。如果不相等,说明数据有误,直接丢弃。
4.4 驱动代码实现——SHT30
好,理论讲完了,咱们来写代码。我习惯用HAL库,但原理是一样的。
// SHT30驱动代码片段
#define SHT30_ADDR 0x44 << 1
#define SHT30_CMD_MEASURE 0x2400
// 读取温湿度
uint8_t SHT30_ReadTempHum(float *temp, float *hum) {
uint8_t buf[6];
uint16_t raw_temp, raw_hum;
// 发送测量命令
uint8_t cmd[2] = {0x24, 0x00};
HAL_I2C_Master_Transmit(&hi2c1, SHT30_ADDR, cmd, 2, 100);
// 等待测量完成(至少15ms)
HAL_Delay(20);
// 读取6字节数据
HAL_I2C_Master_Receive(&hi2c1, SHT30_ADDR, buf, 6, 100);
// 解析数据
raw_hum = (buf[0] << 8) | buf[1];
raw_temp = (buf[3] << 8) | buf[4];
// 转换为实际值
*hum = 100.0f * raw_hum / 65535.0f;
*temp = 175.0f * raw_temp / 65535.0f - 45.0f;
return 0;
}
注意:转换公式是数据手册里给的。我建议你直接复制手册里的公式,别自己推导,容易出错。
4.5 驱动代码实现——DHT22
DHT22的驱动就麻烦一些,因为要精确控制时序。我一般用GPIO模拟,配合定时器。
// DHT22驱动代码片段
#define DHT22_GPIO_PORT GPIOB
#define DHT22_GPIO_PIN GPIO_PIN_0
uint8_t DHT22_ReadTempHum(float *temp, float *hum) {
uint8_t data[5] = {0};
uint8_t i, j;
// 主机发送起始信号
HAL_GPIO_WritePin(DHT22_GPIO_PORT, DHT22_GPIO_PIN, GPIO_PIN_RESET);
HAL_Delay(20); // 至少18ms
HAL_GPIO_WritePin(DHT22_GPIO_PORT, DHT22_GPIO_PIN, GPIO_PIN_SET);
// 等待DHT22响应
delay_us(40);
if (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_RESET) {
// 等待80μs低电平结束
while (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_RESET);
// 等待80μs高电平结束
while (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_SET);
// 读取40位数据
for (j = 0; j < 5; j++) {
for (i = 0; i < 8; i++) {
// 等待低电平结束
while (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_RESET);
delay_us(40);
if (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_SET) {
data[j] |= (1 << (7 - i));
}
// 等待高电平结束
while (HAL_GPIO_ReadPin(DHT22_GPIO_PORT, DHT22_GPIO_PIN) == GPIO_PIN_SET);
}
}
// 校验
uint8_t checksum = data[0] + data[1] + data[2] + data[3];
if (checksum == data[4]) {
*hum = ((data[0] << 8) | data[1]) * 0.1f;
*temp = ((data[2] << 8) | data[3]) * 0.1f;
return 0;
}
}
return 1; // 读取失败
}
避坑指南:我曾经在DHT22的时序上栽过跟头。那个40μs的延时,用普通的for循环延时根本不准。一定要用定时器或者SysTick来精确延时。否则,数据读出来全是0xFF。
4.6 调试技巧与常见问题
写完了驱动,怎么调试?我分享几个经验:
- 先读ID寄存器:SHT30有个状态寄存器,读一下看看能不能正常通信。能读到0xFFFF以外的值,说明I2C通了。
- 用逻辑分析仪:别靠猜。把SCL和SDA挂到逻辑分析仪上,看看波形对不对。我每次调试I2C设备,第一件事就是抓波形。
- 注意上拉电阻:I2C总线需要上拉电阻,一般是4.7kΩ。没有上拉电阻,通信会时好时坏。
- DHT22的采样间隔:DHT22要求两次读取间隔至少2秒。读得太快,传感器会罢工。
我的经验:调试传感器驱动,最忌讳的就是「我觉得没问题」。一定要用工具验证。逻辑分析仪、示波器,该上就上。省下来的时间,够你喝好几杯咖啡了。
4.7 小结
这节课我们讲了SHT30和DHT22的驱动开发。从数据手册解读,到I2C和单总线时序分析,再到代码实现。说白了,传感器驱动开发就是三个步骤:看懂手册、写对时序、做好校验。
下节课,我们会讲气压传感器和风速传感器的驱动。到时候你会发现,有了这节课的基础,后面的内容就轻松多了。
好,今天就到这里。有什么问题,咱们在群里聊。