做课程设计或者DIY一台家用空气质量监测小盒子,红外 PM2.5 传感器 + STM32 是非常常见的一套组合。这类方案的核心器件大多是夏普 GP2Y1010 系列,十几块钱一块成品模块,引出电源、地、模拟输出和 LED 控制四根线,看起来把 STM32 的 ADC 引脚接上就能读数。但等你真正开始调,就会发现读数跳动、数据偏大、跟参考仪器对不上这类问题一个接一个。这篇文章按我实际调通一块 STM32F103 + GP2Y1010 模块的完整过程来写,从红外散射原理、硬件接线、ADC 采样时序,到滤波换算、校准方法和最后踩过的几个坑,尽量把每一步为什么这么做讲清楚,适合正在做课程设计,或者想低成本攒一台 PM2.5 监测仪的同学直接参考。
1. 红外散射测尘的基本盘:GP2Y1010 这类传感器是怎么输出浓度的
1.1 红外散射法的物理基础
红外 PM2.5 传感器的测量原理是光散射。传感器内部有一个很小的检测腔,红外发光二极管朝腔内发射一束光,光电二极管则放在一个特定角度上,接收被颗粒物散射回来的光。空气中的颗粒物经过光路时,散射光的强度会随颗粒浓度、粒径和折射率变化,于是通过测量接收管的光电流就能反推出空气中的粉尘浓度。
这个“特定角度”很关键。GP2Y1010 的发射管和接收管是错开布置的,中间还有挡光结构,干净空气时红外光不会直接照射到接收管,输出是一个较低的基线电压。一旦腔体内有颗粒物,散射光才会进入接收管,输出电压升高。浓度越高,落在接收管上的散射光越强,电压越高。整个过程就是一台微型浊度仪,只是测的对象从液体换成了空气。
用生活化一点的说法:就像手电筒在黑屋子里照一束光,你站在侧面看光柱,屋里灰尘越多,光柱越明显。传感器里那束红外光就是手电筒,光电二极管就是站在侧面的你。这个比喻能帮你理解为什么传感器“怕可见光干扰”——如果外界强光直接照进检测腔,相当于屋子里开了大灯,手电筒的光柱再亮也淹没在背景光里了。
1.2 常见型号与引脚定义
GP2Y1010AU0F 是这颗芯片最经典的型号,后来还有 GP2Y1014AU0F,规格基本一致。芯片本体带一个可开合的外壳,里面就是检测腔体,外观是一个带进气口的小方块。裸芯片一共 6 个引脚,含义如下:
| 引脚 | 名称 | 说明 |
|---|---|---|
| 1 | VLED | LED 电源,5V |
| 2 | LED-GND | LED 地 |
| 3 | LED | LED 控制端,低电平点亮 |
| 4 | S-GND | 系统地 |
| 5 | Vo | 模拟电压输出 |
| 6 | Vcc | 系统电源,5V |
如果你买的是裸芯片,必须用一个三极管或者 MOSFET 去切换 VLED 的通断,LED 端接 GPIO 控制;而市面上绝大多数成品模块已经把开关管做进去了,引出 VCC、GND、AOUT、LED 四个脚,直接用 STM32 的 GPIO 控制 LED 引脚即可,不需要外加驱动。
| 模块引脚 | 对应芯片引脚 | 接 STM32 |
|---|---|---|
| VCC | 6 Vcc + 1 VLED | 5V |
| GND | 2 LED-GND + 4 S-GND | GND |
| AOUT | 5 Vo | PA0(ADC 输入) |
| LED | 3 LED(经板载三极管) | PA1(GPIO 输出) |
买模块之前一定要看卖家的引脚定义和电平要求,有些模块把 LED 控制端做成了高电平有效,跟数据手册正好相反,接反了表现就是数据完全反向或者根本没有响应。
1.3 为什么红外传感器只能算“浓度计”,不是真正的 PM2.5 计数器
这里必须先泼一盆冷水:GP2Y1010 测的不是严格意义上的“粒径小于 2.5 微米的颗粒物浓度”。它的散射信号是检测腔内所有颗粒的综合响应,直径 0.3µm 到 10µm 的粒子都会贡献信号,而且大颗粒的散射强度远大于小颗粒。也就是说,它本质上是一个总粉尘浓度指示器,拿来当 PM2.5 的“近似值”可以,拿来当科研级 PM2.5 数据源不行。
后来的激光 PM2.5 传感器,比如 PMS5003、SDS011,内部有激光管和光电探测器阵列,能逐个粒子数数,按粒径分桶再换算质量浓度,那才是真正的 PM2.5 传感器。但红外方案便宜、模块化、功耗低,在很多课程设计和低成本空气质量监测项目里够用。后面要讲的换算公式,本质上都是把散射电压线性映射到 PM2.5 浓度标称值,这个映射必须经过校准才有一点可信度。所以这篇文章里的“浓度”两个字,你心里要有数:它是在特定校准条件下对 PM2.5 的一种近似。
2. 硬件连线与供电:先排除五个百分百会踩的坑
2.1 引脚连接与电平匹配
典型的模块接线就是把四根线按上面的表格接好。这个环节有两个容易翻车的点,值得展开说。
第一个是 AOUT 的输出电压范围。5V 供电下 GP2Y1010 在干净空气时输出约 0.9V,粉尘浓度到 0.5mg/m³ 时可以到 3.4V 左右。室内正常环境一般到不了这么极端,所以很多人直接接 STM32 的 3.3V ADC 用,也确实能跑。但遇到烟尘环境、传感器个体差异或者模块放得离污染源太近,输出超过 3.3V 会导致 ADC 读数被削顶,甚至逼近引脚绝对最大额定值。稳妥的做法是加分压电阻,把 Vo 经过两个电阻分压后再进 ADC。比如 10kΩ 串联加 10kΩ 对地,信号减半,然后换算浓度时把电压先乘回 2。这样虽然 ADC 的低端分辨率损失一半,但安全系数高很多。
第二个是 GPIO 的电平匹配。模块的 LED 控制端是低电平有效,PA1 用推挽输出拉低即可点亮红外 LED。这个引脚开关频率不高,STM32 的 3.3V 逻辑电平对模块来说完全够用。要避免的是把 LED 端直接接 5V 或者悬空——悬空状态下模块内部可能有上拉或下拉,导致 LED 常亮或常灭,数据完全错乱。
2.2 供电与去耦电容
GP2Y1010 的电源参数里有两个关键点:Vcc 和 VLED 都要 5V ± 0.5V,LED 脉冲电流峰值大约 320mA。320mA 是脉冲电流,周期 10ms 里只有 0.32ms 导通,平均电流并不大,但如果你的 5V 来自 STM32 板载 LDO 或者 USB 口,瞬间压降就会很明显。LED 亮度一旦波动,同一个浓度下输出信号就会跟着抖。
所以供电原则是:别跟电机、继电器、舵机共用电源,别用 USB 口直接怼。我给模块单独加了一个 5V 的 LDO,比如 ME6211 或者 AMS1117-5.0,输入用 7-12V 适配器,输出专门给传感器。在 Vcc 和 VLED 的引脚附近分别放 100µF 电解电容和 0.1µF 陶瓷电容。别小看这两个电容,脉冲电流靠的就是它们兜底。如果还想更讲究,可以在 VDDA 和 VSSA 之间加一个 10Ω 电阻串联 1µF 电容的 π 型滤波,把 ADC 参考电源和数字电源隔开。
2.3 采样时序:为什么必须在 LED 点亮 0.28ms 后读数
这是整个项目里最重要的一个“为什么”。红外 LED 不会瞬间把电流全部转换成光,光电二极管接收散射光后,输出电压也需要建立时间。GP2Y1010 数据手册推荐的脉冲时序是:周期 10ms,脉冲宽度 0.32ms,在 LED 点亮后约 0.28ms 处采样。用文字描述这段时间线:
- 0ms:LED 控制端拉低,LED 导通,红外光开始照射;
- 0ms 到 0.28ms:散射光逐渐建立,接收管输出电压快速上升;
- 0.28ms:输出基本稳定,此刻采样得到有效值;
- 0.32ms:LED 关闭,输出开始回落;
- 10ms:进入下一个周期。
如果你不按这个时序来,比如把 LED 常亮或者随机时刻采样,会发现两个问题。第一是采样太早,输出还在上升段,读数偏低,而且上升速率受环境光、传感器个体差异影响很大,数据的重复性很差。第二是采样太晚,LED 已经熄灭,输出开始回落,读数同样不稳定。把采样点卡在脉冲后段并稳定触发,是后面所有滤波算法能生效的前提。我见过不少人一上来就上卡尔曼滤波,结果数据还是飘,回头一看,采样点都在乱跳。
3. 数据链路设计:从 ADC 原始值到 μg/m³ 浓度
3.1 STM32 ADC 配置要点
如果用 STM32F103,ADC1 是 12 位,量化步长 3.3V/4096 ≈ 0.8mV。这个分辨率对 GP2Y1010 是够用的,毕竟传感器本身的信号噪声就不小。我这里用单通道单次转换,关键配置如下:
- 参考电压:VREF+ 接 VDDA,也就是 3.3V;
- 采样时间:给 ADC 设最大采样周期 239.5 周期。因为传感器输出内阻不低,如果你再加了分压电阻,源阻抗会进一步变大,采样时间太短会导致内部采样电容没充满,读数系统性偏高;
- 转换触发:先用软件触发,紧跟在我规定的 0.28ms 采样时刻点执行,逻辑简单,可读性好。
如果你用 CubeMX,配置 ADC1 的 IN0,不要开连续转换,用单次转换模式。每次读值时执行 HAL_ADC_Start + PollForConversion + HAL_ADC_Stop 的经典组合,稳定不踩坑。另外在 ADC 引脚到地之间放一个 100nF 陶瓷电容,能有效吸收传感器输出端的瞬态毛刺,也会让采样电压更稳定。注意这个电容和分压电阻之间有一个时间常数,RC 不能太大,否则电压建立速度会跟不上 10ms 的脉冲周期。
3.2 电压-浓度换算公式的前世今生
拿到 ADC 原始值后,第一步还原成电压:
V = ADC × 3.3 / 4096
如果是 5V 供电、未加分压电路,夏普数据手册给出的近似关系是:干净空气输出约 0.9V,灵敏度约 0.5V / (0.1mg/m³)。也就是说:
C(mg/m³) ≈ (V - 0.9) / 5
换算成微克每立方米:
C(µg/m³) ≈ 200 × (V - 0.9)
举个具体例子,假设读到的 ADC 原始值是 1200,那么:
V = 1200 × 3.3 / 4096 ≈ 0.967V
代入公式 C = 200 × (0.967 - 0.9) ≈ 13.4µg/m³。这个数在室内算合理。
但注意,这个公式里的 0.9V 是典型值,每一颗传感器的个体差异能达到 ±0.15V。如果你直接抄公式,碰到零点在 1.0V 的传感器,计算值就会是负数——很多人的数据出现大段 0,就是这个原因。数据手册标称的灵敏度也只是近似值,和真实颗粒物成分、粒径分布有关。
所以我把这个公式定位成“初次上电能出数”的临时换算,真正可用的读数必须做校准,具体做法在第 5 节。如果加了分压电阻,设分压系数为 K = R2 / (R1+R2),那就先反推传感器原始电压 V_sensor = V_adc / K,再代入公式。代码里我会放一个 VOLTAGE_DIVIDER_K 宏,方便你直接改。
这里还要提醒一下:网上很多代码写density = 0.17 × voltage - 0.1,那是有人对着自己那块传感器做过线性拟合后的经验公式,不是通用公式。直接抄会导致你的数据比别人高一大截或低一大截。最靠谱的做法是拿自己的传感器重新做两点标定。
3.3 滑动平均与去极值滤波
传感器原始读数在稳定环境里也会有波动,因为颗粒物在腔体内是随机经过的,瞬时散射强度天然有涨落。这是物理噪声,不是电路问题。常见处理是滑动平均:维护一个 N 点的队列,每来一个新样本,丢掉最旧的,取平均。
N 怎么选?我实测过,GP2Y1010 在室内稳定环境下,单次读数的标准差大约在 10% 到 15% 左右。取 N=10 能显著压低噪声,N=20 更平滑,但同时响应时间会被拉长。我的做法是 N=10,配合去极值:队列里剔除一个最大值和一个最小值再求平均,这样能干掉偶发的灰尘团引起的尖峰。
滑动平均是处理这类“随机噪声”性价比最高的手段。卡尔曼滤波在这个场景当然也能用,但参数调起来麻烦,对课程设计来说属于用力过猛。先把时序和滑动平均做好,读数的稳定性已经足够交差了。
4. 完整代码实现:基于 STM32F103 + HAL 的工程模板
先说清楚整套代码的思路:TIM3 产生 10ms 周期中断,每次中断进入采样状态机,DWT 提供 280µs 和 40µs 的微秒延时;ADC 单次转换读到原始值;主循环里用轮询拿浓度、打印串口。这个结构简单清晰,适合直接改到自己的工程里。
4.1 DWT 微秒延时与引脚初始化
HAL_Delay 的精度只有 1ms,给不了微秒级,所以用 Cortex-M3 内核的 DWT 计数器做微秒延时。初始化一次即可:
void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0; } void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000U); while ((DWT->CYCCNT - start) < ticks); }注意 SystemCoreClock 要和实际主频一致。如果你的工程把主频改到 48MHz,或者用了内部 RC 而 CubeMX 里没同步改,延时就会偏。我这里是 72MHz 外部晶振。
引脚初始化用标准 HAL,PA0 配成 ADC 输入,PA1 配成推挽输出:
ADC_HandleTypeDef hadc1; void GPIO_Init_Dust(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef gpio = {0}; gpio.Mode = GPIO_MODE_ANALOG; gpio.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &gpio); gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pin = GPIO_PIN_1; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // LED 默认熄灭 } void ADC1_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); }4.2 定时器调度采样
TIM3 每隔 10ms 进一次中断,在中断里完成一次完整的“点亮 LED → 延时280µs → 读 ADC → 熄灭 LED”流程。CubeMX 里 TIM3 的预分频设 71、自动重载值设 9999,这样定时器输入时钟是 1MHz,溢出周期正好 10ms。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { uint16_t adc_raw; if (htim->Instance == TIM3) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // LED 点亮 delay_us(280); // 等待光路稳定 HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); adc_raw = (uint16_t)HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // LED 熄灭 delay_us(40); // 补足 0.32ms 脉宽 dust_raw_buf_push(adc_raw); // 送入环形缓冲 } }中断服务函数里做 280µs 微秒延时,不是不能接受,但注意把 TIM3 的中断优先级设置成普通级别,别嵌套在急停、通信这类高优先级中断里太久。每次中断占用大约 320µs 的时间去完成采样,也就是 3.2% 的 CPU 占用,对课程设计来说完全没问题。如果你跑 FreeRTOS 并且有严格实时任务,就不建议在中断里这么干,可以考虑用定时器 PWM 输出驱动 LED,再用另一个定时器触发 ADC,改成纯中断异步方式,不过复杂度会高不少。
主函数里记得启动定时器中断:
HAL_TIM_Base_Start_IT(&htim3);4.3 滤波与换算封装
环形缓冲和滤波单独封装,队列长度设 10。每次采样中断把原始 ADC 值丢进队列,主循环再调函数取浓度:
#define DUST_FIFO_LEN 10 #define VOLTAGE_DIVIDER_K 1.0f // 无分压时 1.0,加分压电阻时改成 2.0 static uint16_t fifo[DUST_FIFO_LEN]; static uint8_t fifo_idx = 0; static uint8_t fifo_cnt = 0; void dust_raw_buf_push(uint16_t raw) { fifo[fifo_idx] = raw; fifo_idx = (fifo_idx + 1) % DUST_FIFO_LEN; if (fifo_cnt < DUST_FIFO_LEN) fifo_cnt++; } float dust_read_ugm3(void) { uint32_t sum = 0; uint16_t sorted[DUST_FIFO_LEN]; uint8_t valid = (fifo_cnt < DUST_FIFO_LEN) ? fifo_cnt : DUST_FIFO_LEN; if (valid == 0) return 0.0f; memcpy(sorted, fifo, sizeof(sorted)); // 冒泡排序找最大值最小值,N=10 无所谓 for (uint8_t i = 0; i < DUST_FIFO_LEN - 1; i++) { for (uint8_t j = i + 1; j < DUST_FIFO_LEN; j++) { if (sorted[i] > sorted[j]) { uint16_t t = sorted[i]; sorted[i] = sorted[j]; sorted[j] = t; } } } uint8_t start = 1; // 去掉最小 uint8_t end = valid - 1; // 去掉最大 for (uint8_t i = start; i < end; i++) sum += sorted[i]; float avg_raw = sum / (float)(end - start); float voltage = avg_raw * 3.3f / 4096.0f * VOLTAGE_DIVIDER_K; // 无分压、5V 供电场景的临时公式,校准后替换 float density = (voltage - 0.9f) / 5.0f; // mg/m³ if (density < 0.0f) density = 0.0f; return density * 1000.0f; // µg/m³ }排序那一步其实可以改成线性扫描找最大最小值,但 N=10 差别不大,用排序代码反而好读。滤波后的数据是过去 100ms 的平均,已经相当平滑。很多教程会在这个基础上再叠一层 5 秒平均,那是给显示刷新用的,看你自己需求。
4.4 串口输出与显示对接
主循环里每秒打印一次:
char buf[32]; while (1) { float pm = dust_read_ugm3(); snprintf(buf, sizeof(buf), "PM2.5: %.1f ug/m3\r\n", pm); HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 100); HAL_Delay(1000); }接 OLED 就把 pm 变量直接丢给显示函数。需要上报云平台时,把这段数据整理成 JSON 或简单协议帧,通过 ESP8266 或 4G 模块转发。整个项目的数据流就是“TIM3 定时采样 → 滑动平均滤波 → 主循环换算 → 外设输出”,改动起来非常方便。要注意的是,如果你给 ADC 加分压电阻,记得改 VOLTAGE_DIVIDER_K,否则浓度会系统性偏小一半。
5. 校准实操:没有参考设备时,怎么把读数调到能用的程度
5.1 零点标定:HEPA 滤网法
我没有专业粉尘发生装置,所以用的是最土但最有效的偏置标定法。找一个大号密封袋,把传感器和一个小风扇放进去,袋口夹一块 HEPA 滤网,让风扇转几分钟,袋内空气反复通过滤网过滤,颗粒物浓度接近零。此时记录电压值 V0,这就是这颗传感器的零点。
注意密封袋不能完全封死,留一个小口让气流可以缓慢补充,否则风扇会憋住。过滤时间至少三五分钟,读数稳定后取平均值。V0 大概率不是数据手册的 0.9V,可能在 0.78V 到 1.02V 之间,这取决于个体差异和光路污染程度。把这个 V0 代入公式 C = (V - V0) / S,就先把“负值一大片”的问题解决了。
5.2 两点线性回归:用一支激光传感器做参照
光有零点还不够,斜率 S 也得标。最现实的办法:借一支激光 PM2.5 传感器,比如 PMS5003,或者直接用家用的空气检测仪当标准表。
把红外传感器和标准表放在同一个空间位置,制造几个不同浓度场景:
| 场景 | 操作 | 浓度区间(参考) |
|---|---|---|
| 低浓度 | 开窗通风 | 10-30 µg/m³ |
| 中浓度 | 关窗静置 | 40-100 µg/m³ |
| 高浓度 | 点一小段无烟香,轻轻扇到进气口 | 150-300 µg/m³ |
每组同步记录红外传感器的输出电压和标准表的浓度值,多取几个平衡点,最后用最小二乘拟合一条直线。比如我那次拿到的拟合结果是 C = 190 × (V - 0.83),和手册的经验公式接近,但零点差了不少。如果没条件做完整回归,至少把公式里的 0.9 替换成实测零点,斜率保留下册经验值的 5V/(mg/m³),误差也会小很多。
注意:制造污染时不要对着传感器猛吹烟,红外散射模型在大浓度下会饱和,拟合点全堆在低浓度区间反而更实用。测量时要保证两个传感器进风口在同一空间位置,不要把标准表放窗边、红外模块放墙角,那样拟合出来全是乱的。
5.3 温湿度、气流和位置对测量的影响
湿度是一个隐形杀手。颗粒物吸湿后体积变大,散射截面明显增强,同一个质量浓度下读数会偏大。这就是为什么空气湿度 90% 时的数据普遍比湿度 30% 时高。如果只是室内监测,可以在代码里做粗略的湿度补偿,比如 C_corrected = C / (1 + 0.02 × (RH - 50)),但这个系数完全是经验值,精度有限,只能算改善方向。课程设计答辩时能讲出这个点,反而比一句“我测了误差”要加分。
气流位置也一样重要。GP2Y1010 没有内置风扇,全靠空气自然扩散进出检测腔,响应时间很长。我实测过,从一个浓度环境移到另一个环境,读数要将近一分钟才能稳定。如果想加快响应,可以在靠近检测腔进气口的地方加一个微型轴流风扇,但风扇震动会影响光电信号,风速也不要太猛,避免吹散大颗粒。传感器安装位置要避开阳光直射、电热器附近等强对流和强光环境,否则散射信号会被可见光干扰。
6. 实测复盘:从“读数跳动”到“渐趋稳定”的几个问题
6.1 采样点偏移导致的数据抖动
第一次画完板子,我把 LED 控制脚直接接在 5V 上让 LED 常亮,想着“反正一直照着,随时读都行”。结果 ADC 读数在 1300 到 2200 之间乱跳,根本没法用。后来才意识到这个传感器的光路是脉冲式的,输出只在 LED 点亮且散射稳定后才有意义,常亮模式下接收管同时收到大量环境背景光,信号完全被淹没。改成 10ms 周期加 0.28ms 采样点后,数据立刻稳定下来了。这个教训说明,时序不是“优化项”,而是“必需项”。
6.2 电源纹波与共地问题
第二次调试,数据在低浓度时相对平稳,但只要我一给步进电机发脉冲,浓度读数就会出现和电机驱动脉冲同频率的尖峰。排查后发现问题出在供电上:电机驱动器从同一个 5V 取电,GP2Y1010 的 VLED 瞬时电流被拉低,红外 LED 亮度随之波动。后来把电机电源单独出来,传感器用一个独立 5V LDO 供电,并且在模块 Vcc 引脚旁加了 100µF 加 0.1µF 电容,尖峰就消失了。另外确认传感器地和 STM32 地是同一个网络,并且铺了较宽的铜皮,避免地线环路造成共模噪声。
6.3 无风扇传感器的时延性格
还有一个很多人不提的点:GP2Y1010 测的其实是“穿过检测腔空气”的浓度,而检测腔容积很小,换气全靠扩散,分子扩散速度很慢。所以你会发现读数变化总是慢半拍。出门测 PM2.5,走到马路边,读数要几十秒才往上爬;回到室内又要几十秒才回落。如果你拿它去跟手机上显示的实时数据对比,总觉得对不上,大多数时候其实是时延问题,不是传感器坏了。
解决办法就是前面说的加个小风扇,并且给足稳定时间。如果做比对测试,最好让两台设备并排放置,等 5 分钟读数稳定后再记录,而不是频繁移动设备。最后再说一个购买上的小技巧:买模块时优先选带金属屏蔽罩和导气口结构的版本,外壳密闭性更好,环境光和气流干扰都会小一些。拿到手也别急着焊,先按第 5 节的步骤把 V0 测出来写进代码,你会发现后面所有数据都顺眼很多。