2. 传感器数据采集基础:温度传感器(DS18B20)、湿度传感器(DHT22)、光照传感器(BH1750)的原理与驱动
各位同学,欢迎来到第二章。这一章我们直接上手干活——聊聊嵌入式温室里最常用的三个传感器:DS18B20、DHT22 和 BH1750。
说实话,传感器驱动这块,我早年踩过不少坑。记得第一次用 DS18B20 的时候,时序没调好,读出来的温度忽高忽低,差点把一整个温室的幼苗给坑了。嗯,从那以后,我对时序就特别敏感。
好,咱们一个一个来。
2.1 温度传感器 DS18B20
DS18B20 是个好东西。它用单总线(1-Wire)通信,说白了就是一根数据线既当电源又传数据。你想想看,省引脚啊!
核心原理:
- 测温范围:-55°C 到 +125°C,精度 ±0.5°C(-10°C 到 +85°C 范围内)
- 分辨率可配置:9 到 12 位,默认 12 位
- 每个芯片有唯一的 64 位 ROM 编码,可以挂多个在一条总线上
重要提醒:DS18B20 的转换时间取决于分辨率。12 位分辨率下,最大转换时间 750ms。我建议你在代码里别急着读,等够时间再说。
驱动要点:
单总线协议其实不复杂,但时序要求严格。我习惯用 GPIO 模拟时序,因为很多 MCU 没有硬件 1-Wire 控制器。
核心步骤就三步:
- 初始化:主机拉低总线 480μs 以上,然后释放,等待 DS18B20 拉低 60-240μs 应答
- ROM 命令:跳过 ROM(0xCC)或者匹配 ROM(0x55)
- 功能命令:启动温度转换(0x44),然后读暂存器(0xBE)
// DS18B20 读取温度示例(简化版)
uint8_t ds18b20_reset(void) {
uint8_t presence;
GPIO_LOW; // 拉低总线
delay_us(480); // 保持 480μs
GPIO_HIGH; // 释放总线
delay_us(60); // 等待 60μs
presence = GPIO_READ; // 读应答信号
delay_us(420); // 等待剩余时间
return presence; // 0 表示有设备应答
}
float ds18b20_read_temp(void) {
uint8_t temp_l, temp_h;
int16_t raw_temp;
ds18b20_reset();
ds18b20_write_byte(0xCC); // 跳过 ROM
ds18b20_write_byte(0x44); // 启动转换
delay_ms(750); // 等待转换完成
ds18b20_reset();
ds18b20_write_byte(0xCC); // 跳过 ROM
ds18b20_write_byte(0xBE); // 读暂存器
temp_l = ds18b20_read_byte();
temp_h = ds18b20_read_byte();
raw_temp = (temp_h << 8) | temp_l;
return raw_temp * 0.0625; // 12 位分辨率下,每个 LSB 代表 0.0625°C
}
我的经验:实际项目中,我一般会在启动转换后加一个超时机制。因为有时候传感器会掉线,死等 750ms 会卡死整个系统。加个 1 秒超时,读不到就重试,稳得很。
2.2 温湿度传感器 DHT22
DHT22(也叫 AM2302)是温室里测温湿度的主力。它比 DHT11 精度高不少,价格也贵一点,但我觉得值。
核心参数:
| 参数 | DHT22 | DHT11(对比) |
|---|---|---|
| 温度精度 | ±0.5°C | ±2°C |
| 湿度精度 | ±2% RH | ±5% RH |
| 采样周期 | 2 秒 | 1 秒 |
| 数据格式 | 40 位(16 位湿度 + 16 位温度 + 8 位校验) | 40 位 |
通信协议:
DHT22 也是单总线,但时序和 DS18B20 不一样。它由主机发起,传感器响应后发送 40 位数据。
我当年第一次调 DHT22 的时候,发现读出来的湿度一直是 0。查了半天,原来是主机拉低起始信号的时间不够。DHT22 要求至少 1ms,我给了 800μs,就差那么一点。
// DHT22 读取数据示例
uint8_t dht22_read_data(float *temp, float *hum) {
uint8_t data[5] = {0};
uint8_t i, j;
// 主机发送起始信号
GPIO_LOW;
delay_ms(2); // 拉低至少 1ms,我习惯给 2ms
GPIO_HIGH;
delay_us(30); // 拉高 20-40μs
// 等待传感器响应
if (GPIO_READ == 0) {
while (GPIO_READ == 0); // 等待拉高
while (GPIO_READ == 1); // 等待拉低
} else {
return 0; // 无响应
}
// 读取 40 位数据
for (j = 0; j < 5; j++) {
for (i = 0; i < 8; i++) {
while (GPIO_READ == 0); // 等待数据开始
delay_us(40); // 采样点
if (GPIO_READ == 1) {
data[j] |= (1 << (7 - i));
}
while (GPIO_READ == 1); // 等待数据结束
}
}
// 校验
if ((data[0] + data[1] + data[2] + data[3]) != data[4]) {
return 0; // 校验失败
}
*hum = ((data[0] << 8) | data[1]) / 10.0;
*temp = ((data[2] << 8) | data[3]) / 10.0;
return 1;
}
注意:DHT22 的采样周期是 2 秒。你如果读得太频繁,它会不理你。我曾经在循环里每 100ms 读一次,结果传感器直接罢工了。后来加了 2 秒的间隔,一切正常。
2.3 光照传感器 BH1750
BH1750 是数字光照传感器,用 I2C 接口。它直接输出 lux 值,不用你自己算,省心。
为什么选它?
- 测量范围:1 - 65535 lux,覆盖温室从阴天到强光
- 分辨率可调:高分辨率模式(1 lux)、低分辨率模式(4 lux)
- 内置 ADC,直接输出数字量,抗干扰能力强
I2C 地址:
BH1750 的 I2C 地址由 ADDR 引脚决定:
- ADDR 接 GND:地址 0x23
- ADDR 接 VCC:地址 0x5C
我习惯把 ADDR 接 GND,因为 0x23 好记。你想想看,调试的时候少查一次手册,就少一次出错的机会。
驱动流程:
- 上电后发送上电命令(0x01)
- 设置测量模式(如连续高分辨率模式 0x10)
- 等待测量完成(高分辨率模式约 120ms)
- 读取 2 字节数据,计算 lux = (high << 8 | low) / 1.2
// BH1750 读取光照示例(I2C 通信)
#define BH1750_ADDR 0x23
#define BH1750_POWER_ON 0x01
#define BH1750_MODE_H 0x10 // 连续高分辨率模式
float bh1750_read_lux(void) {
uint8_t buf[2];
uint16_t raw;
// 上电
i2c_write(BH1750_ADDR, BH1750_POWER_ON);
delay_ms(10);
// 设置模式
i2c_write(BH1750_ADDR, BH1750_MODE_H);
delay_ms(180); // 等待转换完成,多给点余量
// 读取数据
i2c_read(BH1750_ADDR, buf, 2);
raw = (buf[0] << 8) | buf[1];
return raw / 1.2; // 高分辨率模式,除以 1.2 得到 lux
}
避坑指南:BH1750 有个小脾气——它怕光突变。如果你从黑暗环境突然拿到强光下,第一次读到的值可能偏小。我一般会连续读两次,取第二次的值,或者做一次简单的低通滤波。
2.4 三种传感器的选型对比
最后,我给大家整理一个对比表。选型的时候,对着这个表看,基本不会出错。
| 特性 | DS18B20 | DHT22 | BH1750 |
|---|---|---|---|
| 测量对象 | 温度 | 温度 + 湿度 | 光照强度 |
| 通信接口 | 1-Wire | 1-Wire(自定义时序) | I2C |
| 精度 | ±0.5°C | ±0.5°C / ±2% RH | ±20% 典型 |
| 采样周期 | 750ms(12位) | 2s | 120ms(高分辨率) |
| 典型应用 | 多点温度监测 | 温湿度一体监测 | 光照强度监测 |
| 我的推荐 | 需要多个温度点时首选 | 温室环境监测标配 | 数字光照,简单可靠 |
好了,这一章的内容就到这里。三种传感器,三种接口,各有各的脾气。你只要记住:DS18B20 重时序,DHT22 重间隔,BH1750 重滤波。把这三点拿捏住,驱动就稳了。
下一章,我们聊聊如何把这些传感器整合到一起,做一个真正的温室数据采集系统。到时候,我会分享一个我实际项目中用过的架构,保证实用。
课后思考:如果让你用这三种传感器设计一个温室环境监测节点,你会怎么安排它们的采样顺序?为什么?