3、传感器数据采集基础:GPIO与I2C协议、读取温湿度传感器(DHT22)、数据格式化与时间戳标记
好,咱们进入第三章。这一章我打算带大家真正把手弄脏——去跟硬件打交道。你想想看,物联网终端如果连传感器数据都读不出来,那还叫啥智能设备?
我个人习惯,每次开始一个新项目,第一件事就是先搞定传感器。为啥?因为数据是上层应用的命根子。今天咱们就拿最常见的温湿度传感器 DHT22 开刀,把 GPIO、I2C 这些底层通信协议彻底搞明白。
3.1 GPIO 基础:管脚不是你想用,想用就能用
GPIO,全称 General Purpose Input Output,通用输入输出口。说白了,就是芯片上那些可以编程控制的引脚。
我在项目中遇到过不少新手,上来就把传感器往 GPIO 上一插,然后代码跑不通。嗯,这里要注意:GPIO 有几种工作模式,你得先搞清楚。
| 模式 | 说明 | 典型用途 |
|---|---|---|
| 推挽输出 | 可以输出高电平或低电平 | 控制 LED、继电器 |
| 开漏输出 | 只能输出低电平,高电平靠上拉 | I2C 总线、多设备共享线 |
| 浮空输入 | 引脚电平由外部决定 | 读取按键状态 |
| 上拉/下拉输入 | 内部有电阻拉高或拉低 | 防抖、默认电平确定 |
3.2 I2C 协议:两根线搞定一切
I2C 协议,Inter-Integrated Circuit,集成电路间通信。你想想看,只用两根线——SCL(时钟线)和 SDA(数据线)——就能挂载几十个设备,是不是很神奇?
其实原理不复杂。SCL 负责同步时钟,SDA 负责传输数据。每次通信开始,主设备会发一个起始信号,然后带上从设备的地址。从设备收到自己的地址后,回复一个 ACK(应答信号)。
我建议你记住这个流程:
- 起始条件:SCL 高电平时,SDA 从高变低
- 发送地址:7 位或 10 位地址 + 读写位
- 等待应答:从设备拉低 SDA 表示收到
- 数据传输:每 8 位数据后跟一个应答位
- 停止条件:SCL 高电平时,SDA 从低变高
核心要点:I2C 是半双工通信,同一时刻只能一个方向传数据。但好处是节省引脚,两根线能带一堆传感器。
3.3 读取 DHT22:单总线协议的倔强
DHT22 这个传感器有点特别。它不用 I2C,也不用 SPI,而是用自己的一套单总线协议。说白了,就是一根数据线既当爹又当妈,既要传时钟又要传数据。
为什么会这样?因为便宜啊!少一根线,成本就降一点。但代价是时序要求非常严格,差几微秒就读不出来。
DHT22 的数据格式是这样的:
- 40 位数据:16 位湿度 + 16 位温度 + 8 位校验和
- 湿度:实际值 = 原始值 / 10,比如 512 就是 51.2%
- 温度:实际值 = 原始值 / 10,比如 256 就是 25.6°C
- 校验和:前 4 个字节相加,取低 8 位,等于第 5 个字节
读取时序大致分三步:
- 主机拉低总线至少 18ms,然后释放
- DHT22 拉低 80μs 作为响应,再拉高 80μs 准备发送
- 逐位读取 40 位数据,每位开始是 50μs 低电平,然后高电平长度决定 0 或 1
💡 我的经验:DHT22 的时序对微秒级延迟很敏感。在 FreeRTOS 或 Linux 下,建议关掉中断再读,或者用硬件定时器精确控制。我曾经在树莓派上用软件延时读 DHT22,结果十次有八次超时,后来改用 wiringPi 的微秒延时函数才稳定。
3.4 代码实战:读取 DHT22 并格式化数据
好,理论说完了,咱们直接上代码。下面是一个在 ESP32 上用 Arduino 框架读取 DHT22 的例子:
#include <DHT.h>
#define DHTPIN 4 // GPIO4 接数据线
#define DHTTYPE DHT22 // 传感器型号
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
Serial.println("DHT22 测试开始");
dht.begin();
}
void loop() {
// 读取温湿度
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
// 检查读取是否成功
if (isnan(humidity) || isnan(temperature)) {
Serial.println("读取失败!检查接线");
return;
}
// 数据格式化
char buffer[128];
snprintf(buffer, sizeof(buffer),
"时间戳:%lu,温度:%.1f°C,湿度:%.1f%%",
millis(), temperature, humidity);
Serial.println(buffer);
delay(2000); // 每 2 秒读一次
}
这段代码做了三件事:初始化传感器、读取数据、格式化输出。注意那个 snprintf,我习惯用它来拼接字符串,比 String 类更省内存,也更安全。
3.5 时间戳标记:让数据有「时间感」
光有温湿度数据还不够。你想想看,如果不知道数据是什么时候采集的,那分析起来就毫无意义。所以,时间戳是必须的。
在嵌入式系统里,时间戳通常有两种来源:
| 来源 | 精度 | 适用场景 |
|---|---|---|
| 系统滴答(millis) | 毫秒级 | 本地记录、短时间分析 |
| RTC 实时时钟 | 秒级 | 需要绝对时间、断网场景 |
| NTP 网络时间 | 毫秒级 | 联网设备、云端同步 |
我个人习惯,在本地调试时用 millis() 就够了。但产品上线后,一定要用 RTC 或 NTP。为啥?因为设备重启后 millis() 会归零,你根本不知道数据是今天还是昨天的。
数据格式化建议:我推荐用 JSON 格式,比如:{"ts": 1700000000, "temp": 25.6, "hum": 51.2}。这样后续传到云端,解析起来非常方便。
3.6 避坑指南:我踩过的那些雷
做传感器采集这么多年,我踩过的坑真不少。挑几个典型的说说:
- 上拉电阻忘加:DHT22 的数据线需要 4.7kΩ 上拉到 VCC。我第一次用的时候没加,结果数据全是 0。后来查手册才发现,单总线协议要求外部上拉。
- 读取间隔太短:DHT22 的采样周期是 2 秒。如果你 1 秒读一次,它根本来不及更新数据,返回的全是旧值。
- 校验和忽略:有些同学图省事,不校验校验和。结果数据偶尔跳变,还以为是传感器坏了。其实只要加上校验,就能过滤掉大部分错误。
- 时序被中断打乱:在 RTOS 环境下,如果读取过程中被高优先级任务打断,时序就乱了。我建议在读取时关中断,或者用 DMA 方式。
3.7 小结:从数据到信息
这一章我们干了三件事:搞懂了 GPIO 和 I2C 协议、用代码读取了 DHT22、给数据加上了时间戳。你想想看,从一根根管脚到一串串有意义的温湿度数据,这个过程其实就是物联网终端最核心的能力——把物理世界的信号,变成数字世界的信息。
下一章,咱们会把这些数据通过网络发出去,让终端真正「联网」。到时候你会发现,前面这些基础打得越扎实,后面就越顺手。
嗯,今天就到这儿。去动手试试吧,别光看代码,把传感器接上,跑一遍,你会有不一样的体会。