
1. 从一个灰尘传感器说起为什么选STM32做PM2.5监测前阵子帮朋友做一个室内空气质量监测的小盒子需求很明确实时读取PM2.5浓度超标就亮红灯数据能通过串口发到电脑上看。预算有限不可能上激光散射式传感器最后选了市面上最常见的红外PM2.5传感器主控用的是手头现成的STM32F103C8T6最小系统板。整套方案下来成本不到三十块精度虽然比不上专业设备但用来做室内空气趋势判断、触发新风或者净化器完全够用。这篇文章就把整个从选型、接线、写代码到调试排错的过程完整梳理一遍。如果你手上正好有STM32开发板和红外PM2.5传感器想快速跑通数据采集或者正在做基于STM32的环境监测类项目、毕业设计这篇内容可以直接拿来参考。我会把每个环节为什么这么做、参数怎么算、坑在哪里都讲清楚不是那种贴一段代码就完事的教程。红外PM2.5传感器的原理其实不复杂。它内部有一个红外发光二极管和一个光电接收管空气通过传感器内部的采样腔时颗粒物会散射红外光接收管检测到散射光的强度变化输出一个脉冲信号。颗粒物浓度越高单位时间内脉冲越多。传感器内部或者外部MCU需要统计一段时间内的脉冲占空比再通过一个经验公式换算成浓度值。市面上常见的型号比如GP2Y1010AU0F、PM2.5红外粉尘传感器带风扇或不带风扇的都有输出方式主要有两种模拟电压输出和数字脉冲输出。模拟输出直接接ADC读取电压简单但受温度漂移影响大数字脉冲输出需要MCU用定时器或者外部中断统计低电平时间占比精度相对好一些也是我更推荐的方式。STM32在这里的角色就是“大脑”负责给传感器提供合适的驱动时序有些传感器需要周期性点亮内部红外LED、采集脉冲信号、做滤波和换算、驱动显示或者上报数据。为什么不用Arduino说实话Arduino也能做但STM32的定时器资源丰富、中断响应快、ADC精度高做脉冲宽度测量和实时性要求高的场景更稳。而且STM32价格现在也很便宜F103系列几块钱一片性能绰绰有余。2. 硬件选型与接线别小看那几根线2.1 传感器型号确认与引脚定义红外PM2.5传感器型号挺多我手上这块是常见的五pin数字输出型引脚定义如下引脚名称功能说明1VCC供电正极通常5V2GND供电地3LED内部红外LED驱动控制脚4OUT数字脉冲输出5VCC部分型号有第二电源脚接5V这里要特别注意LED引脚不是给传感器供电的而是用来控制内部红外发光管的开关。传感器工作时需要周期性地点亮红外LED一般是每10ms点亮一次每次持续几百微秒。如果你直接把LED脚接高电平常亮传感器也能输出但寿命会缩短而且功耗偏高。正确做法是用STM32的一个GPIO或者定时器PWM来控制这个脚按照传感器手册给的时序来驱动。我用的这块传感器手册给出的推荐时序是周期10ms其中LED点亮时间约0.32ms。也就是说占空比大概3.2%。这个时序很关键后面代码里会体现。2.2 STM32最小系统板资源分配STM32F103C8T6最小系统板48脚封装资源足够。我的分配方案PA0接传感器LED控制脚配置为推挽输出用定时器TIM2的PWM通道或者软件延时控制PA1接传感器OUT脚配置为浮空输入或者上拉输入用外部中断或者定时器输入捕获来测量脉冲PA9/PA10USART1接USB转TTL模块用于串口打印数据PC13板载LED用于超标报警指示SWD接口PA13/PA14用于下载和调试供电方面传感器需要5VSTM32最小系统板一般有5V输入和3.3V输出。传感器VCC接5VOUT脚输出的是5V电平而STM32的GPIO耐压是3.3V直接接上去可能会损坏引脚。我在这里踩过坑第一次直接接上去PA1读数一直不正常后来查手册才发现需要电平转换。简单做法是用两个电阻分压比如10k和20k把5V降到3.3V左右。或者用一块电平转换模块更省事。注意如果你用的是3.3V供电的传感器版本那就不需要分压直接接就行。买传感器的时候一定要看清楚供电电压和输出电平。2.3 电源与滤波电容红外PM2.5传感器内部有风扇或者加热元件部分型号启动瞬间电流可能达到100mA以上。如果和STM32共用一路5V建议在传感器VCC和GND之间并一个100uF电解电容加一个0.1uF陶瓷电容滤掉低频和高频噪声。我实测不加电容的时候传感器输出脉冲偶尔会抖动加了之后稳定很多。另外STM32的ADC参考电压如果也用5V那ADC读数会受电源波动影响。不过我们这个方案用的是数字脉冲输出不依赖ADC所以电源要求相对宽松。但如果你用的是模拟输出型传感器那最好给STM32的VDDA和VREF加独立的LC滤波或者用一颗低压差稳压器单独给模拟部分供电。3. 驱动时序与脉冲采集核心逻辑拆解3.1 传感器工作时序详解红外PM2.5传感器的数字输出逻辑是这样的内部红外LED周期性点亮每次点亮时如果采样腔内有颗粒物接收管就会输出一个低电平脉冲脉冲宽度与颗粒物浓度相关。传感器OUT脚在LED点亮期间输出低电平其余时间输出高电平。MCU需要做的是按照固定周期比如10ms触发LED点亮在LED点亮期间测量OUT脚低电平的持续时间计算低电平时间占整个周期的比例即占空比通过公式换算成PM2.5浓度这里有个关键点低电平脉冲的宽度很窄通常在几十微秒到几百微秒之间。如果用普通的GPIO轮询去读很容易错过。所以必须用定时器输入捕获或者外部中断加定时器计数的方式。我一开始用HAL_GPIO_ReadPin在循环里读结果读到的全是高电平偶尔跳一下低电平根本测不准。后来改用TIM3的输入捕获功能配置为上升沿和下降沿都捕获就能精确测量低电平宽度了。3.2 定时器输入捕获配置要点以STM32F103的TIM3为例配置步骤使能TIM3和GPIOA时钟配置PA1为复用推挽或者浮空输入取决于具体引脚复用功能TIM3预分频器设为71这样计数频率为72MHz/(711)1MHz即每个计数代表1微秒自动重装载值设为65535避免频繁溢出通道1配置为输入捕获模式触发边沿先设为下降沿开启捕获中断和更新中断在中断服务函数里第一次捕获到下降沿时记录当前计数值然后把触发边沿改为上升沿第二次捕获到上升沿时再次记录计数值两者之差就是低电平持续时间。如果中间发生了溢出还需要加上溢出次数乘以65536。这个逻辑听起来简单但实际写的时候要注意中断里不要做太多事情只记录时间戳把计算和换算放到主循环里做。否则中断响应不及时会丢脉冲。3.3 LED驱动时序的实现方式LED控制脚需要每10ms产生一个约0.32ms的高电平脉冲。有两种实现方式方式一软件延时。在主循环里用HAL_Delay或者delay_us来控制。优点是简单缺点是占用CPU而且延时精度受中断影响。方式二定时器PWM。用TIM2的通道1输出PWM频率设为100Hz周期10ms占空比设为3.2%。这样硬件自动产生时序CPU完全不占用。我推荐这种方式稳定可靠。配置TIM2// 假设系统时钟72MHz // PWM频率 72MHz / ((PSC1)*(ARR1)) // 目标频率100Hz周期10ms // 设PSC71则计数频率1MHz // ARR 10000 - 1对应10ms周期 // CCR 320对应0.32ms高电平 TIM2-PSC 71; TIM2-ARR 9999; TIM2-CCR1 320; TIM2-CCMR1 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1 TIM2-CCER | TIM_CCER_CC1E; // 使能通道1输出 TIM2-CR1 | TIM_CR1_CEN; // 启动定时器这样PA0就会自动输出周期10ms、高电平0.32ms的方波直接接传感器LED脚即可。4. 数据换算与滤波从脉冲到浓度值4.1 浓度换算公式推导不同厂家的传感器换算公式略有差异但基本形式都是PM2.5浓度 K × 低电平占空比 B其中K和B是经验系数需要根据传感器手册或者实测标定来确定。以我手上这块为例手册给出的参考公式是浓度(ug/m3) (低电平时间 / 总周期) × 1000也就是说如果低电平占空比是0.1%那浓度大约是1ug/m3。但这个公式是线性的实际传感器在低浓度和高浓度段都有非线性。更准确的做法是分段拟合或者用查表法。我实测下来在洁净空气中PM2.5约10-20占空比大概在0.01-0.02之间点燃一根烟后占空比能到0.3-0.5对应浓度300-500。这个量级和公式基本吻合。4.2 滑动平均滤波的实现传感器原始数据跳动比较大直接显示会闪得厉害。我用了滑动平均滤波窗口大小设为10。具体做法是维护一个长度为10的数组每次新数据进来就替换最旧的数据然后求平均。#define FILTER_SIZE 10 float pm_buffer[FILTER_SIZE] {0}; uint8_t pm_index 0; float filter_pm(float new_val) { pm_buffer[pm_index] new_val; pm_index (pm_index 1) % FILTER_SIZE; float sum 0; for (int i 0; i FILTER_SIZE; i) { sum pm_buffer[i]; } return sum / FILTER_SIZE; }窗口大小可以根据需要调整。窗口越大数据越平滑但响应越慢。10个样本在10ms周期下就是100ms的响应时间对于室内监测足够了。实操心得如果发现数据偶尔出现极大值或者极小值可以在滤波前先做限幅比如超过1000的当作无效值丢弃。我遇到过传感器附近有强光干扰时输出脉冲异常加限幅后稳定很多。4.3 串口数据上报格式设计数据算出来后通过USART1发送到电脑。我用的格式是PM2.5: 35.2 ug/m3简单直接方便用串口助手查看。如果要接上位机做曲线显示可以用更结构化的格式比如$PM,35.2,OK#其中$是帧头PM是数据类型35.2是浓度值OK是状态#是帧尾。上位机解析起来方便。串口配置波特率1152008位数据位1位停止位无校验。STM32的USART1挂在APB2总线上时钟72MHz配置的时候注意波特率计算。// 波特率 fCK / (16 * USARTDIV) // 115200 72000000 / (16 * USARTDIV) // USARTDIV 39.0625 // 整数部分39小数部分0.0625*161 USART1-BRR (39 4) | 1;5. 常见问题与排查实录5.1 串口无输出或者乱码这是新手最常遇到的问题。排查顺序检查波特率STM32和串口助手的波特率必须一致。我遇到过有人设了9600但助手用115200出来的全是乱码。检查时钟配置如果系统时钟没配好比如外部晶振没起振默认用的是内部8MHz RC那波特率就会偏。用示波器或者逻辑分析仪测一下USART_TX引脚看波形周期对不对。检查TX/RX接线STM32的TX接USB转TTL的RXRX接TX别接反了。检查串口助手是否打开了对应COM口有时候电脑识别到多个COM口选错了。5.2 传感器读数一直为零或者满量程可能原因LED驱动时序不对如果LED脚一直低电平传感器不工作输出恒高如果一直高电平传感器可能过热保护。用示波器测PA0确认有10ms周期、0.32ms高电平的波形。OUT脚电平不匹配5V输出直接接3.3V引脚可能被钳位导致读不到低电平。加电阻分压或者电平转换。传感器镜头脏了红外传感器的光学腔如果积灰散射光被遮挡读数会偏低甚至为零。用洗耳球吹一下别用嘴吹口水汽会污染镜头。供电不足传感器启动电流大如果USB供电不足传感器可能反复重启。换一个输出能力强的USB口或者外接电源。5.3 数据跳动大、不稳定的处理除了前面说的滑动平均滤波还可以从硬件上改善在传感器VCC和GND之间加100uF0.1uF电容传感器远离风扇、空调出风口避免气流扰动避免强光直射传感器光学腔如果用的是带风扇的传感器风扇电源和信号地分开走线避免共地噪声我实测在加了电容和滤波之后洁净空气下读数波动在±3ug/m3以内点烟后能稳定上升到300以上响应时间大约2-3秒。5.4 常见问题速查表现象可能原因排查方法解决措施串口无输出波特率不匹配检查双方波特率设置统一为115200串口乱码系统时钟未起振测TX引脚波形检查晶振和时钟配置读数恒为零LED时序不对示波器测LED脚配置PWM输出读数恒为满量程OUT脚电平不匹配万用表测OUT电压加电阻分压数据跳动大电源噪声示波器看VCC纹波加滤波电容偶尔出现极大值强光干扰遮挡传感器测试加限幅滤波传感器发热严重LED常亮测LED脚电平改为脉冲驱动6. 项目扩展与个人体会这套方案跑通之后可以做的扩展挺多。比如加一个OLED屏幕用I2C接口直接显示实时浓度加一个蜂鸣器超标报警加一个ESP8266或者ESP32模块把数据传到手机或者云平台甚至可以用STM32的USB虚拟串口功能直接通过USB线把数据发给电脑省掉USB转TTL模块。我在实际使用中发现红外传感器的精度虽然有限但用来做趋势判断非常合适。比如室内抽烟、炒菜、扫地浓度都会明显上升净化器开启后能肉眼可见地下降。如果你需要更精确的数值那还是得上激光散射式传感器比如攀藤PMS5003系列但价格会贵不少。最后分享一个小技巧调试阶段可以用STM32的SWD接口配合ST-Link Utility实时查看变量比串口打印方便。把PM2.5浓度值放到一个全局变量里在ST-Link Utility的Watch窗口里直接看不用改代码重新烧录。这个技巧在调滤波参数的时候特别有用改一个参数看波形变化很快就能找到合适的窗口大小。代码方面我建议用标准库或者HAL库都行关键是理解定时器输入捕获和PWM输出的配置逻辑。如果你用HAL库HAL_TIM_IC_Start_IT和HAL_TIM_PWM_Start这两个函数要熟悉。标准库的话直接操作寄存器效率更高但可读性差一些。我个人习惯用标准库因为代码量小烧录快调试的时候心里更有底。