☰
STM32驱动红外PM2.5传感器:从脉冲采集到浓度换算的完整实战
2026/9/26 11:44:03 网站建设 项目流程

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供电正极,通常5V
2GND供电地
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/PA10:USART1,接USB转TTL模块,用于串口打印数据
  • PC13:板载LED,用于超标报警指示
  • SWD接口:PA13/PA14,用于下载和调试

供电方面,传感器需要5V,STM32最小系统板一般有5V输入和3.3V输出。传感器VCC接5V,OUT脚输出的是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需要做的是:

  1. 按照固定周期(比如10ms)触发LED点亮
  2. 在LED点亮期间,测量OUT脚低电平的持续时间
  3. 计算低电平时间占整个周期的比例,即占空比
  4. 通过公式换算成PM2.5浓度

这里有个关键点:低电平脉冲的宽度很窄,通常在几十微秒到几百微秒之间。如果用普通的GPIO轮询去读,很容易错过。所以必须用定时器输入捕获或者外部中断加定时器计数的方式。

我一开始用HAL_GPIO_ReadPin在循环里读,结果读到的全是高电平,偶尔跳一下低电平,根本测不准。后来改用TIM3的输入捕获功能,配置为上升沿和下降沿都捕获,就能精确测量低电平宽度了。

3.2 定时器输入捕获配置要点

以STM32F103的TIM3为例,配置步骤:

  • 使能TIM3和GPIOA时钟
  • 配置PA1为复用推挽或者浮空输入(取决于具体引脚复用功能)
  • TIM3预分频器设为71,这样计数频率为72MHz/(71+1)=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 / ((PSC+1)*(ARR+1)) // 目标频率100Hz,周期10ms // 设PSC=71,则计数频率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是状态,#是帧尾。上位机解析起来方便。

串口配置:波特率115200,8位数据位,1位停止位,无校验。STM32的USART1挂在APB2总线上,时钟72MHz,配置的时候注意波特率计算。

// 波特率 = fCK / (16 * USARTDIV) // 115200 = 72000000 / (16 * USARTDIV) // USARTDIV = 39.0625 // 整数部分39,小数部分0.0625*16=1 USART1->BRR = (39 << 4) | 1;

5. 常见问题与排查实录

5.1 串口无输出或者乱码

这是新手最常遇到的问题。排查顺序:

  1. 检查波特率:STM32和串口助手的波特率必须一致。我遇到过有人设了9600但助手用115200,出来的全是乱码。
  2. 检查时钟配置:如果系统时钟没配好,比如外部晶振没起振,默认用的是内部8MHz RC,那波特率就会偏。用示波器或者逻辑分析仪测一下USART_TX引脚,看波形周期对不对。
  3. 检查TX/RX接线:STM32的TX接USB转TTL的RX,RX接TX,别接反了。
  4. 检查串口助手是否打开了对应COM口:有时候电脑识别到多个COM口,选错了。

5.2 传感器读数一直为零或者满量程

可能原因:

  • LED驱动时序不对:如果LED脚一直低电平,传感器不工作,输出恒高;如果一直高电平,传感器可能过热保护。用示波器测PA0,确认有10ms周期、0.32ms高电平的波形。
  • OUT脚电平不匹配:5V输出直接接3.3V引脚,可能被钳位导致读不到低电平。加电阻分压或者电平转换。
  • 传感器镜头脏了:红外传感器的光学腔如果积灰,散射光被遮挡,读数会偏低甚至为零。用洗耳球吹一下,别用嘴吹,口水汽会污染镜头。
  • 供电不足:传感器启动电流大,如果USB供电不足,传感器可能反复重启。换一个输出能力强的USB口或者外接电源。

5.3 数据跳动大、不稳定的处理

除了前面说的滑动平均滤波,还可以从硬件上改善:

  • 在传感器VCC和GND之间加100uF+0.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这两个函数要熟悉。标准库的话,直接操作寄存器效率更高,但可读性差一些。我个人习惯用标准库,因为代码量小,烧录快,调试的时候心里更有底。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询