ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32红外PM2.5传感器实战:硬件接法与ADC时序全解析

STM32红外PM2.5传感器实战:硬件接法与ADC时序全解析 我之前在家捣鼓空气净化器的时候一直想实时看看滤网到底管不管用。恰巧手上还有一个STM32最小系统板和几个现成的传感器模块就决定自己接一个红外PM2.5传感器把颗粒物浓度直接打印到串口和屏幕上。真正做下来才发现这类传感器接线并不难难的是供电、采样时序和校准这三件事。这篇文章把选型、硬件连接、280μs采样时序、软件实现、浓度换算和踩坑过程完整写一遍打算做环境监测盒子、新风联动或者毕业设计里涉及颗粒物检测的朋友可以直接拿这份流程当参考。1. 项目目标与红外PM2.5的选型逻辑1.1 激光传感器和红外传感器的取舍PM2.5传感器按原理分主要有两类一类是激光散射式比如PMS5003、PMS7003另一类是红外散射式比如GP2Y1010AU0F、DSM501A。名字里虽然都有“散射”但工作方式差别很大。激光传感器用半导体激光器做光源光束窄、单色性好能根据散射光的角度和强度做比较细的粒径区分所以可以直接输出PM1.0、PM2.5、PM10的浓度值还能给出质量浓度。红外传感器用的是红外LED加光电接收管结构简单光线散射后的信号经过放大后输出一个模拟电压本质上只能告诉你“空气里颗粒物多了还是少了”再通过一个经验公式估算PM2.5浓度。从成本上看激光传感器模块普遍在60到150元之间还自带风扇体积和功耗都更大红外传感器模块只要十几二十元体积小巧工作电流低。如果你做的是室内空气质量指示装置、净化器联动、新风系统启停控制这类“看趋势、看变化”的项目红外传感器的精度完全够用。但如果你要做科研级测量或者需要准确的颗粒物粒径分布数据红外方案就不合适了。对比项红外传感器GP2Y1010AU0F激光传感器PMS5003类价格区间约10-30元约60-150元体积功耗小、约20mA较大、含风扇约100mA输出方式模拟电压需自己换算串口直接输出各粒径浓度粒径区分能力粗糙只能整体估算可细分PM1.0/PM2.5/PM10长期稳定性红外LED寿命长无风扇风扇有机械磨损但精度高适合场景低成本趋势监测、联动控制数据采集、精密对比我最终选了GP2Y1010AU0F一方面是因为手上正好有这颗料另一方面是它属于模拟输出接STM32的ADC特别自然整个过程能把GPIO控制、定时器延时、ADC采样、滤波和标定全部串起来适合作为嵌入式入门项目。1.2 用在哪、谁来抄作业这个项目最大的价值不是做出一块“能读数”的板子而是给你一个完整的颗粒物检测系统原型。我做完之后用它做了三件事放在空气净化器出风口看滤网效果在书房监测白天和晚上的浓度波动以及接到家里的中控屏上做新风联动演示。对于正在做STM32毕业设计的同学这个项目也特别合适。红外PM2.5传感器涉及模拟信号采集、定时时序、ADC采样、串口输出和数据显示知识密度大但难度适中答辩时能讲的东西很多。对单纯想给智能家居加传感器的朋友来说这篇文章里的接线和代码可以直接抄不需要自己从头扣驱动。2. 引脚定义、电气特性与最小硬件连接2.1 GP2Y1010AU0F引脚到底怎么接GP2Y1010AU0F是夏普推出的一款红外粉尘传感器6个引脚我按数据手册和实际接线经验整理成下面的表格。注意不同厂家做的转接板可能把引脚顺序改了焊接前一定要对着丝印确认别只靠颜色认线。引脚编号名称典型接法1V-LED接5V电源V-LED对GND并联220μF电容2LED-GND接GND3LED通过150Ω电阻接5V同时接STM32的GPIO控制脚4S-GND接GND5Vo模拟输出经分压电阻接STM32的ADC引脚6Vcc接5V电源Vcc对GND并联0.1μF去耦电容数据手册的典型应用电路里Pin1和GND之间要放一个220μF电容Pin3通过150Ω电阻接到5VPin3同时作为LED驱动控制点。当STM32的GPIO输出低电平时内部LED导通点亮输出高电平时LED熄灭。这个“低电平点亮”反直觉我第一次用普通的“高电平使能”思维去写代码结果LED一直不工作。供电方面传感器内部检测电路需要5V供电Vcc接5VPin6和GND之间加0.1μF陶瓷电容滤高频噪声。如果你用的是5V转接板板上一般已经集成好了但买裸片回来就必须自己补这两个电容。2.2 电压匹配、供电和去耦GP2Y1010AU0F的模拟输出范围大约在0.5V到3.6V之间而大部分STM32的ADC参考电压是3.3V。直接接上去超过3.3V时采样结果会直接顶到4095信息丢失。我选择用两枚电阻做分压传感器的Vo串一个1kΩ电阻到STM32的ADC引脚ADC引脚对GND再并一个10kΩ电阻。这样分压系数是10/(101)0.909传感器最大输出3.6V时ADC引脚电压约3.27V安全落在3.3V以内。随后在软件里把读到的电压除以0.909就能还原出传感器真实的输出电压。分压电阻的精度直接影响浓度计算尽量用1%精度的电阻普通10%电阻会让零点产生偏移。供电是另一个容易踩坑的地方。传感器内部LED点亮瞬间电流变化很快如果直接从开发板的3.3V引脚取电电压会被拉垮ADC读数像心电图一样上下乱跳。我的做法是单独用一个低压差5V LDO给传感器供电主控和传感器共地但电源路径分开。如果手头只有USB 5V也要保证在传感器电源引脚附近放220μF大电容做储能0.1μF小电容做高频去耦。接线长度尽量控制在20厘米以内。Vo是高阻抗模拟信号线太长容易被环境干扰尤其是在电机、继电器附近。如果项目结构限制必须把传感器放远可以在Vo后级加一级电压跟随器再把放大后的信号送给ADC。3. 280μs采样时序的来龙去脉3.1 为什么必须踩在280μs这个点上红外PM2.5传感器的内部有一个红外LED和一个光电接收管。LED发出的光在检测腔里被颗粒物散射接收管把散射光的强度转成电流再经过放大电路变成电压。颗粒物浓度越高散射光越强输出电压就越高。问题是LED光并不是一打开就瞬间稳定接收管的输出信号也有一个建立过程。从数据手册的波形图可以看到LED点亮之后输出信号会逐渐爬升大约在280μs附近达到峰值然后随着LED关闭迅速回落。如果我们采样太早信号还在上升沿结果偏低采样太晚脉冲已经开始衰减结果也不稳定。而且这里的“早”和“晚”都是微秒级别的偏差10μs的误差就能让测到的电压差出一截。正确的做法是每隔10ms给LED一个宽度为0.32ms320μs的脉冲在脉冲开始后280μs处用ADC采集一次电压。也就是说整个采样周期是10msLED真正点亮的时间只有320μs剩余时间都用来复位和稳定。这样既不会持续发热LED也保证每次采样都落在同一个信号特征点上。3.2 STM32里怎么制造这条时间轴在STM32上控制时序很多人第一反应是HAL_Delay但它只能做到毫秒级对280μs这种微秒级窗口无能为力。我常用的方案有两种一是定时器微秒延时二是DWT时钟周期计数器。DWT方案更简单代码量小。STM32Cortex-M3内核自带一个32位的周期计数器DWT-CYCCNT频率等于系统主频在72MHz主频下每微秒计72次。用它做延时不受中断调度影响精度很高。void DelayUs_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } void DelayUs(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks) { } }有了微秒延时完整的单次采样流程就是先拉低GPIO点亮LED延时280μs启动ADC转换并读取结果然后拉高GPIO熄灭LED。我在代码里把这个流程封装成一个函数每次调用返回一个ADC原始值。如果用了定时器也可以用定时器比较中断来做更精准的节拍但DWT已经足够应付这个项目。需要提醒一下不要在点亮LED之后调用HAL_Delay(1)哪怕只延时1ms也会错过280μs窗口。网上很多半抄半改的代码把这里写错读出来的数据要么恒定一个值要么波动大得离谱。4. 完整软件实现定时微秒、ADC与滤波4.1 工程结构HAL库还是标准库STM32的开发方式大致有寄存器、标准库、HAL库三种。寄存器最直白但外设多时配置代码很啰嗦标准库封装程度适中很多老工程师习惯用HAL库配合CubeMX能快速初始化工程代码生成效率高。这个项目我用的是HAL库加CubeMX原因是ADC和GPIO初始化只需要在图形界面点几下生成的代码容易看懂后期换引脚的改动成本也低。工程里需要用到的外设就三个PA0做GPIO输出控制LEDPA1配置为ADC1的输入通道USART1做调试输出。时钟配置72MHz其他外设如果不用可以不分配。CubeMX生成工程后再把DWT延时函数加到main.c里。4.2 ADC配置和采样时间ADC采样最关键的参数是采样时间。STM32的ADC转换分为采样阶段和转换阶段采样阶段是给内部采样电容充电的过程时间越短对信号源内阻越敏感。因为GP2Y1010AU0F输出端串联了1kΩ分压电阻信号源阻抗不低采样时间太短会导致转换结果偏低。我把采样时间配置成239.5周期。在ADC时钟14MHz时239.5个周期大约17μs足够让内部电容稳定充到信号电压。转换完成后用阻塞方式读取结果。这个项目一共只需要采集一个通道不需要DMA。void PM25_ADC_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); } uint16_t PM25_ReadSample(void) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); DelayUs(280); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t adc HAL_ADC_GetValue(hadc1); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); return adc; }这里有个容易被忽略的细节每次采样结束要记得把LED熄灭而不是等下一次循环再关。关闭LED之后信号通道会有一段恢复时间正好被下一次10ms周期的等待时间覆盖形成一个稳定的采样节拍。4.3 数据滤波与流程状态机单次读到的ADC值波动很大因为空气中的颗粒物分布本来就不均匀加上气流变化传感器输出电压会随机起伏。我试过直接打印单次值屏幕上完全是噪声。解决办法是滑动平均滤波每10ms读取一次数据取最近10次平均值作为当前时刻的浓度。这样既保留了实时性又削掉了大部分毛刺。如果想做更平滑的趋势曲线可以加大窗口到30次但响应速度会变慢颗粒物浓度突然升高时读数会有滞后。对于净化器联动这种应用10点到20点的窗口比较合适。下面是一个一秒输出一个数值的示例主循环while (1) { sum PM25_ReadSample(); count; HAL_Delay(10); if (count 100) { uint32_t avg_adc sum / count; float vout PM25_GetVoltage(avg_adc); float density PM25_ToDensity(vout); printf(PM2.5: %.1f ug/m3\r\n, density); sum 0; count 0; } }串口打印之前需要重定向printf到UART1Keil里勾选Use MicroLIB并实现fputc函数int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }输出格式里我是用“ug/m3”代表微克每立方米读者做项目时注意自己平台的编码格式中文逗号在串口终端里有时会乱码。5. 电压转浓度的换算与校准5.1 出厂标定的线性模型GP2Y1010AU0F数据手册给出的输出电压和粉尘浓度近似线性关系但不是一个特别统一的“标准公式”不同批次传感器个体差异明显。网上流传较广的经验式是浓度(mg/m³) 0.17 × Vout(V) - 0.10换算成微克每立方米就是浓度(μg/m³) (0.17 × Vout - 0.10) × 1000比如传感器输出电压1.5V算出来浓度约155μg/m³输出3.0V浓度约410μg/m³。如果Vout小于0.6V直接按0处理因为式子里会出现负值那是无意义的。实际使用时要先把我前面提到的分压系数还原回去再套公式。完整换算函数float PM25_GetVoltage(uint16_t adc) { // 3.3V参考12位ADC float vadc 3.3f * adc / 4095.0f; // 还原传感器输出分压系数10/11 float vout vadc * 11.0f / 10.0f; return vout; } float PM25_ToDensity(float vout) { if (vout 0.6f) return 0.0f; float mg 0.17f * vout - 0.10f; return mg * 1000.0f; }这里面的0.17和0.10是估算系数只适合“先跑通”的阶段。真要拿它做环境评价必须校准。5.2 自己做零点和多点校准校准的第一步是确定零点。你以为“干净空气”是0但传感器内部总会有轻微的暗电流和电子噪声可能在干净环境下仍然输出0.5V到0.7V的底噪。我的做法是把HEPA滤网直接封住传感器进气口连续运行10分钟取这段时间输出电压的平均值记作V0。这个V0不是从公式硬套来的而是根据你的板子、电源、电阻精度实测出来的。第二步是确定斜率。找一台激光PM2.5传感器作为参考把两台设备放在同一个空气环境中同步采集数据。每隔一分钟记录一次两边的读数连续记录半小时以上尽量覆盖至少两三个不同的浓度区间比如关窗状态、开加湿器状态、净化器运行状态。把参考设备的浓度值C和红外传感器的输出电压V做最小二乘拟合得到一个更符合自己传感器的线性关系C k × (V - V0)。以后就用这个k和V0计算。我在实际操作中拟合出来的k和网上经验式的0.17差别不大但V0比预期的0.6V高一点大约0.67V。如果不修正这个偏移干净环境下浓度会一直显示40多μg/m³很容易让人怀疑设备坏了。校准过程别在刚点燃蜡烛、炒菜油烟重、湿度爆表的时候做这些环境下颗粒物成分复杂红外传感器的响应和激光传感器不在同一个逻辑上拟合出来的曲线会失真。5.3 温湿度带来的变数红外传感器对湿度相当敏感。相对湿度超过70%时空气中的颗粒物会吸湿变大散射截面增加同一真实浓度下传感器读数可能偏高30%以上。我在潮湿天气里测过没开净化器时红外读数能到100μg/m³参考激光设备只有70多。湿度修正没法做到完全精确因为不同粒径吸湿特性不一样。比较务实的做法是接入温湿度传感器当RH大于70%时对读数做一个经验衰减修正后浓度 原始浓度 ÷ (1 0.3 × (RH - 70%) / 30)这个公式不是标准公式只是我在固定场景下试出来的近似补偿。校正后的数据在趋势上有明显提升但绝对精度仍然不能和激光设备比。项目中如果只做联动开关阈值设计建议留出20%的滞回区间避免在临界点频繁开关设备。6. 实测结果与踩坑记录6.1 同一时间窗的对比测试我在书房里把GP2Y1010AU0F和一台PMS5003放在相距20厘米的位置连续运行两个小时选出一段有代表性的数据对比如下时间点红外传感器读数(μg/m³)激光参考读数(μg/m³)偏差14:05383411.8%14:35726314.3%15:05857414.9%15:35464112.2%16:05312810.7%最明显的特征是两个设备的趋势基本同步。红外数据虽然始终偏高10%到15%但波峰波谷的位置和时间都能对上。这就是我前面说的“看趋势足够看绝对值差一点”。如果你做的是空气净化器联动完全可以用红外传感器因为净化器启动后读数下降的曲线是可信的。6.2 三个伪装成“设备坏了”的坑第一个坑是LED控制极性接反。我一开始按常规思维写代码GPIO输出高电平点亮LED结果等了半天读数一直是0.002几伏。后来对照数据手册才发现这个传感器是低电平使能GPIO必须拉低LED才点亮。把代码里的GPIO_PIN_SET和GPIO_PIN_RESET对调之后数据立刻正常。第二个坑是供电不足导致读数乱跳。我最初为了图方便直接用STM32开发板上的3.3V引脚给传感器检测电路供电5V则从USB取电。看似有电但LED脉冲瞬间电流抽动让模拟信号一路抖动ADC连续采样出来像地震波。后来把传感器供电和主控分开5V用LDO单独供给并检查了220μF电容方向无误波形才稳定下来。第三个坑是分压电阻精度不够。我随手拿了两枚普通电阻做分压算出来的浓度和参考激光传感器偏差大到30%。换用1%精度电阻并实测分压点电压后偏差才缩小到15%以内。不要小看这两个电阻它们直接决定你反推Vout的准确性是校准链条里最基础的一环。6.3 从串口到OLED再到云端的扩展当串口能稳定输出浓度值之后其他应用就顺理成章了。想本地显示接一块0.96寸OLED在STM32上开个软件模拟SPI就能画出一个实时曲线比纯粹看数字直观很多。想联网报警可以把串口接到ESP8266这类WiFi模块上通过AT指令把浓度值推送到自己的服务器或MQTT平台。我还见过有人直接用一片STM32收集多路传感器数据按浓度阈值控制继电器驱动新风机的开关整个系统就这么搭起来了。如果你也打算自己搭一套我的建议是先别急着优化算法拿到传感器第一件事就是量零点和比对标定。供电稳、时序准坑就已经少了一大半剩下的交给滤波和趋势观察就行。
返回列表