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 了。下一章,咱们要把这些数据用起来——控制净化器自动开关。说实话,那才是真正有意思的部分。