ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32驱动红外PM2.5传感器GP2Y1010AU0F低成本粉尘检测实战

STM32驱动红外PM2.5传感器GP2Y1010AU0F低成本粉尘检测实战 最近我在折腾室内空气监测的小板子最初想直接用现成的串口PM2.5模块后来发现成本压不住——好一点的激光传感器模块动不动几十上百而手上正好还有一堆STM32F103C8T6最小系统板和一片夏普GP2Y1010AU0F于是干脆用STM32把这块红外PM2.5传感器接了起来做一个低成本粉尘浓度检测方案。这个方案最适合三类人一是要做课程设计或者毕业设计的同学想要一个能跑起来、能出数据、还能讲清楚原理的环境监测项目二是搞智能家居DIY想给新风系统加个空气检测前端的工程师三就是跟我一样手头有闲置物料、想废物利用的嵌入式玩家。整套实现思路是用定时器产生脉冲信号驱动传感器内部的红外LED在指定时刻用ADC采集光电二极管转换后的输出电压再按传感器的标定曲线换算成mg/m³或者μg/m³浓度值最后从串口发出去。下面我把选型、接线、驱动时序和调参踩坑整个复盘一遍。1. 红外PM2.5传感器到底是个什么原理1.1 灰尘是怎么被看见的讲这个之前先想想天空中的丁达尔效应——光束在浑浊空气里会呈现一条清晰的光路就是因为微小颗粒把光散射了。红外PM2.5传感器用的就是这个散射原理只是把人眼看到光路这件事换成光电二极管转成电压。GP2Y1010AU0F内部结构其实很简单一个红外LED波长大约880nm一个光电二极管再加一级信号处理电路。LED朝检测腔发射红外光当空气里的颗粒物经过检测腔时部分入射光被颗粒散射散射光打到光电二极管上转换成电压信号输出。颗粒越多散射光越强输出电压就越高。需要注意一个很关键的细节红外传感器是靠气流中的颗粒被动扩散进入检测腔来检测的它没有内部风扇。也就是说传感器周围的空气必须流动检测腔里面的颗粒才会持续更新。如果你把它放在完全密封的盒子里读数会慢慢漂移甚至越来越低。但用于室内环境监测只要有正常的空气流通基本够用。1.2 红外传感器和激光传感器怎么选很多人一上来就问为什么不用激光的。激光传感器走的是另一条路子——光散射加颗粒计数内置风扇主动抽气通过测量颗粒反射激光的脉冲数量来统计不同粒径范围的颗粒数。它的优点非常明显精度高能区分PM1.0、PM2.5、PM10数据曲线平滑直接输出数字量。缺点是贵、功耗高、有风扇噪音睡眠场景下特别出戏。红外传感器便宜、功耗低、体积小、电路简单缺点同样明显第一它输出的是模拟电压不是颗粒数只能按浓度拟合曲线去换算误差天然存在第二它没法区分颗粒大小实际测的是粉尘质量浓度的粗略估算不是严格意义上的PM2.5计数浓度第三对粒径大于0.8μm的颗粒灵敏度高对小颗粒的响应差。选型建议比较直接做产品原型验证、低成本环境监测、课程设计或者只需要大致知道空气好不好的场景红外方案足够。如果做空气质量监测仪拿去卖对精度有要求老老实实上激光传感器别在红外方案上死磕。1.3 传感器关键参数拿最常见的GP2Y1010AU0F来说真正需要关心的参数就这几个我列在下面供参考无尘环境输出电压0.9V左右部分个体在0.6V左右灵敏度0.5V / (0.1mg/m³)换算一下就是5V/(mg/m³)量程最大约0.5mg/m³再高电压会接近饱和线性变差LED脉冲周期要求10ms即100HzLED高电平脉宽0.32ms采样窗口在LED点亮后0.28ms开始采样持续0.04ms这个时序是整个项目的核心搞错了数据一定乱跳。后面我会专门讲怎么在STM32上把这段时序精确做出来。2. 硬件接线别急着焊先理清每一个引脚2.1 芯片引脚定义与连接GP2Y1010AU0F是个六脚插针的传感器模块引脚定义如下不同批次丝印可能有差异拿到实物先对着手册确认别凭印象接1脚 VLED红外LED电源正极一般串限流电阻后接5V2脚 LED-GNDLED电源地3脚 LEDLED脉冲控制脚由MCU的IO或PWM驱动4脚 S-GND信号地5脚 VCC传感器供电正极典型5V6脚 AOUT模拟信号输出我这次的具体接法是VLED通过一个150Ω限流电阻接到5VLED引脚接STM32的某个GPIO或PWM输出脚VCC接5VS-GND接GNDAOUT接STM32的ADC输入脚我用的是PA0。电源部分强烈建议用AMS1117-5.0这类线性稳压芯片单独给传感器供电因为传感器对电源纹波非常敏感。我实际对比过用开关电源的5V给传感器供电AOUT上的噪声明显变大数据跳得厉害。2.2 关于电平匹配的一个大坑GP2Y1010AU0F数据手册里的标定曲线是在5V供电下给出的AOUT输出范围大约0.9V到3.6V。而STM32F103的ADC参考电压是3.3V如果直接把AOUT接到PA0电压一旦超过3.3VADC就饱和了读出来永远是4095。这个问题我在第一次调试时撞了个正着一开始还以为是传感器坏了。解决办法有两个一是传感器改用3.3V供电输出最高不会超过3.3V但灵敏度会随供电电压下降实测大概打七折无尘输出电压也会掉到0.6V左右二是保持5V供电把AOUT做电阻分压后再进ADC软件里按比例还原。我最后用了方案二目的是尽量保住传感器手册上的灵敏度指标。具体分压做法从AOUT串一个10kΩ电阻到PA0PA0对地再接一个20kΩ电阻。这样分压比是20/(1020)2/3传感器AOUT最高3.6V时PA0上只有2.4V远远低于3.3V安全得很。注意两个分压电阻的精度会直接影响最终浓度换算结果如果手头有精度1%的电阻最好实在没有就用普通电阻然后拿万用表量一下实际分压比把实测值写进代码比理论值靠谱。2.3 电磁干扰和走线这个项目看起来简单但传感器输出的本质是微弱电流转成的电压信号而LED驱动脉冲电流又有几十毫安如果布局走线太随意AOUT会被串扰得很惨。我现在的做法是把传感器放在距离MCU不超过15cm的位置AOUT信号线用屏蔽双绞线地线单独走绝对不和LED驱动线并在一起。电源线也尽量短每个电源引脚处放一个100nF去耦电容。这些小细节平时不显眼但在数据稳定性上差别很大。3. 软件实现PWM驱动、ADC采样和浓度换算3.1 驱动时序详解GP2Y1010AU0F的数据手册给了非常明确的驱动时序每10ms给LED发一次高电平脉冲脉冲宽度0.32ms在脉冲开始后0.28ms到0.32ms之间完成采样。也就是说PWM频率是100Hz占空比3.2%采样点必须落在LED高电平的末尾段。听起来简单实际很容易翻车如果直接让ADC自由运行、随机时刻采样采到的很多数据点根本不在有效窗口内结果自然乱七八糟。我之前看到过一些人的做法用一个普通GPIO拉高LED延时280us后读ADC再拉低。这种办法思路没问题但要保证延时足够精确别用那种会被编译器优化掉的空循环。另外如果用标准PWM输出计算也有讲究。72MHz主频下如果PSC设为719ARR设为999PWM频率就是72000000/(720×1000)100Hz周期10msCCR设为32占空比是32/10003.2%高电平时间正好0.32ms。这个配置刚好匹配传感器要求。3.2 CubeMX初始化和配置我这次项目用的是STM32CubeMX生成工程MCU是STM32F103C8T6。关键配置分三块ADC、定时器、串口。ADC这块PA0配置为ADC1_IN0单通道。采样时间我选了最长的239.5周期目的是降低信号源内阻对采样精度的影响。连续转换不启用我们希望在确定的时刻触发单次转换而不是让ADC一直跑。定时器方面如果走PWM方案PA6配置为TIM3_CH1输出100Hz PWMPulse设为32。串口USART1配置为115200、8-N-1用来往外发结果。CubeMX里生成MDK-ARM工程后再手动加DWT延时函数用于精确控制280us的采样窗口。3.3 采样代码实现先说一个很容易踩的坑一开始我用HAL_TIM_PWM_Start和HAL_ADC_PollForConversion来做采样逻辑上看起来没问题代码大概是下面这样void read_dust_pwm(void) { HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 等待280us让采样点进入有效窗口 delay_us(280); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t adc_val HAL_ADC_GetValue(hadc1); // 等待40us窗口结束 delay_us(40); HAL_TIM_PWM_Stop(htim3, TIM_CHANNEL_1); }实测发现HAL库函数内部包含大量寄存器操作和状态判断执行时间不确定PWM启动后到ADC采样之间的精确间隔很难保证。后来我换成了更直接的方式把LED驱动引脚当成普通GPIO手动拉高用DWT Cycle Counter做精确延时这样从拉高到采样的时间完全由自己控制。DWT延时的代码如下// 启用DWT计数器 void dwt_delay_init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } // 精确延时单位us适用于72MHz主频 void delay_us_dwt(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * 72; while ((DWT-CYCCNT - start) ticks); }然后这样读取一次浓度对应的ADC值uint16_t read_dust_adc(void) { // 拉高LED开始脉冲 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // 等待到采样窗口手册要求0.28ms delay_us_dwt(280); // 启动单次ADC转换并读取 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t adc_val HAL_ADC_GetValue(hadc1); // 保持到0.32ms后拉低LED delay_us_dwt(40); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); return adc_val; }这个方案的缺点是采样一次要占用CPU大约0.32ms对低功耗场景不太友好但绝大多数学习类和产品原型项目完全够用。想要低功耗可以在每次采样前临时把LED引脚复用为定时器PWM输出等窗口结束后立即关掉MCU再进入休眠。思路是一致的只是把软件延时换成硬件PWM波形。3.4 浓度换算公式ADC读出来的是0到4095的原始值得先转成电压再还原分压比例。假设PA0上读到的ADC值是adc_val// PA0处电压 float pa0_voltage (float)adc_val / 4096.0f * 3.3f; // 还原分压前面用的是2/3分压 float sensor_voltage pa0_voltage / 0.6667f;然后按手册标定曲线换算浓度。GP2Y1010AU0F的常见公式有这么几种我实测下来最贴合的是float density_mg (sensor_voltage - g_volt_no_dust) / 5.0f; float density_ug density_mg * 1000.0f;其中g_volt_no_dust是无尘环境下的输出电压典型值0.9V左右。也有资料给过一个拟合公式浓度(mg/m³) 0.17 × 输出电压 - 0.1。这个公式在0.1到0.4mg/m³区间表现还行但低浓度段容易算出负值我不太推荐。这里要特别强调一句红外传感器本身精度有限与其纠结用哪个公式不如拿到模块后自己标定一次。做法也不复杂找一个相对干净的环境记录无尘输出电压V0再用一支激光传感器做参照物测几组不同空气环境下两个设备的数据做两点或三点校准效果比套用任何厂家公式都稳。3.5 滤波处理不要直接拿原始值显示红外传感器的模拟输出噪声非常明显尤其在室内有人走动、空气净化器运行或者开窗通风的时候瞬时电压尖峰很多。如果你直接把每次采样的值换算后扔到屏幕上会看到一个疯狂跳动的数字跟心电图似的。滤波处理必须做。我常用的组合方案是这样的每100ms采一次连续采16次取平均这样大概1.6秒更新一次结果对平均后的序列再取窗口为3的中值滤波消掉偶发尖峰最后做限幅处理连续两次结果差超过100μg/m³时用前一次的值代替。平均加中值加限幅三件套下来数据表现就很稳了。核心代码大概长这样#define FILTER_N 16 uint16_t adc_buf[FILTER_N]; uint8_t buf_idx 0; float filter_and_calc(void) { uint32_t sum 0; for (uint8_t i 0; i FILTER_N; i) sum adc_buf[i]; float avg_adc (float)sum / FILTER_N; float pa0_voltage avg_adc / 4096.0f * 3.3f; float sensor_voltage pa0_voltage / 0.6667f; float density_mg (sensor_voltage - g_volt_no_dust) / 5.0f; if (density_mg 0.0f) density_mg 0.0f; return density_mg * 1000.0f; }3.6 串口输出和上位机联动数据算出来了得想办法看结果。最简单的办法是重定向printf到USART1直接在串口助手里看。重定向代码是嵌入式老传统的玩法int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }主循环里直接打印printf(PM2.5: %.1f ug/m3, raw: %d, volt: %.2f\r\n, density, adc_raw, sensor_voltage);如果是想接到自己的上位机、家庭网关或者Node-RED这类平台建议直接输出JSON结构省得解析麻烦printf({\pm25\:%.1f,\adc\:%d}\r\n, density, adc_raw);这里有一个小教训HAL_UART_Transmit阻塞发送虽然简单但每次输出都会占住CPU一小段时间。如果后续要同时采集温度、湿度等其他传感器或者要处理按键、显示刷新最好改成DMA发送或者把数据先放进环形缓冲区让发送在后台完成。4. 实测数据、常见问题与排查经验4.1 实测数据红外传感器的真实表现我在办公桌环境实测了一组数据密闭办公室、空调房开窗前后对比开窗前红外读数大概在30到60μg/m³之间浮动噪声较大同一环境下手里一支激光传感器对照读数是35μg/m³左右开窗通风两分钟后红外读数升到80到120μg/m³趋势非常明显靠近燃着的香烟附近时读数能冲到200μg/m³以上峰值到过350μg/m³。结论是当作趋势级监测完全够用但别指望它和几千块的专业仪器数值完全一致。个体偏差大数值仅供参考如果拿它来做超标报警或者比较级的显示效果是可以接受的。4.2 常见问题速查表我把这次调试中遇到和预料过的问题整理成了一张表方便大家快速对照现象可能原因解决办法ADC读数一直是0LED引脚没驱动或接线错误先写个简单的LED翻转程序验证引脚再查传感器各脚电压ADC读数一直是4095传感器输出电压超过3.3V加分压电阻或传感器改用3.3V供电数据噪声极大电源纹波大、接线过长改用线性稳压缩短信号线加屏蔽数据缓慢下降检测腔积灰气吹清理或用酒精棉签轻轻擦拭光敏窗口数据瞬间跳高人对传感器呼吸吹气、采样窗口没对准保持距离重新确认时序数据与激光传感器对不上红外方案固有误差、个体差异两点标定只做趋势对比用3.3V供电后数据明显偏低灵敏度下降重新标定或者改回5V供电加分压4.3 排查心得最让我头疼的一个坑这次项目卡了最久的坑说出来可能没人信是延时函数。最开始我用HAL_Delay但它只支持毫秒级没法精确控制280us的窗口。后来换了一个常见的循环延时函数开-O0优化时一切正常改成-O2优化后数据全乱套原因是编译器把那个空循环直接优化没了延时几乎变成零。这个坑在调试中被我找了整整一个晚上最后用DWT的Cycle Counter才彻底解决。启用方式很简单前面代码里已经写了这里再单独列一下避免有人翻不到CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk;用DWT的计数器做延时完全不受编译优化级别影响在Cortex-M3和M4系列上都是稳定性很高的做法。以后但凡遇到微秒级的精确延时需求我都建议直接用DWT别再写空循环了。4.4 关于校准和长期稳定性最后说校准。红外传感器出厂个体差异非常大同一型号的两个模块放在同一环境下读数相差20%以上都是正常的。如果要做正式产品每台设备出厂都需要独立标定如果只是自己做着玩建议定期在干净环境里校准一次无尘电压值。另外传感器内部的红外LED会随使用时间老化发光强度会衰减AOUT电压会整体变低表现就是读数整体偏低。解决方法也很朴素每隔几个月重新校准一次。5. 这套方案还能往哪些方向扩展5.1 从红外传感器换到激光传感器如果后续想提高精度建议直接把模块换成PMS5003这类串口输出的激光传感器。STM32这边不需要再保留PWM加ADC的逻辑只用把USART接到模块的串口上解析它的数据包即可协议通常以0x42、0x4D开头后面跟数据长度和校验值。代码结构可以完全保留只是把采集入口从ADC换成了串口解析。5.2 加显示和物联网接一块0.96寸OLED显示实时浓度用I2C接口代码量不大几十行就能搞定。想上云的话可以加一块ESP8266或者ESP32做Wi-Fi透传模块STM32把数据通过USART发给ESPESP上报到MQTT broker。这样一套下来一个低成本的室内空气质量监测节点就完整了后续还能扩展甲醛、温湿度等传感器拼成一个多参数环境监测站。5.3 低功耗改进如果要做电池供电的便携设备可以用定时器定时唤醒采集一次立即进入Stop模式采一次数据大约只占0.4ms剩下时间MCU都在睡觉。传感器本身功耗也很低配合一颗小锂电池做成一个随身携带的空气质量提醒小挂件是完全可行的。6. 一些值得记录的实操经验6.1 调试模拟传感器波形比代码更重要这次做下来我最大的感触是代码逻辑其实不难难的是把硬件时序和采样窗口对齐。强烈建议拿到传感器后先看数据手册把LED脉冲时序、采样窗口、供电电压都吃透再写代码否则后面全是返工。调试过程中示波器是最好的朋友。把AOUT接到示波器上同步触发LED脉冲信号能清清楚楚看到输出电压是否在正确的时间点跳变。没有示波器的话逻辑分析仪抓GPIO电平或者用ADC连续采样模式观察数据变化规律也比闷头猜强得多。6.2 别忽视传感器个体差异红外传感器不是精密仪器两个模块放在同一个环境里读数都可能差20%以上而且会随时间和积灰状态漂移。如果只是做着玩量级对得上就行如果要做产品每台设备出厂前都要标定。我在实际使用中的体会是红外PM2.5传感器适合做趋势检测和低成本提醒不适合做严格意义上的计量仪器认清这个定位项目推进起来会顺畅很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进