4、传感器数据采集:DHT11/DHT22 温湿度传感器、BH1750 光照传感器、ADC 模拟量读取、传感器数据滤波算法
做智能家居,传感器就是系统的眼睛和耳朵。没有准确的数据,再漂亮的自动化逻辑都是空中楼阁。这一章,我们来聊聊几种最常用的传感器,以及怎么让它们的数据变得可靠。
4.1 DHT11 与 DHT22:温湿度传感器的选择与实战
温湿度传感器里,DHT11 和 DHT22 是绝对的「入门级明星」。我最早做环境监测项目时,用的就是 DHT11,便宜、简单,但坑也不少。
4.1.1 选型对比:别买错了
这两兄弟长得几乎一样,引脚定义也相同,但性能差距挺大。我个人习惯直接看这张表:
| 参数 | DHT11 | DHT22 (AM2302) |
|---|---|---|
| 温度范围 | 0~50°C | -40~80°C |
| 温度精度 | ±2°C | ±0.5°C |
| 湿度范围 | 20~90% RH | 0~100% RH |
| 湿度精度 | ±5% RH | ±2% RH |
| 采样周期 | 1 秒 | 2 秒 |
| 价格 | 约 3-5 元 | 约 15-25 元 |
说白了,DHT11 只能告诉你「大概热不热」,DHT22 才能告诉你「精确到几度」。做室内恒温控制,我建议直接上 DHT22,省得后期数据不准来回折腾。
4.1.2 接线与代码:单总线通信
DHT 系列用的是单总线协议,一根数据线搞定通信。接线很简单:
- VCC → 3.3V
- GND → GND
- DATA → GPIO4(示例)
代码方面,我推荐用 Adafruit 的库,封装得很好:
#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT22 // 如果是 DHT11,改成 DHT11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
delay(2000); // DHT22 采样周期至少 2 秒
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("读取失败!检查接线");
return;
}
Serial.print("湿度: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("温度: ");
Serial.print(t);
Serial.println(" °C");
}
4.2 BH1750 光照传感器:数字化的光感方案
光照传感器里,BH1750 是我的首选。为什么?因为它直接输出数字量,精度高,而且不受光源角度影响。你想想看,用光敏电阻还得自己算公式、校准,多麻烦。
4.2.1 硬件连接与 I2C 地址
BH1750 走 I2C 协议,接线就四根线:
- VCC → 3.3V
- GND → GND
- SCL → GPIO22 (ESP32 默认)
- SDA → GPIO21 (ESP32 默认)
它的 I2C 地址由 ADDR 引脚决定:
| ADDR 电平 | I2C 地址 |
|---|---|
| GND (低电平) | 0x23 |
| VCC (高电平) | 0x5C |
嗯,这里要注意:如果你同时挂多个 BH1750,每个必须设置不同的 ADDR 电平,否则地址冲突。
4.2.2 读取光照强度
用库就两行代码的事:
#include <BH1750.h>
#include <Wire.h>
BH1750 lightMeter;
void setup() {
Serial.begin(115200);
Wire.begin();
lightMeter.begin();
}
void loop() {
float lux = lightMeter.readLightLevel();
Serial.print("光照强度: ");
Serial.print(lux);
Serial.println(" lx");
delay(500);
}
4.3 ADC 模拟量读取:ESP32 的「模拟耳朵」
很多传感器输出的是模拟电压,比如土壤湿度传感器、MQ 系列气体传感器。这时候就得靠 ADC(模数转换器)了。ESP32 内置了两个 12 位 ADC,共 18 个通道。
4.3.1 基本读取方法
12 位 ADC 意味着返回值范围是 0~4095,对应 0~3.3V。代码很简单:
#define ADC_PIN 34 // ADC1 通道 6
void setup() {
Serial.begin(115200);
analogReadResolution(12); // 设置分辨率 12 位
}
void loop() {
int raw = analogRead(ADC_PIN);
float voltage = raw * (3.3 / 4095.0);
Serial.print("原始值: ");
Serial.print(raw);
Serial.print(" 电压: ");
Serial.print(voltage);
Serial.println(" V");
delay(100);
}
4.3.2 电压衰减与量程
默认 ADC 量程是 0~3.3V。如果你要测更高电压,得用分压电阻。比如测 12V 电池,用 10kΩ 和 3.3kΩ 分压,把 12V 降到约 3V。公式很简单:
Vout = Vin * R2 / (R1 + R2)
选电阻时注意功率,别冒烟了。
4.4 传感器数据滤波算法:让数据「稳」下来
原始传感器数据往往噪声很大。你想想看,一个光照传感器,读数在 300 lx 到 350 lx 之间乱跳,这自动化怎么触发?所以滤波是必须的。
4.4.1 限幅滤波:剔除野值
最简单的方法。设定一个最大允许偏差,如果当前值和上次值差太多,就丢掉。
#define MAX_GAP 50
int lastValue = 0;
int limitFilter(int newValue) {
if (abs(newValue - lastValue) <= MAX_GAP) {
lastValue = newValue;
return newValue;
} else {
return lastValue; // 超出范围,用旧值
}
}
我在做烟雾传感器时用过这招,有效滤掉了偶尔的尖峰干扰。
4.4.2 中位值滤波:抗脉冲干扰
连续采样 N 次,排序后取中间值。对脉冲干扰效果极好。
#define N 5
int medianFilter() {
int buf[N];
for (int i = 0; i < N; i++) {
buf[i] = analogRead(ADC_PIN);
delay(10);
}
// 冒泡排序
for (int i = 0; i < N - 1; i++) {
for (int j = 0; j < N - i - 1; j++) {
if (buf[j] > buf[j + 1]) {
int temp = buf[j];
buf[j] = buf[j + 1];
buf[j + 1] = temp;
}
}
}
return buf[N / 2];
}
4.4.3 滑动平均滤波:平滑曲线
维护一个固定长度的队列,每次取平均值。这是我最常用的方法,适合温湿度这种变化缓慢的信号。
#define WINDOW_SIZE 10
int buffer[WINDOW_SIZE];
int index = 0;
int sum = 0;
int movingAverageFilter(int newValue) {
sum -= buffer[index];
buffer[index] = newValue;
sum += buffer[index];
index = (index + 1) % WINDOW_SIZE;
return sum / WINDOW_SIZE;
}
说白了,这就是一个「最近 N 次读数的平均」。窗口越大越平滑,但滞后也越大。做恒温控制时,我一般用 5~10 的窗口。
4.4.4 一阶低通滤波:轻量级平滑
不需要数组,只用一个公式:结果 = 上次结果 * (1 - α) + 当前值 * α。α 越小越平滑。
#define ALPHA 0.3
int lastFiltered = 0;
int lowPassFilter(int newValue) {
lastFiltered = (int)(lastFiltered * (1 - ALPHA) + newValue * ALPHA);
return lastFiltered;
}
这个算法内存占用极小,适合资源受限的场景。我曾在 ESP01 上用这个,效果不错。
4.5 综合实战:一个可靠的环境监测节点
把上面所有东西串起来。一个典型的做法是:
- 用 DHT22 读温湿度
- 用 BH1750 读光照
- 用 ADC 读一个模拟土壤湿度传感器
- 对每个数据流分别应用滑动平均滤波
- 每 5 秒上报一次到 HomeAssistant
嗯,这里要注意:不同传感器的采样周期不同。DHT22 最快 2 秒一次,BH1750 可以 100ms 一次,ADC 可以更快。我习惯用定时器统一调度,而不是在 loop 里用 delay 死等。
好了,这一章的内容就到这里。下一章我们聊聊怎么把这些数据优雅地传给 HomeAssistant,让它们真正派上用场。