3、读取粉尘传感器数据:PMS5003数据手册解读、UART通信初始化、数据帧解析(32字节协议)、校验和计算、串口打印PM2.5与PM10数值
好,咱们进入第三个实战环节。前面两章把硬件搭好了,开发环境也配齐了。现在该让传感器真正开口说话了。
这一章的核心,就是搞定 PMS5003 这个粉尘传感器。说实话,我第一次用这颗传感器时,也被它那 32 字节的数据帧搞得有点懵。但摸清套路后,你会发现它其实很「老实」——数据格式固定,校验机制清晰,几乎没有歧义。
3.1 PMS5003 数据手册解读
拿到一个新传感器,我的习惯是先看数据手册里的「输出数据」部分。PMS5003 通过 UART 输出,每秒主动发送一帧数据。帧长固定 32 字节,结构如下:
| 字节索引 | 内容 | 说明 |
|---|---|---|
| 0 | 0x42 | 起始字节 1(固定) |
| 1 | 0x4D | 起始字节 2(固定) |
| 2-3 | 帧长度 | 高字节在前,通常为 0x001C(28) |
| 4-5 | PM1.0(CF=1) | 标准颗粒物浓度,单位 μg/m³ |
| 6-7 | PM2.5(CF=1) | 我们最关心的数据之一 |
| 8-9 | PM10(CF=1) | 同样重要 |
| 10-11 | PM1.0(大气环境) | 大气环境下的浓度值 |
| 12-13 | PM2.5(大气环境) | 通常用这个值 |
| 14-15 | PM10(大气环境) | 同上 |
| ... | ... | 中间还有一些保留字节 |
| 30-31 | 校验和 | 低字节在前 |
你可能会问:为什么有 CF=1 和大气环境两套数据?我个人理解是,CF=1 是工厂校准条件下的值,而大气环境值更贴近实际使用场景。我在项目中一般直接取大气环境那组数据。
关键点:起始字节必须是 0x42 0x4D,这是「M」和「B」的 ASCII 码。如果收到别的开头,直接丢掉这一帧。
3.2 UART 通信初始化
ESP32 的 UART 初始化其实不复杂。但要注意一点:PMS5003 的默认波特率是 9600,8 位数据位,1 位停止位,无校验。别想当然用 115200,我第一次就踩了这个坑。
下面是我常用的初始化代码:
#include <HardwareSerial.h>
// 定义 UART2 引脚
#define RX_PIN 16
#define TX_PIN 17
HardwareSerial PMS_Serial(2); // 使用 UART2
void setup() {
Serial.begin(115200); // 调试串口
// PMS5003 串口初始化
PMS_Serial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
Serial.println("PMS5003 初始化完成");
}
嗯,这里有个小细节:PMS5003 的 TX 接 ESP32 的 RX,RX 接 TX。别接反了。我见过有人把线焊反了,折腾半天读不到数据。
3.3 数据帧解析:32 字节协议
数据来了,怎么解析?说白了就是按字节读,按协议拆。
我的做法是:先等起始字节 0x42 0x4D,然后读满 32 字节,再做校验。如果校验通过,就提取 PM2.5 和 PM10 的值。
bool readPMSData(uint16_t &pm25, uint16_t &pm10) {
uint8_t buffer[32];
uint8_t index = 0;
// 等待起始字节
while (PMS_Serial.available()) {
uint8_t b = PMS_Serial.read();
if (b == 0x42) {
buffer[0] = b;
// 读下一个字节,应该是 0x4D
if (PMS_Serial.available()) {
buffer[1] = PMS_Serial.read();
if (buffer[1] == 0x4D) {
index = 2;
break;
}
}
}
}
// 读满 32 字节
while (index < 32) {
if (PMS_Serial.available()) {
buffer[index++] = PMS_Serial.read();
}
}
// 校验和计算
uint16_t sum = 0;
for (int i = 0; i < 30; i++) {
sum += buffer[i];
}
uint16_t checksum = (buffer[30] << 8) | buffer[31];
if (sum != checksum) {
Serial.println("校验和错误,丢弃此帧");
return false;
}
// 提取 PM2.5 和 PM10(大气环境值,字节 12-15)
pm25 = (buffer[12] << 8) | buffer[13];
pm10 = (buffer[14] << 8) | buffer[15];
return true;
}
避坑指南:我曾经遇到过传感器刚上电时,前几帧数据全是 0。后来查手册才知道,PMS5003 需要约 30 秒的稳定时间。建议上电后先等一会儿再开始读数据。
3.4 校验和计算
校验和是 PMS5003 数据帧的「防伪标识」。计算方法很简单:
- 把前 30 个字节的值累加起来
- 得到一个 16 位的和
- 与帧尾的校验和(字节 30-31)比较
如果相等,说明数据没被干扰。如果不相等,直接扔掉。
你想想看,空气净化器要是读到错误数据,可能会乱转。所以校验这步不能省。
3.5 串口打印 PM2.5 与 PM10 数值
最后一步,把数据打印出来看看。我习惯用逗号分隔,方便后面用串口绘图工具观察趋势。
void loop() {
uint16_t pm25 = 0, pm10 = 0;
if (readPMSData(pm25, pm10)) {
Serial.print("PM2.5: ");
Serial.print(pm25);
Serial.print(" μg/m³, PM10: ");
Serial.print(pm10);
Serial.println(" μg/m³");
}
delay(1000); // 每秒读一次
}
打开串口监视器,你应该能看到类似这样的输出:
PM2.5: 35 μg/m³, PM10: 68 μg/m³
PM2.5: 33 μg/m³, PM10: 65 μg/m³
PM2.5: 36 μg/m³, PM10: 70 μg/m³
注意:如果串口一直输出 0,或者数据跳动异常,先检查接线和供电。PMS5003 工作时需要 5V 供电,电流约 100mA。用 ESP32 的 3.3V 引脚供电是带不动的。
好了,到这一步,你已经能从 PMS5003 里读出 PM2.5 和 PM10 了。下一章,咱们要把这些数据用起来——控制净化器自动开关。说实话,那才是真正有意思的部分。