ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32红外PM2.5传感器ADC采集与标定实战

STM32红外PM2.5传感器ADC采集与标定实战 1. 为什么选红外PM2.5传感器而不是激光方案做空气质量监测类项目传感器选型是第一个绕不开的岔路口。市面上能买到的PM2.5传感器大致分两类红外散射式和激光散射式。激光方案精度高、能测到0.3微米颗粒但价格通常在几十到上百元而且需要风扇持续抽气功耗和体积都上去了。红外方案用的是红外LED加光电接收管靠颗粒物对红外光的散射强度反推浓度成本能压到十块钱以内结构简单寿命也长。我这次选红外方案核心原因是项目定位在室内空气质量粗略监测不需要出检测报告级别的数据只要能把“空气好、一般、差”三档区分开就够了。另外红外传感器有个天然优势它对烟雾、灰尘这类大颗粒响应很灵敏做烟雾报警联动反而比激光方案更合适因为激光方案容易把水汽也当成颗粒物误报。不过红外方案有几个必须提前知道的坑。第一它输出的是模拟电压不是数字浓度值你得自己标定换算。第二它的读数受温湿度影响明显湿度超过80%时数值会虚高。第三不同批次的传感器一致性差同一型号换一个模块同样环境下输出电压可能差0.3V。所以如果你要做多节点组网每个节点都得单独标定不能共用一条曲线。STM32这边我选的是F103C8T6最小系统板原因很简单这颗芯片ADC资源够用12位、多通道、串口多、价格便宜而且标准库和HAL库资料都全。红外传感器的模拟输出接到STM32的ADC引脚上用DMA搬运数据CPU几乎不占资源。如果你手头是F4或者G0系列逻辑完全一样只是寄存器配置和库函数调用方式不同。提示红外PM2.5传感器模块通常有5根线——VCC、GND、AO模拟输出、DO数字阈值输出、LED控制。DO引脚可以不用但AO必须接到STM32的ADC通道上不能接普通GPIO。2. 红外PM2.5传感器的内部结构与输出特性2.1 光学腔体里的散射原理拆开一个红外PM2.5模块你能看到里面有个黑色的塑料腔体腔体一侧是红外发射管另一侧是光电接收管两者之间有个遮光挡板防止直射光串过去。空气从腔体的小孔自然扩散进去颗粒物经过光路时把红外光散射到接收管上接收管输出的电流经过运放转换成电压。颗粒物越多散射光越强输出电压越高。这个结构决定了它的响应速度比较慢通常需要30秒到1分钟才能稳定反映当前空气状态。如果你拿它做实时报警得加个滑动平均滤波否则数值会跳得你怀疑人生。另外腔体不能堵安装时别把进气孔朝下或者贴着墙面留出至少2厘米的流通空间。2.2 输出电压与浓度的对应关系红外模块的AO引脚在洁净空气中输出大约0.3V到0.6V随着颗粒物浓度升高电压逐渐上升在重度污染环境下能到3.5V以上。但这个关系不是线性的低浓度段斜率小高浓度段斜率大近似一条指数曲线。我实测过三款不同厂家的红外模块在同一个密闭房间里点同一根烟输出电压从0.4V升到2.8V的时间分别是45秒、70秒和110秒。差异主要来自腔体设计和运放增益。所以不要迷信厂家给的曲线自己标定才是正道。标定方法很简单找一个已知浓度的环境比如用香烟烟雾在密闭箱里制造梯度同时用一台靠谱的激光检测仪做参考记录多组电压浓度数据然后用Excel拟合。我一般用二次多项式拟合R²能到0.95以上。如果手头没有参考仪器可以用“洁净空气零点香烟满量程”两点法粗略标定精度差一些但够用。2.3 温湿度对读数的影响红外方案对湿度特别敏感。我在南方梅雨季做过测试同一房间同一时段湿度从55%升到85%传感器输出电压从0.8V涨到1.6V但实际PM2.5浓度并没有明显变化。原因是水汽在红外波段也有散射传感器分不清是颗粒物还是水滴。解决办法有两个一是加温湿度补偿用DHT22或者SHT30测出湿度在软件里做修正二是加加热电阻让腔体温度高于环境温度减少水汽凝结。我选的是软件补偿因为硬件加热会增加功耗和成本。补偿公式我用的经验式修正后电压 原始电压 / (1 0.012 * (湿度 - 50))湿度低于50%时反向修正。这个系数是我自己拟合的不同模块可能需要微调。3. STM32的ADC采集链路怎么搭才稳3.1 引脚选择与硬件连接STM32F103C8T6有10个外部ADC通道分布在PA0到PA7和PB0、PB1。我习惯把传感器AO接到PA1因为PA0通常留给按键或者唤醒源。接线时注意三点第一传感器和STM32必须共地否则ADC读数会飘第二AO输出阻抗一般在10kΩ左右STM32的ADC要求输入阻抗小于50kΩ直接接没问题但如果走线超过20厘米最好加一个电压跟随器第三传感器供电用3.3V还是5V要看模块规格很多红外模块标称5V供电但AO输出最大3.5V左右接STM32的3.3V ADC引脚是安全的不过保险起见可以串一个1kΩ电阻限流。我画过一版PCB把传感器和STM32放在同一块板上ADC走线尽量短下面铺地铜皮读数稳定性比杜邦线连接好很多。如果你用杜邦线尽量让AO线远离电机、继电器这类干扰源否则ADC值会周期性跳动。3.2 ADC配置为什么用DMA加定时器触发STM32的ADC有三种工作模式单次转换、连续转换、扫描模式。我推荐用定时器触发DMA搬运的方式。具体来说用TIM2产生一个1kHz的更新事件触发ADC规则通道转换转换结果由DMA自动搬到内存数组里。这样CPU完全不用管ADC只在需要的时候去读数组平均值就行。为什么不用连续转换因为连续转换模式下ADC一直在跑功耗高而且数据更新太快你根本来不及处理。用定时器触发可以精确控制采样率比如你要做50Hz工频滤波采样率设成1kHz就刚好。DMA搬运则避免了每次转换都进中断减少CPU开销。配置步骤大致如下先开GPIOA和ADC1的时钟把PA1设成模拟输入然后配置ADC为独立模式、规则通道、外部触发源选TIM2的TRGO事件接着开DMA1通道1外设地址填ADC_DR寄存器内存地址填数组循环模式最后配置TIM2为向上计数ARR设成7199PSC设成0这样72MHz主频下更新频率是10kHz再通过TIM2的TRGO输出分频到1kHz。具体寄存器操作可以参考标准库的ADC_Init和DMA_Init函数。3.3 滤波算法滑动平均加中值滤波原始ADC数据即使加了硬件滤波还是会有±20个LSB的跳动。我一般用中值滤波滑动平均两级处理。中值滤波取最近5个采样点排序取中间值能干掉脉冲干扰滑动平均取最近16个点的均值平滑随机噪声。两级下来数据波动能压到±3个LSB以内。代码实现上中值滤波用一个5元素数组每次新数据进来就移位覆盖然后冒泡排序取中间。滑动平均用一个16元素环形缓冲区维护一个累加和新数据进来减去最老的数据加上新数据再除以16。这样计算量很小F103跑起来毫无压力。注意滑动平均的窗口不要设太大否则响应会变慢。16个点在我1kHz采样率下对应16毫秒加上传感器本身的响应时间整体延迟可以接受。如果你采样率更低比如100Hz窗口就要相应减小。4. 从ADC值到PM2.5浓度的换算与标定4.1 电压换算与参考电压选择STM32的ADC是12位的读数范围0到4095对应电压0到VDDA。F103的VDDA通常接3.3V但实际可能在3.28V到3.32V之间波动。如果你直接用3.3V算误差会有1%左右。我一般用内部参考电压来校准STM32F103内部有一个1.2V的基准源连到ADC的通道17上。先读通道17的ADC值算出实际VDDAVDDA 1.2 * 4095 / adc_ref然后用这个VDDA去算传感器电压。这一步很多人会忽略但如果你用USB供电3.3V稳压器的输出会随负载变化不校准的话浓度读数会跟着USB线长变化。我实测过同一块板子换一根USB线ADC读数能差30个LSB换算成浓度就是10微克每立方米左右的偏差。4.2 分段线性拟合的实操前面说了红外传感器的电压-浓度曲线是非线性的我一般把它分成三段做线性拟合低浓度段0.3V到1.0V、中浓度段1.0V到2.0V、高浓度段2.0V到3.5V。每段用两个标定点确定斜率和截距代码里用if-else判断当前电压落在哪一段然后套对应的公式。标定数据我整理成了一张表你可以参考这个格式自己填电压区间(V)参考浓度(μg/m³)斜率截距0.3-1.00-5071.4-21.41.0-2.050-150100-502.0-3.5150-500200-250这张表是我用某款红外模块在密闭箱里标出来的不同模块数值会差很多必须自己重新标。标定时注意箱内湿度控制在50%左右温度25摄氏度否则数据不可比。4.3 温湿度补偿的代码实现补偿部分我写了一个简单的函数输入原始电压和湿度输出修正电压。湿度用DHT22读取精度±2%够用了。代码逻辑是如果湿度大于60%按比例下调电压如果湿度小于40%按比例上调。中间段不补偿。系数我用的0.012你可以根据自己模块的实测数据调整。float compensate_humidity(float voltage, float humidity) { if (humidity 60.0f) { return voltage / (1.0f 0.012f * (humidity - 50.0f)); } else if (humidity 40.0f) { return voltage * (1.0f 0.008f * (40.0f - humidity)); } return voltage; }这个函数我放在ADC数据处理之后、浓度换算之前调用。实测在湿度85%的环境下补偿后读数比不补偿低了约35%更接近激光检测仪的参考值。5. 串口输出与上位机联调的细节5.1 串口配置与数据帧格式STM32的USART1接PA9和PA10波特率我习惯用1152008位数据位1位停止位无校验。数据帧我定义成简单的文本格式$PM2.5,浓度值,电压值,湿度值#每秒钟发一次。用文本而不是二进制方便用串口助手直接看也方便Python上位机解析。发送用printf重定向最省事在Keil里勾选Use MicroLIB然后重写fputc函数就行。注意发送频率不要太高115200波特率下每秒发100帧文本会占满带宽1Hz足够了。如果你要接OLED显示可以把数据同时写到屏幕和串口互不影响。5.2 上位机Python脚本快速搭建上位机我用Python的pyserial加matplotlib做了个实时曲线。核心代码就十几行打开串口循环读一行用split切出浓度值追加到列表然后plt.plot刷新。为了不卡界面我用FuncAnimation做定时刷新每200毫秒更新一次曲线。这个脚本的好处是你可以一边调传感器一边看曲线标定的时候特别方便。比如你点一根烟看着曲线从0.5V爬到2.5V同时记录参考仪器的读数标定点就出来了。如果你不会Python用串口助手的“波形显示”功能也能凑合看但没法存数据做拟合。5.3 常见联调问题排查串口调不通的情况我遇到最多的是三种第一TX和RX接反这个新手最容易犯记住STM32的TX接USB转串口的RX第二波特率不匹配串口助手设成9600而代码里是115200出来全是乱码第三共地问题如果STM32和USB转串口模块分别供电不共地的话数据会丢包。还有一种隐蔽的问题STM32的USART1默认引脚是PA9和PA10但如果你用了重映射或者别的外设占用了就得改配置。我建议在main函数开头加一句串口自检发一个“UART OK”字符串确认通了再往下跑。6. 实测中遇到的三个坑和解决过程6.1 传感器预热时间被低估第一个坑是预热。我以为红外传感器上电就能用结果发现前30秒读数从0.1V慢慢爬到0.5V如果这时候就采数据浓度会显示成负数。后来查资料才知道红外发射管需要达到热平衡接收管的暗电流也会随温度漂移。解决办法很简单上电后延时60秒再开始采集或者在代码里加一个预热标志位前60秒的数据直接丢弃。这个预热时间在冬天会更长因为环境温度低腔体升温慢。我在10摄氏度环境下测过需要90秒才能稳定。所以如果你做的是电池供电的便携设备得考虑这个预热功耗要么用低功耗模式间歇工作要么选带加热的模块。6.2 ADC读数被电机干扰的排查第二个坑是干扰。我把传感器和一个小风扇接在同一块板上风扇一转ADC读数就跳50个LSB。一开始以为是电源问题换了LDO稳压器没解决。后来用示波器看AO引脚发现风扇换向时有个几十毫伏的尖峰。原因是风扇电机和传感器共用了一条地线电机电流在地线电阻上产生压降串到了传感器地上。解决办法是分开走地电机的地线单独回到电源负极传感器和STM32的地线走另一条路径最后在电源入口处单点汇合。改完之后ADC跳动降到5个LSB以内。如果你板子已经画好了可以在传感器AO和地之间并一个0.1微法电容也能压掉一部分高频干扰。6.3 长时间运行的数据漂移第三个坑是漂移。我让设备连续跑了72小时发现浓度读数每天下午都会比上午高20%左右但房间里并没有明显污染源。排查后发现是温漂下午室温比上午高3到5摄氏度红外接收管的暗电流增大导致输出电压整体抬升。解决方法是加温度补偿。我在传感器旁边贴了一个NTC热敏电阻用STM32的另一个ADC通道读温度然后在软件里做一阶线性补偿。补偿系数我通过实验确定温度每升高1摄氏度电压减去0.005V。加上这个补偿后72小时漂移从20%降到了5%以内。提示如果你不想加NTC也可以用STM32内部温度传感器但它的精度只有±1.5摄氏度而且受芯片自身发热影响补偿效果不如外置的。7. 这个方案还能怎么扩展7.1 加OLED做本地显示现在数据只往串口发没有本地显示用起来不方便。加一个0.96寸OLEDSSD1306驱动I2C接口就能把浓度、电压、湿度都显示出来。接线很简单SCL接PB6SDA接PB7供电3.3V。代码用现成的u8g2库或者自己写I2C驱动都行。我一般显示三行第一行PM2.5浓度第二行电压和湿度第三行状态好/一般/差。刷新率不用太高2Hz就够了否则I2C总线占用太多时间。OLED的功耗很低全亮也就20毫安左右电池供电也能撑很久。如果你要做便携设备可以加一个按键切换显示页面比如按一下看历史最大值再按一下看平均值。这些逻辑用状态机实现代码不复杂。7.2 接继电器做联动控制红外传感器对烟雾响应快很适合做排风扇联动。STM32的某个GPIO接继电器模块的IN引脚浓度超过阈值就拉高继电器吸合排风扇启动。阈值我一般设两档浓度超过75微克每立方米启动低速档超过150微克每立方米启动高速档。加个迟滞区间防止频繁启停比如启动阈值75停止阈值50。继电器模块注意选光耦隔离的否则线圈反向电动势会干扰STM32。如果继电器和STM32共用电源记得在继电器线圈两端并一个续流二极管这个二极管很多模块已经自带了买的时候看清楚。7.3 用ESP8266上传数据到本地服务器如果你想做远程监测可以加一个ESP8266模块用AT指令或者直接写固件都行。STM32通过串口2和ESP8266通信把数据打包成JSON发到本地服务器。服务器用树莓派或者旧笔记本跑一个简单的HTTP服务存到SQLite数据库里再用网页展示历史曲线。这个扩展的关键是串口2的配置和串口1一样只是引脚换成PA2和PA3。注意ESP8266的供电要足峰值电流能到300毫安最好单独用一个LDO别从STM32的3.3V取电。数据上传频率不用太高5分钟一次就够了PM2.5浓度变化没那么快。7.4 低功耗改造的思路如果要做电池供电的版本功耗是大头。STM32F103在72MHz全速运行时大概30毫安加上传感器和OLED整体能到60毫安。改造思路是间歇工作用RTC定时唤醒每30秒采一次数据采完就进STOP模式电流能降到1毫安以下。传感器也断电用MOS管控制电源采之前提前60秒上电预热。这样平均电流能压到5毫安左右2000毫安时的电池能撑十几天。如果你用STM32L系列功耗还能再低一个数量级。不过低功耗模式下ADC和串口都要重新初始化代码复杂度会上去建议先把全速版本跑通再改。我在实际使用中发现红外PM2.5传感器虽然精度不如激光方案但胜在便宜、耐用、对烟雾敏感做室内空气质量粗略监测和排风联动完全够用。标定和温湿度补偿是绕不过去的两步花点时间把这两件事做好读数稳定性会有质的提升。STM32这边ADC加DMA的配置一旦跑通后面换传感器或者加通道都很方便算是一次投入长期受益。
返回列表