1. 项目缘起与整体设计思路
红外PM2.5传感器在空气质量监测领域算是性价比很高的一类方案,尤其是那种带风扇主动进气的红外粉尘传感器,几十块钱就能拿到,输出方式通常是UART串口或者PWM脉宽。我这次做的项目,就是用STM32去读取这类传感器的数据,然后做本地显示和串口上报。选这个题目的原因很直接:很多环境监测类的毕业设计、鱼缸控制、智能台灯项目,都需要一个能测颗粒物的环节,而红外方案比激光方案便宜、驱动简单,对STM32的算力要求也低,F103C8T6这种最小系统板就能跑得很稳。
整体设计上,我把项目拆成四块:传感器供电与电平匹配、STM32串口接收、数据帧解析、以及数据输出。传感器这边,市面上常见的红外PM2.5模块工作电压多在5V,而STM32的IO是3.3V电平,所以不能直接把传感器的TX接到STM32的RX上,中间要处理电平。我试过两种方案,一种是电阻分压,一种是电平转换模块,后面会详细说。STM32这边,我用的是USART2接传感器,USART1做调试打印,这样调试的时候不会和传感器数据打架。数据解析部分,红外传感器的串口协议一般是固定帧头加数据字节加校验和,帧长和校验方式不同厂家略有差异,需要先拿到手册确认。
为什么不用PWM方式读?因为PWM方式只能得到一个浓度对应的脉宽,精度和抗干扰都不如串口,而且串口方式能拿到更原始的数据,方便后期做滤波和校准。另外,串口方式对定时器的占用也少,PWM捕获还得额外配置定时器输入捕获,对新手来说容易在时钟树和中断优先级上踩坑。所以这个项目里,我优先推荐串口方案,PWM方案作为备选。
适合谁来参考?如果你正在做基于STM32的环境监测、智能家居、毕业设计,或者单纯想学串口通信和传感器驱动,这个项目都很合适。不需要你有很深的RTOS或者DMA基础,会用Keil新建工程、会配置串口中断就能跟上。下面我按实际操作的顺序,把每个环节拆开讲。
2. 硬件连接与电平匹配的细节
2.1 传感器引脚定义与供电选择
红外PM2.5传感器一般有四个引脚:VCC、GND、TX、RX。有的模块还多一个SET或者EN脚,用来控制风扇启停或者进入校准模式。我手上这个模块是5V供电,TX输出高电平也是5V。这里有个坑:如果你直接把TX接到STM32的PA3(USART2_RX),短时间可能能读到数据,但时间长了STM32的IO口会被5V倒灌,轻则引脚损坏,重则芯片发热。所以电平匹配必须做。
供电方面,我建议用STM32板子上的5V引脚给传感器供电,但要注意电流。红外传感器内部有风扇或者加热丝,启动瞬间电流可能到100mA以上,如果STM32板子上的5V是从USB口过来的,一般能撑住,但如果同时带OLED、蜂鸣器,就要算一下总电流。我实测这个模块稳定工作电流在60mA左右,峰值不超过120mA,USB供电没问题。
2.2 电平转换的两种做法与取舍
第一种是电阻分压。传感器TX接一个10k电阻,再串一个20k电阻到GND,中间抽头接STM32的RX。这样5V经过分压后大约是3.3V。这个方案成本最低,两个电阻就行。但缺点是分压后的信号边沿会变缓,如果波特率高于9600,波形可能畸变。我实测在9600波特率下没问题,115200就不太稳。所以如果你要用高波特率,建议用第二种方案。
第二种是电平转换模块,比如TXS0108E或者简单的MOS管电平转换电路。这种方案信号质量好,支持高速率,但要多花几块钱,接线也多两根。我自己的做法是:如果项目对成本敏感且波特率不超过9600,用电阻分压;如果要做产品或者波特率较高,直接上电平转换模块。另外,STM32的TX接到传感器RX时,因为STM32输出3.3V高电平,传感器那边一般能识别为高电平,所以这一路可以不加转换,但为了保险,我一般也会加一个1k电阻限流。
2.3 接线顺序与上电注意事项
接线顺序很重要。我习惯先接GND,再接VCC,最后接TX和RX。为什么?因为如果先接VCC,传感器和STM32之间没有共地,信号线可能会有电位差,容易打坏IO。共地之后,电位基准一致,再接信号线就安全。上电顺序上,先给STM32上电,再给传感器上电,或者同时上电。如果传感器先上电,它的TX会输出5V高电平,而STM32还没上电,IO口处于高阻态,这时候5V直接加到IO上,虽然电流不大,但长期这样也可能有问题。
还有一个细节:传感器的TX和RX不要接反。我见过有人把传感器的TX接到STM32的TX上,结果两边都是输出,谁也收不到数据,还容易烧。记住口诀:TX接RX,RX接TX,交叉连接。
3. STM32串口接收与数据帧解析
3.1 串口初始化与中断配置
我用的是标准库,因为很多教学和毕业设计还在用标准库,资料多,踩坑少。USART2的初始化步骤:使能GPIOA和USART2时钟,配置PA2为复用推挽输出(TX),PA3为浮空输入(RX),然后设置波特率9600、8位数据位、1位停止位、无校验。中断方面,我开了接收中断RXNE,每收到一个字节就进一次中断,把数据存到缓冲区。
这里有个关键点:波特率必须和传感器一致。红外PM2.5传感器常见的波特率是9600,也有4800的,拿到手册先确认。如果波特率不对,收到的全是乱码。我一般会在初始化之后先发一个测试指令(如果传感器支持),或者直接看接收到的数据有没有固定帧头,来判断波特率是否正确。
中断优先级方面,如果项目里还有定时器中断、OLED刷新,建议把串口接收中断设成较高优先级,防止数据丢失。但也不要设成最高,否则可能影响系统滴答定时器。我一般设成抢占优先级1,子优先级0。
3.2 数据帧格式与校验方式
红外PM2.5传感器的串口输出一般是主动上报模式,每隔1到2秒发一帧数据。帧格式常见的是:帧头0x42 0x4D(和某款激光传感器类似),然后是数据长度、数据内容、校验和。但不同厂家不一样,有的帧头是0xFF 0xAA,有的是0xA5 0x5A。我手上这个模块的帧格式是:帧头0xAA 0xBB,然后是低字节、高字节、校验和,共5个字节。校验和是前四个字节之和取低8位。
解析的时候,我建议用状态机的方式,而不是一次性把缓冲区读完。状态机的好处是能处理断帧、粘帧的情况。具体做法:定义几个状态,比如等待帧头1、等待帧头2、接收数据、校验。每进一次中断,根据当前状态决定下一步。这样即使数据流中间有干扰,也能重新同步。
3.3 数据滤波与浓度计算
原始数据拿到之后,不要直接显示。红外传感器的数据跳动比较大,尤其是风扇刚启动的时候。我一般做两级滤波:第一级是限幅滤波,如果本次值和上次值差超过一定阈值(比如50微克每立方米),就认为这次是干扰,丢弃;第二级是滑动平均,取最近5次的有效值做平均。这样显示出来的数据会平稳很多。
浓度计算方面,有的传感器直接输出浓度值,有的输出的是原始AD值,需要根据手册的公式换算。我手上这个模块输出的是浓度值,单位是微克每立方米,低字节在前,高字节在后。计算方式就是低字节加上高字节乘以256。如果手册给的是电压和浓度的关系曲线,那就需要先算电压,再查表或者用公式算浓度。
4. 完整实操流程与关键代码
4.1 工程搭建与时钟配置
新建工程的时候,我习惯用Keil5,芯片选STM32F103C8。时钟树配置成72MHz,外部晶振8MHz。为什么强调时钟树?因为串口波特率的计算依赖系统时钟,如果时钟配错了,波特率就会偏,数据就乱。标准库里面,SystemInit函数会帮你把时钟配好,但如果你自己改了晶振频率,就要手动改PLL倍频系数。
串口初始化的代码大概是这样:
void USART2_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART2, ENABLE); }这段代码里,GPIOA的时钟和USART2的时钟都要开,缺一不可。PA2配成复用推挽,PA3配成浮空输入,这是标准做法。中断优先级设成1和0,避免和系统滴答冲突。
4.2 中断服务函数与状态机实现
中断服务函数里,我只做一件事:把接收到的字节存到环形缓冲区,然后置一个标志位。解析放在主循环里做,这样中断时间短,不容易丢数据。状态机的实现大概是这样:
void USART2_IRQHandler(void) { if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { u8 data = USART_ReceiveData(USART2); ring_buf_write(data); USART_ClearITPendingBit(USART2, USART_IT_RXNE); } }主循环里,从环形缓冲区读数据,喂给状态机。状态机的状态定义:
typedef enum { STATE_HEAD1, STATE_HEAD2, STATE_DATA_L, STATE_DATA_H, STATE_CHECK } ParseState;每读一个字节,根据当前状态判断。比如在STATE_HEAD1时,如果字节是0xAA,就转到STATE_HEAD2;否则留在STATE_HEAD1。在STATE_CHECK时,计算校验和,如果正确,就把数据存到全局变量,否则丢弃,重新回到STATE_HEAD1。
4.3 数据输出与显示
数据解析出来之后,我一般做两件事:一是通过USART1打印到串口助手,方便调试;二是显示到OLED或者LCD上。串口打印用printf重定向就行,标准库里面重定向fputc到USART1。OLED显示的话,用I2C或者SPI驱动,显示浓度值和单位。
这里有个小技巧:如果浓度值跳动大,可以在显示之前再做一次四舍五入,比如显示整数。另外,如果浓度超过阈值,可以让LED闪烁或者蜂鸣器报警,这个在智能家居项目里很实用。
5. 常见问题与排查技巧实录
5.1 收不到数据怎么办
先检查硬件:传感器供电是否正常,TX和RX是否接反,电平匹配是否做了。用万用表量一下传感器TX脚在空闲时的电压,如果是5V,说明传感器在工作;如果是0V,可能传感器没启动。然后检查STM32这边:串口初始化是否正确,波特率是否匹配,中断是否使能。我遇到过有人忘了开USART2的时钟,结果初始化函数里写再多也没用。
还有一个容易忽略的点:如果用了电阻分压,电阻值选得太大,比如100k,信号边沿会非常缓,STM32可能识别不到。我建议分压电阻用10k和20k,或者4.7k和10k,不要超过47k。
5.2 数据乱码或跳变
乱码一般是波特率不对,或者时钟配置错了。先确认传感器的波特率,再检查STM32的系统时钟。如果时钟是72MHz,波特率9600,误差应该在2%以内。如果用了内部晶振,误差可能大一些,建议用外部晶振。
数据跳变的话,先看是不是电源干扰。传感器和STM32共用电源时,风扇启动瞬间会有电压波动,可能导致数据异常。可以在传感器VCC和GND之间并一个100uF电解电容和一个0.1uF陶瓷电容,能明显改善。另外,软件滤波也要做,前面说的限幅加滑动平均就很有效。
5.3 校验和总是对不上
校验和的计算方式一定要看手册。有的厂家是累加和取反,有的是异或,有的是累加取低8位。我手上这个模块是累加取低8位,但手册上写的是“校验和=0xAA+0xBB+低字节+高字节”,我一开始以为是取反,结果一直对不上。后来用串口助手抓了几帧数据,手动算了一下,才发现是直接累加。所以,拿到新模块,先用串口助手抓原始数据,手动验证校验和,再写代码。
5.4 中断优先级冲突
如果项目里还有定时器中断、外部中断,串口接收中断的优先级要合理安排。我遇到过串口中断被定时器中断打断,导致数据丢失的情况。解决办法是把串口接收中断的抢占优先级设得比定时器高,或者把解析放到主循环,中断里只存数据。另外,如果用了DMA接收,可以进一步降低中断频率,但DMA配置对新手来说稍复杂,建议先把中断方式跑通再考虑DMA。
5.5 传感器预热与校准
红外PM2.5传感器上电后需要预热,一般1到3分钟数据才稳定。刚上电时浓度值可能偏高或者偏低,这是正常现象。如果项目要求高精度,可以在预热完成后做一次校准,比如在洁净空气中读一个基准值,然后后续数据减去这个基准。但要注意,红外传感器的一致性不如激光传感器,校准只能针对单个模块,换一个模块就要重新校准。
6. 项目扩展与个人经验
这个项目跑通之后,可以扩展的方向很多。比如加一个SD卡模块,把浓度数据存成CSV文件,方便后期分析;或者加一个ESP8266,把数据上传到云平台,做远程监测;再或者加一个继电器,当浓度超过阈值时自动启动空气净化器。我自己的鱼缸项目里,就用这个方案监测空气质量,联动风扇换气,效果还不错。
最后分享一个我在调试时的小技巧:如果手头没有串口助手,可以用STM32的USB虚拟串口功能,把USART1的数据通过USB转发到电脑,这样只需要一根USB线就能看数据,不用额外接USB转TTL模块。配置USB虚拟串口稍微麻烦一点,但网上教程很多,照着做就行。另外,Keil5的软件仿真也能看串口输出,但实时性不如硬件,调试传感器的时候还是建议用硬件串口。
踩过的坑主要集中在校验和和电平匹配上,这两个地方多花点时间,后面就很顺了。传感器手册一定要看,不要凭经验猜,不同厂家的协议差异很大。