
1. 项目缘起与整体设计思路1.1 为什么选红外PM2.5传感器而不是激光方案做空气质量监测类项目传感器选型是第一个分水岭。市面上主流的PM2.5检测方案大致分三档红外散射式、激光散射式、以及称重法这类专业设备。称重法不用考虑那是实验室级别的东西成本和体积都不适合嵌入式项目。真正让人纠结的是红外和激光之间怎么选。激光方案的代表是攀藤系列精度高、能测PM1.0/PM2.5/PM10三档数据一致性也好但价格通常在几十到上百元而且对气路结构有要求风扇和激光头都需要稳定的供电和防尘设计。红外方案的代表就是GP2Y1010AU0F这类模块价格便宜十几块钱就能拿到驱动简单对气路要求低缺点是精度一般、容易受温湿度影响、输出是模拟量需要自己做ADC采样和标定。我最终选红外方案核心原因有三个。第一是成本控制这个项目本身定位是入门级空气质量监测用激光方案有点杀鸡用牛刀。第二是驱动复杂度红外模块输出模拟电压STM32的ADC直接就能采不需要解析串口协议对新手友好。第三是功耗红外模块工作电流通常在10mA左右比激光模块的几十毫安低不少如果后续要做电池供电的便携版本红外方案更有优势。当然这个选择是有代价的。红外方案测出来的值只能作为趋势参考不能当计量级数据用。如果你要做的是需要上报环保平台或者做科研记录的项目那还是老老实实上激光方案。这一点在项目开始前就要想清楚不然后面标定的时候会很痛苦。1.2 STM32型号选择与最小系统搭建STM32的型号选择上这个项目其实要求不高。核心外设只需要一个ADC通道、一个定时器用于传感器内部LED的脉冲驱动、一个串口用于调试输出。所以F103C8T6这颗经典的“蓝板”芯片完全够用Flash 64KB、RAM 20KB跑这个项目绰绰有余。如果你手头有F401或者F411这类带FPU的芯片也可以用但没必要。这个项目不涉及复杂浮点运算标定公式用定点数或者简单的浮点运算都能搞定。我实测下来F103在72MHz主频下整个采集计算串口输出的循环跑下来CPU占用率不到5%。最小系统板的选择上建议直接用现成的开发板比如常见的“蓝色药丸”或者江科大那套最小系统板。自己画板子的话注意几个点BOOT0和BOOT1要下拉到地否则可能从系统存储器启动NRST要接复位电路虽然STM32内部有上电复位但外部复位按键在调试时很有用VDDA和VSSA要单独滤波ADC的参考电压质量直接影响采样精度。晶振方面外部8MHz晶振配内部PLL倍频到72MHz是标准操作。如果你用的是F103C8T6注意它的ADC时钟最高只能到14MHz所以ADC预分频器要设置成6分频这样72MHz/612MHz在安全范围内。这个细节很多人会忽略导致ADC采样值跳动很大。1.3 系统整体架构与数据流整个系统的数据流是这样的STM32的定时器输出PWM信号驱动红外传感器的LEDLED发出的红外光经过空气散射后被光电二极管接收传感器输出一个与粉尘浓度相关的模拟电压。STM32的ADC采集这个电压经过滤波和标定计算后得到PM2.5浓度值最后通过串口输出到上位机或者OLED屏幕显示。这个架构里有两个关键点需要提前想清楚。第一是采样时序红外传感器的LED不能一直亮着否则光电二极管会饱和而且LED寿命也会受影响。标准做法是用一个周期性的脉冲驱动LED在LED亮起后的特定时间窗口内采样ADC。GP2Y1010AU0F的推荐驱动周期是10ms脉冲宽度0.32ms采样点设在脉冲开始后0.28ms处。这个时序必须用定时器精确控制不能靠软件延时否则采样值会飘。第二是滤波策略。红外传感器的输出噪声比较大原始ADC值跳动可能有几十个LSB。我试过几种滤波方案简单平均、滑动平均、中值滤波、以及一阶低通滤波。实测下来滑动平均配合一阶低通效果最好既能抑制随机噪声又不会让响应变得太迟钝。具体参数后面会详细说。2. 硬件连接与核心细节解析2.1 红外PM2.5传感器的引脚定义与驱动要求GP2Y1010AU0F这个模块有6个引脚但实际用到的只有5个。引脚定义如下引脚编号名称功能说明1V-LEDLED阳极接5V供电2LED-GNDLED阴极接STM32的GPIO控制通断3LEDLED驱动信号实际接法见下文4S-GND信号地接STM32的GND5Vo模拟输出接STM32的ADC输入6Vcc内部电路供电接5V这里有个容易踩坑的地方引脚1和引脚2之间要串一个限流电阻典型值是150欧姆。如果你直接接5V到引脚1引脚2接地LED电流会超过100mA虽然短时间内不会烧但长期运行LED光衰会很快。我第一版就是忘了加这个电阻跑了大概两周后发现读数整体偏低换了新传感器才恢复。正确的接法是引脚1接5V引脚2通过150欧姆电阻接STM32的GPIO推挽输出引脚3悬空或者接一个电容到地。引脚6接5V引脚4接地引脚5接STM32的ADC输入引脚。注意STM32的GPIO输出高电平时是3.3V而传感器LED阳极接的是5V所以GPIO输出高电平时LED两端电压差是5V-3.3V1.7V再减去电阻压降实际LED电流会偏小。实测下来用3.3V GPIO驱动时LED电流大约在8mA左右比推荐的10mA略低但完全能用。如果你追求更精确的驱动可以用一个NPN三极管做电平转换GPIO控制三极管基极三极管集电极接LED阴极发射极接地。2.2 STM32 ADC配置与采样时序设计ADC的配置是这个项目的核心难点。STM32F103的ADC是12位逐次逼近型最大采样率1MHz但实际使用时受限于采样时间和输入阻抗。红外传感器的输出阻抗大约在10k欧姆左右所以ADC的采样时间要设置得足够长让采样电容充分充电。我推荐配置如下ADC时钟12MHz采样时间设为55.5个周期这样总转换时间大约是(55.512.5)/12MHz≈5.67微秒。这个速度对于10ms的采样周期来说绰绰有余。采样时序的设计上我用TIM2的通道1输出PWM周期10ms脉冲宽度0.32ms。在PWM上升沿触发ADC采样但要注意ADC采样不能立即开始因为LED刚亮起时输出还不稳定。我实测下来在脉冲开始后0.28ms处采样最稳定。实现方式是用TIM2的另一个通道做比较输出在0.28ms处产生一个触发信号启动ADC转换。具体配置步骤配置TIM2为PWM模式预分频器设为72-1自动重装载值设为10000-1这样PWM周期就是10ms。配置TIM2的通道1为PWM输出比较值设为320对应0.32ms脉冲宽度。配置TIM2的通道2为输出比较模式比较值设为280对应0.28ms。将TIM2的通道2比较事件配置为ADC的外部触发源。配置ADC为外部触发模式触发源选择TIM2的CC2事件。开启ADC的DMA传输将转换结果自动搬运到内存数组。这样配置下来整个采样过程完全由硬件自动完成CPU只需要在DMA传输完成中断里读取数据即可。我实测下来这套时序的采样一致性非常好同一浓度下连续采1000次标准差不到2个LSB。2.3 电源与滤波电路的设计要点电源质量直接影响ADC采样精度。STM32的VDDA和VSSA要单独接一个100nF陶瓷电容和一个10uF钽电容越靠近芯片引脚越好。红外传感器的Vcc也要加一个100nF去耦电容否则LED通断时会在电源线上产生尖峰干扰ADC采样。如果你用的是USB供电注意USB的5V电源噪声比较大建议在5V输入端加一个LC滤波。我试过直接用USB供电ADC采样值跳动大约有10个LSB加了LC滤波后跳动降到3个LSB以内。地线的处理也很关键。模拟地和数字地要分开走线最后在一点汇合。如果实在做不到至少保证ADC的接地引脚和传感器的S-GND接在同一个铜皮上不要经过长导线。提示如果你发现ADC采样值总是偏大或者偏小先检查VDDA电压是否准确。用万用表量一下VDDA对VSSA的电压应该是3.3V。如果偏差超过50mV检查稳压芯片和滤波电容。3. 实操过程与核心环节实现3.1 开发环境搭建与工程配置开发环境我用的是Keil MDK5配合STM32CubeMX。CubeMX负责生成初始化代码Keil负责编写业务逻辑。这个组合的好处是外设配置可视化不容易出错而且生成的代码结构清晰后续维护方便。如果你习惯用标准库也可以但CubeMX生成的HAL库代码在可读性和移植性上更好。我试过用标准库重写一遍代码量差不多但HAL库的抽象层次更高换芯片时改动更少。工程配置的关键步骤在CubeMX里选择STM32F103C8T6芯片配置RCC为外部晶振时钟树设置HCLK为72MHz。配置PA0为ADC1_IN0用于采集传感器输出。配置PA1为GPIO输出用于驱动传感器LED。配置TIM2的通道1为PWM输出通道2为输出比较。配置USART1为异步模式波特率115200用于调试输出。配置ADC1为外部触发模式触发源选择TIM2_CC2开启DMA传输。生成代码选择HAL库工具链选MDK-ARM。生成代码后需要在main.c里添加自己的业务逻辑。CubeMX生成的代码已经初始化了所有外设我们只需要在while循环里处理数据即可。3.2 传感器驱动代码的编写与调试驱动代码的核心是启动PWM和ADC然后在DMA完成中断里读取数据。下面是我实际使用的代码片段// 启动PWM和ADC HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_OC_Start(htim2, TIM_CHANNEL_2); HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE); // DMA完成回调函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC1) { // 计算平均值 uint32_t sum 0; for (int i 0; i ADC_BUFFER_SIZE; i) { sum adc_buffer[i]; } uint16_t avg sum / ADC_BUFFER_SIZE; // 转换为电压值单位mV uint32_t voltage avg * 3300 / 4096; // 标定计算PM2.5浓度 float pm25 (voltage - 500) * 0.17f; if (pm25 0) pm25 0; // 输出到串口 printf(PM2.5: %.1f ug/m3\r\n, pm25); } }这段代码里有个细节需要注意ADC_BUFFER_SIZE我设的是16也就是每次触发采集16个点取平均。为什么是16而不是更多因为PWM周期是10ms如果采集32个点就需要320ms才能输出一次数据响应太慢。16个点对应160ms的输出周期对于空气质量监测来说足够了。标定公式里的0.17和500这两个参数是怎么来的这是根据传感器数据手册的典型曲线拟合出来的。GP2Y1010AU0F在无尘环境下的输出电压大约是0.5V每增加100ug/m3的PM2.5浓度输出电压增加约0.17V。所以用(电压-500mV)/0.17得到浓度值。但这个公式只是近似实际使用前需要用标准仪器做一次两点标定。3.3 数据滤波与标定计算的实现原始ADC数据噪声很大直接用来计算浓度会跳得很厉害。我用了两级滤波第一级是滑动平均窗口大小8第二级是一阶低通滤波系数0.2。滑动平均的代码很简单维护一个环形缓冲区每次新数据进来就替换最老的数据然后求平均。一阶低通滤波的公式是filtered filtered * (1 - alpha) new_value * alpha其中alpha取0.2。为什么选0.2我试过0.1、0.2、0.3、0.5几个值。0.1太迟钝浓度变化后要好几秒才能跟上0.5太灵敏噪声抑制不够0.2是比较好的平衡点响应时间大约1秒噪声抑制也够用。标定计算上我建议至少做一次两点标定。找一个干净环境比如室外通风处和一个污染环境比如点一根烟分别记录ADC电压值然后线性拟合。我实测下来干净环境下电压约0.52V香烟烟雾环境下约1.8V对应的PM2.5浓度大约500ug/m3。拟合出来的斜率是(1800-520)/(500-0)2.56mV/(ug/m3)比数据手册的1.7mV/(ug/m3)要大一些说明我这个传感器的灵敏度偏高。注意标定时的参考浓度值最好用专业仪器测量如果没有可以用当地环保部门发布的实时数据做参考。但要注意环保部门的数据是经过湿度校正的而红外传感器对湿度很敏感所以标定时要记录湿度必要时做湿度补偿。4. 常见问题与排查技巧实录4.1 采样值跳动大或不稳定的排查思路这是最常见的问题我遇到过好几次。排查思路按以下顺序来第一检查电源。用示波器看VDDA和Vcc的纹波如果峰峰值超过50mV就要加滤波电容。我遇到过因为USB线太长导致电源纹波大换了根短线就好了。第二检查采样时序。用示波器同时看LED驱动信号和ADC触发信号确认采样点确实在LED亮起后的稳定窗口内。如果采样点太早或太晚读数都会异常。第三检查传感器是否被污染。红外传感器的光学腔体如果积灰读数会整体偏高。用洗耳球吹一下或者用无水酒精棉签轻轻擦拭光学窗口。第四检查ADC配置。确认采样时间足够长输入阻抗匹配。如果采样时间太短采样电容充不满读数会偏小。下面是一个快速排查表现象可能原因排查方法解决方法读数整体偏高光学腔体积灰目视检查清洁光学窗口读数整体偏低LED限流电阻过大测量LED电流减小限流电阻读数跳动大电源纹波大示波器看VDDA加LC滤波读数周期性波动采样时序不对示波器看时序调整比较值读数不变化ADC未触发检查DMA配置重新配置触发源4.2 串口输出乱码或数据异常的处理串口乱码通常是波特率不匹配。STM32的USART1挂载在APB2总线上时钟是72MHz配置波特率115200时BRR寄存器的值应该是72MHz/(16115200)39.0625取整后是39实际波特率是72MHz/(1639)115384误差0.16%在允许范围内。如果波特率没问题但还是乱码检查以下几点TX和RX是否接反了STM32的TX接USB转串口的RX地线是否共地串口线是否太长超过1米建议用屏蔽线。数据异常的话先确认printf重定向是否正确。在Keil里需要勾选“Use MicroLIB”否则printf可能无法正常工作。重定向代码如下int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t*)ch, 1, 100); return ch; }4.3 传感器长期运行的漂移与维护建议红外传感器长期运行后LED光衰和光学腔体污染会导致读数漂移。我实测下来连续运行一个月后同一环境下的读数大约偏高10%左右。这个漂移是渐进的不会突然跳变所以很难通过单次读数发现。维护建议每两周用干净的气吹清理一次光学窗口每三个月做一次零点标定在干净环境下记录电压值如果比初始值高了超过50mV就需要考虑更换传感器。如果项目要求长期无人值守运行建议加一个自动零点校正功能。具体做法是每天凌晨3点空气质量通常最好的时候记录一次ADC值作为零点参考然后动态调整标定公式的截距。这个功能我试过效果不错能把漂移控制在5%以内。提示自动零点校正的前提是每天凌晨确实有干净空气。如果项目部署在工业区凌晨也可能有污染那这个方法就不适用了。可以考虑加一个温湿度传感器用温湿度数据做补偿效果更稳定。4.4 与激光方案的对比实测数据为了验证红外方案的实际表现我同时用GP2Y1010AU0F和攀藤PMS5003做了对比测试。测试环境是办公室分别记录了三天的数据。指标红外方案激光方案价格约15元约80元精度±30%±10%响应时间约1秒约0.5秒功耗约10mA约30mA湿度影响大小寿命约1年约2年从数据看红外方案在精度和响应时间上确实不如激光方案但价格只有五分之一功耗只有三分之一。对于入门级项目和成本敏感的应用红外方案是合理的选择。如果项目要求高精度或者需要长期稳定运行建议直接上激光方案省得后面反复调试。我在实际使用中发现红外方案最大的优势是驱动简单不需要解析串口协议ADC直接采就行。但最大的坑是湿度影响下雨天读数会偏高20%以上。如果项目部署在室外一定要加湿度补偿否则数据没法看。最后再分享一个小技巧如果你觉得红外传感器的读数还是太跳可以在传感器输出和ADC输入之间加一个RC低通滤波截止频率设10Hz左右。我试过用1k电阻和10uF电容效果很明显原始数据的标准差从15个LSB降到了5个LSB。这个改动成本不到一毛钱但效果立竿见影。