☰
西电B测雾霾检测.zip实战:PM2.5传感器到屏幕的完整测量链路
2026/10/22 15:52:49 网站建设 项目流程

简介:西电B测雾霾检测.zip是西安电子科技大学雾霾检测项目的前端工程包,面向环境科学、前端可视化与uni-app开发者,围绕空气中PM2.5、PM10等污染物浓度数据的展示需求,帮助理解雾霾监测从数据采集、接口对接到图表呈现的完整链路。压缩包共73个文件,大小约10.51MB,其中包含41个JS、4个Vue、6个JSON、8个MD以及多张JPG/PNG图片素材,JS/Vue构成页面逻辑与交互,JSON保存项目配置,MD提供说明文档,图片用于界面展示。目前已有513人学习/下载,适合作为小型可视化项目参考。从目录可见,项目基于uni-app搭建,内置ECharts可视化组件、Axios网络请求封装和完整页面结构,可支撑从数据请求到图表渲染的完整流程;同时附带依赖锁定文件与配置文件,便于还原开发环境,尤其适合想快速上手数据可视化或多端开发的学习者。

1. 西电B测雾霾检测.zip 里装的不只是代码:一条从传感器到屏幕的测量链路

拿到西电B测雾霾检测.zip这种命名的压缩包,如果不先看里面的文件清单,很容易踩进同一个坑:打开工程就烧录,屏幕一亮就以为测出的数能用。其实以“西电B测”为代号的这个实践项目,是一条完整的测量链路,从激光散射粉尘传感器采回原始脉冲,到主控里滤波、换算浓度和 AQI,再到 OLED 显示、串口上报和 SD 卡存储。它解决的问题很具体:在没有实验室设备的环境里,用成本可控的硬件得到可信的 PM2.5 数据,并验证标定。这里按这条链路的顺序拆,从选型、解压、烧录讲到五个高频踩坑点,再给一套可控尘源标定法,照着做一遍,就能判断这套方案到底值不值得继续投入。

2. 硬件链路与选型逻辑:激光散射传感器、主控和浓度换算怎么配合

雾霾检测的硬件链路不长,但每一环的选型都会直接影响后面的调试工作量。从传感器输出方式到主控型号,再到浓度与 AQI 的换算,这三步决定了你在 B测场景里是三天跑通还是三周跑通。

2.1 激光散射粉尘传感器为什么是标配:PWM输出与串口输出的取舍

雾霾检测的核心器件是激光散射式粉尘传感器。原理不算复杂:内部风扇把空气抽进暗室,激光打在尘粒上产生散射光,光电二极管把光信号转成电脉冲,脉冲经过放大整形后,最终输出为与浓度相关的信号。所以整条链路的第一道选择,不是主控,而是传感器的输出接口方式。

常见做法是两类传感器。一类是 PWM 脉宽输出,引脚输出的高电平时间与粉尘浓度成正比,主控需要用定时器输入捕获测量高电平宽度,再按数据手册的曲线换算浓度。这类方案成本低,但对时基精度敏感,风扇转速波动会直接影响占空比,B测这种需要横向对比数据的场景,每颗传感器都要单独查曲线,否则偏差互相叠加。另一类是 UART 串口输出,传感器内部已经完成光电转换和浓度计算,主动向主控发固定格式的帧,典型结构是帧头 0x42 0x4D,后面跟着 PM2.5 和 PM10 浓度值。价格高一些,但免去了大量标定工作,是实践项目里更常见的选型。

选型上没有绝对标准,但有一条经验可以参考:如果项目目的是把检测系统跑通并展示测量链路,串口型省下来的调试时间非常可观;如果目的是学习传感器底层原理、练输入捕获和查表插值,PWM 型更值得折腾。B测场景通常是一批人同时做对比测量,串口型的一致性问题更少,我一般优先建议串口型。

2.2 主控选型看 B测场景的够用原则:从51到STM32的差异

传感器输出是低速信号,雾霾检测本身对主控性能要求不高,但主控选型会决定你后面踩的坑是多是少。51 系列的优势是上手简单、资料多,但两个短板在雾霾检测场景里很致命:一是 PWM 输入捕获要用定时器模拟,51 定时器数量少、位宽小,测高电平时间误差大;二是串口通常只有一个,同时接传感器和调试串口,就得用 IO 口模拟或者反复跳线。

STM32F103 是这类项目里见到最多的选择。72MHz 主频、多路 USART 和定时器、12 位 ADC 加上 DMA,串口接收可以走 DMA 空闲中断,完全不用在中断里一字节一字节地等。传感器采集是慢速任务,STM32 有余量跑滤波、计算 AQI、驱动 OLED 和 SD 卡,一片全搞定。比它再高的选择没必要,上 Linux 板卡做雾霾检测属于杀鸡用牛刀,还会引入系统启动时间和功耗问题。

主控选型有一条够用原则:接的外设数量乘以每路外设的数据速率,不超过主控总线吞吐的三成,就选熟悉的芯片,而不是选参数最高的芯片。B测项目周期短,把时间留给标定和测量,比留给芯片调试划算得多。我更看重两点:串口数量够不够、定时器能不能同时支持 PWM 捕获和软件定时,这两点占了,后面的麻烦会少一半。

2.3 浓度与AQI的换算:分段线性插值才是关键

串口型传感器的输出已经是计算好的质量浓度,单位 ug/m³,但 B测报告里通常要求的不是浓度,而是 AQI 指数。AQI 不是浓度本身,而是对健康影响的分级映射,每一档污染物都有一个分段线性区间,PM2.5 是其中最常用的一段。

以 PM2.5 为例,常见的分段区间如下表所示:

浓度下限 (ug/m³)浓度上限 (ug/m³)IAQI 下限IAQI 上限
035050
357550100
75115100150
115150150200
150250200300
250350300400
350500400500

落在哪个区间,就按线性插值公式计算:IAQI = (IAQI_H - IAQI_L) / (BP_H - BP_L) * (C - BP_L) + IAQI_L。这里的 BP 是浓度边界值,IAQI 是分数边界值,C 是实测浓度。

这个换算的坑不在于公式本身,而在于很多人直接拿浮点运算硬算,放在中断里或者高频循环里,白白占用主频。真正的问题是边界条件没做钳位:浓度超过 500 时,算出一个大于 500 的值,有些字段拼进字符串还会撑爆缓冲区。正确做法是先把浓度钳位到 [0, 500],再查分段,算完 IAQI 后取整。PWM 型传感器的换算更麻烦,数据手册给的往往是高电平时间与浓度的曲线坐标点,你需要先做线性插值,再进 AQI 分段,两步换算每一步都会引入误差,这也是前面说串口型更适合 B测的原因之一。

3. 解压搭建出数:把 zip 变成 PM2.5 真实数值的完整过程

打包文件解压后,工程结构、串口配置、滤波参数三个环节依次打通,传感器才会吐出一条可信的浓度曲线。这一章按实际动手的顺序走一遍。

3.1 解压后的目录结构:哪些文件该改、哪些别碰

解压之后,第一件事不是打开工程直接编译,而是先确认目录结构。按常见配套工程的结构说,Src 目录放业务源码,Inc 目录放头文件,Hardware 目录里是传感器和屏幕的驱动,User 目录通常是 main.c 和中断回调。如果压缩包根目录有说明文档,先读它,里面一般写了传感器的接线引脚、波特率和供电说明,这些信息比你自己翻代码猜要可靠得多。

重点区分两类文件:一类是需要按接线修改的,比如串口号配置、中断优先级分组、校准系数;另一类原则上不要动,比如传感器帧解析的状态机、DMA 处理逻辑。改错地方的结果往往是传感器出数正常但屏幕上数值永远不对,或者串口调试助手每隔一会儿收到一帧乱码。我一般会给工程建一个版本提交点,改任何文件之前先提交,后面排查时能用对比快速定位改坏了什么。

这个项目里最需要关注的三个可改点:第一,串口初始化里波特率要和传感器数据手册一致,串口型粉尘传感器常见 9600bps、8 位数据、无校验、1 停止位,波特率配错,帧解析永远碎片化;第二,DMA 接收缓冲区大小,帧长度通常在 32 字节以内,缓冲区设太大会浪费内存,设太小会截断帧尾,校验和永远对不上;第三,滤波窗口长度和上报周期,后面单独说。这三个点改完,剩下代码保持原样,出问题的概率已经小了大半。

3.2 串口帧解析与烧录验证:让传感器先吐数据

串口型粉尘传感器的数据格式并不统一,最常见的帧结构是两字节帧头、两字节数据长度、若干字节数据体,最后是校验和。以帧头 0x42 0x4D 为例,一次典型的接收流程是:主控用 DMA 把串口数据收进缓冲区,在空闲中断里把缓冲区交给解析状态机,状态机逐字节识别帧头并核对数据长度。

下面是一段常见的状态机解析代码,适合放在串口接收回调里调用:

#define DUST_FRAME_MAX 32 typedef struct { uint8_t buf[DUST_FRAME_MAX]; uint8_t len; uint8_t state; uint8_t want_len; uint16_t pm25; uint16_t pm10; } dust_ctx_t; void dust_parse_byte(dust_ctx_t *ctx, uint8_t byte) { switch (ctx->state) { case 0: /* 等待第一帧头 */ if (byte == 0x42) { ctx->buf[0] = byte; ctx->len = 1; ctx->state = 1; } break; case 1: /* 等待第二帧头 */ if (byte == 0x4D) { ctx->buf[1] = byte; ctx->len = 2; ctx->state = 2; } else { ctx->state = 0; /* 帧头不对,重新同步 */ } break; case 2: /* 解析数据长度 */ ctx->buf[ctx->len++] = byte; if (ctx->len == 4) { ctx->want_len = (ctx->buf[2] << 8) | ctx->buf[3]; if (ctx->want_len > DUST_FRAME_MAX) { ctx->state = 0; /* 长度异常,丢弃重来 */ } } break; default: /* 按长度收完整帧 */ ctx->buf[ctx->len++] = byte; if (ctx->len >= 4 + ctx->want_len) { ctx->pm25 = (ctx->buf[4] << 8) | ctx->buf[5]; ctx->pm10 = (ctx->buf[6] << 8) | ctx->buf[7]; ctx->state = 0; } break; } }

逻辑说明:状态机把逐字节处理拆成四个阶段,前两帧头防止错位同步,第三、第四字节组成数据长度,默认状态下按长度收满整帧后提取浓度字段。pm25 取数据体前两字节,pm10 取后两字节,不同型号的偏移可能不同,务必对照数据手册确认。

参数说明:want_len 用 16 位变量,因为数据长度字段本身就是双字节,但实际帧长一般不超过 32,所以 DUST_FRAME_MAX 设 32 够用。如果收到的 pm25 一直是 0 且帧头能对齐,优先怀疑数据偏移位置,而不是怀疑传感器损坏。烧录后先用串口调试助手看原始 HEX,确认帧结构正确,再跑固件里的解析逻辑,这是最稳的验证顺序。

3.3 滤波与校准:把跳变的原始采样转成稳定浓度

传感器输出的原始浓度值,在环境稳定时也会有明显跳变,原因是气流湍流导致激光照射区的粒子数量波动。直接把这个值送去显示或上报,屏幕上会一直跳,B测记录里也是密密麻麻的异常点。常见做法是加滑动平均,窗口长度是这套系统最重要的参数:

#define FILTER_WIN 10 static uint16_t ring_buf[FILTER_WIN]; static uint8_t ring_idx; uint16_t dust_moving_average(uint16_t raw) { uint32_t sum = 0; ring_buf[ring_idx] = raw; ring_idx = (ring_idx + 1) % FILTER_WIN; for (uint8_t i = 0; i < FILTER_WIN; i++) { sum += ring_buf[i]; } return (uint16_t)(sum / FILTER_WIN); }

逻辑说明:每次调用把新采样写进环形缓冲区,缓冲区满后覆盖最旧的数据,最后对所有元素求平均,等效于一个时域低通滤波器,效果和窗口大小成正比。

参数说明:FILTER_WIN 设 10,稳定环境下能把波动压到 ±10% 以内,但对突变的响应大约滞后 10 个采样周期。如果采样周期是 1 秒,一个烟雾事件发生后要 10 秒才能在数值上看到趋势。实时监测场景设 5~8,标定和复盘场景设 15~20。不要超过 20,响应时间太长会让后续尘源标定完全抓不到峰值。

滤波之后是校准。传感器的出厂曲线虽然可用,但个体之间存在基线偏移,常见做法是线性校准 y = k * x + b。k 用于修正放大倍数偏差,b 用于扣除无尘环境下的本底读数。在干净房间把传感器开机稳定半小时,记下读数的均值作为 b 的估算;再与参考仪器并排做一组对照数据,拟合出 k。这个步骤看着简单,却是 B测数据可信度的关键,跳过它的后果就是后面对比偏低的问题。

4. 把裸数据做成可用结果:显示、上报与本地存储的落地细节

采集和换算跑通之后,雾霾检测系统还没有真正可用,因为数据要被人看到、被记录端收到、被复盘时翻出来。这一章讲三个输出通道的落地细节,每一个都有对应的参数选择。

4.1 屏幕显示更新:OLED刷新时序与数字稳定性

B测现场看数据,多数项目的配套屏是 0.96 寸 OLED,常见的驱动是 SSD1306,I2C 接口。显示本身不难,难点在刷新策略。如果每 100ms 全屏重绘一次,画面会闪烁,而且 I2C 总线被持续占用,给串口和存储让不出时间。

我一般把刷新拆成两层:底层数值缓存,上层按需重绘。采集线程只更新缓存里的 PM2.5、PM10 和 AQI,显示线程每 500ms 检查一次数值变化,变化超过阈值时才重绘。阈值可以设 1ug/m³,人眼对这个量级的变化基本无感,但能省掉大量无意义的 I2C 写入。SSD1306 没有局部更新接口,简单起见,整屏重绘配合降低频率,比做局部刷新更实际。

刷新过程中有个容易翻车的细节:数值字符串转换。float 直接拼进显示缓冲区,小数位不稳定,还会引入格式化的 CPU 开销。务实做法是浓度值取整数显示,AQI 本来就是整数,把解析出来的 uint16_t 直接转十进制字符串,避免浮点格式化。显示上再加一个小逻辑:前几秒的原始值不要立刻上屏,等滤波窗口填满后再显示,否则开机那一下会闪出一个大数值。

4.2 数据上报:串口/网口JSON报文怎么做才不丢帧

B测过程中,数据要同时给上位机记录或远程平台,最常见的上报方式是串口 JSON,如果工程里带无线模块,则是 TCP 或 HTTP 上报。无论走哪条通道,报文格式和发送节奏必须稳定,否则记录端会看到断断续续的帧。

下面是一个实用的 JSON 拼包代码:

char txbuf[96]; int n = snprintf(txbuf, sizeof(txbuf), "{\"t\":%lu,\"pm25\":%.1f,\"pm10\":%.1f,\"aqi\":%u}\r\n", (unsigned long)rtc_ts, pm25, pm10, calc_aqi(pm25)); uart_send_bytes(txbuf, n);

逻辑说明:报文把时间戳、两个浓度值和一个 AQI 放在一行 JSON 里,以 \r\n 结尾,解析端按行切割即可。关键点是先用 snprintf 拿到返回值 n,再按 n 发送,而不是用 strlen,因为 float 格式化可能受编译选项影响,strlen 会发多或发少。

参数说明:发送节奏单独用变量控制,不要和采集循环耦合。常见做法是采集 2 秒一次,上报 5 秒一次,拼包时使用最近一次稳定值。发送前必须等上一包发送完成,用 DMA 发送时只需检查发送完成标志位。如果走 TCP,还要注意粘包问题,解析端不能按长度收,要按 \r\n 分割。

4.3 本地存储:SD卡时间戳记录与B测复盘

屏幕显示和数据上报都有时效性,真正能用于复盘的是本地存储。SD 卡方案在 STM32 上通常是 SPI 接口加 FatFS 文件系统,B测记录格式建议用最简的 CSV:一行一条记录,字段包括时间戳、PM2.5、PM10、AQI。

写文件的时机不要一条记录写一次,那是直接把 SD 卡寿命耗尽。我一般用 64 字节的缓冲行,攒够十几条再一次性写入,既保证数据不丢,也避免频繁擦写扇区。写完后调用 f_sync() 把数据落盘,但不能每条都 sync,否则写速率会掉到难以接受。写入调用的代码示意如下:

char line[64]; int n = snprintf(line, sizeof(line), "%lu,%.1f,%.1f\r\n", (unsigned long)rtc_ts, pm25, pm10); f_write(&file, line, n, &bw); if (++write_count >= 10) { f_sync(&file); write_count = 0; }

逻辑说明:行缓冲攒到 10 条触发一次落盘,f_sync 确保数据真正写入物理介质,这样断电时最多丢最近几秒数据,而不是丢几十条。

参数说明:断电保护是这个环节最实际的教训。测试到一半拔电,最后几条数据大概率丢,原因不是代码没写,而是数据还在 FatFS 的缓存里没有落盘。结合 B测场景,建议分卷记录:文件名带日期,比如 log_20260112.csv,每次启动新建一个文件,既避免单文件过大,也方便按天复盘。如果时间戳来自 RTC,记得先用串口校时;没 RTC 的板子可以用开机后的秒计数代替,复盘时再换算。

5. 雾霾检测避坑手册:5个高频翻车现场与排查路径

代码能编译、串口能出数,这只是开始。雾霾检测真正磨人的是各种边界情况,这一章按现象到原因再到解决的路径,整理 5 个高频踩坑点。

5.1 上电后读数恒为0:传感器预热与初始化顺序的坑

现象:固件烧进去,屏幕亮了,串口有帧,但 PM2.5 值始终是 0。

原因:串口型粉尘传感器内部有激光器和风扇,上电后需要预热时间,激光器稳定输出和风扇建立气流都要几十秒。很多代码在初始化时立刻读取传感器,得到的浓度自然是 0。另一种常见原因是传感器带了休眠模式,某些型号的复位引脚在上电瞬间保持低电平,主控必须主动拉高并延时,否则传感器永远不工作。

解决:初始化函数里把传感器复位引脚先拉低再拉高,然后延时 30 秒以上再开始解析数据。更稳妥的办法是状态机里加一个启动标志,预热完成前丢弃所有解析结果。排查时先用串口调试助手直接看传感器主动发出来的原始帧:如果 HEX 有数据但浓度字段为 0,问题在传感器本身;如果 HEX 完全没有数据,问题在主控的串口或 DMA 配置。

5.2 浓度值疯狂跳变:滤波窗口参数的取舍

现象:环境明明稳定,屏幕上浓度值却在 50 到 150 之间来回弹。

原因:传感器测量本身是抽样式的,每次采样的粒子数服从统计涨落,气流扰动会让瞬时值波动很大;滤波窗口设为 3 或 5 时,低通效果有限。

解决:滑动平均窗口设到 10 以上,波动幅度通常能压住一半。但有代价:窗口越大,响应越慢。应急监测场景优先保留响应速度,窗口最多别超过 8;需要稳定记录的 B测场景,窗口可以放宽到 15 到 20。如果你发现把窗口调到 20 还是跳,不要继续加窗口,先检查传感器进风口是不是对着风扇或空调气流,换个位置比改代码快得多。

5.3 与官方站点对比总是偏低:本底噪声与校准基线

现象:拿着自测数据跟附近的官方空气质量站对比,整体偏低 25% 到 40%。

原因:两个因素叠加。一是传感器在零污染环境下仍有本底读数,出厂曲线未必帮你扣干净;二是官方站测的是区域尺度数值,你测的是机器周围数十厘米范围的局部值,点位差异天然存在。

解决:先做基线扣除。在密闭空间用净化手段让空气静置一段时间,让传感器稳定运行半小时,记录读数均值作为 b 的估算值。再把传感器与参考仪器并排放置,收集至少 10 组对照数据,用最小二乘拟合 k。做完后再对比,偏差会落到一个可接受的范围内。记住:校准是给一套系统做的,换传感器或换风道都要重做一次,偷懒的结果就是数据永远差一截。

5.4 湿度一大就虚高:吸湿增长要不要补偿

现象:下雨天或湿度超过 70% 时,PM2.5 读数明显高于晴天,看着像中度污染,实际上空气很干净。

原因:亲水性气溶胶在相对湿度高的时候吸水长大,粒子直径增加,激光散射信号增强,传感器把变大的粒子也算成了 PM2.5 的一部分。光学法测的就是这种光学等效粒径,所以湿度敏感是原理性的。

解决:视场景决定。如果只做趋势监测,可以在湿度超过 70% 时对读数打折或打上标记,数据记录里附上湿度字段,复盘时人工剔除。如果要做严谨的定量测量,需要引入相对湿度补偿模型,但不同地区气溶胶成分不同,模型参数也要重新标定。对 B测来说,先做到标记湿度、不把高湿数据当真值,就很足够了。

5.5 烧录后反复重启:风扇启动电流与电源纹波

现象:程序运行几秒后,板子复位重启,指示灯闪一下又恢复,如此循环。

原因:传感器内部风扇启动瞬间电流是稳态的 2 到 3 倍,如果主控和传感器共用同一个低压差稳压器,风扇启动瞬间会把电压拉低到主控复位阈值以下,产生掉电复位。这个现象在劣质供电线和杜邦线供电时尤其明显。

解决:给传感器单独供电,或者把电源线加粗并与主控电源之间加一个 100uF 以上的电解电容。软件层面也能缓解,初始化时先用低速启动风扇,等电流稳定后再切全速,但硬件电容是最省事的方案。排查时把传感器拔掉,如果板子不再重启,基本锁定电源问题。

6. 用可控尘源给雾霾检测验证:标定一条曲线数据才敢用

前面的代码和参数解决的是能不能跑,真正让数据可信的是最后一步标定验证。常见做法是做一个简易的密闭标定箱:用一个容积稳定的透明收纳箱,侧面开两个小孔,一个用于线香或烟尘注入,一个用于传感器采样线缆穿出,内部放一个小风扇搅匀空气。步骤分成三段:先在干净空气中记录基线,再点燃一根线香伸入箱内燃烧固定时长后撤走,最后继续记录衰减曲线。同一支线香重复三次,看响应是否一致。

记录下来的数据能榨出三个指标:T90 响应时间,从点火到读数达到峰值 90% 所需的时间;峰值重复性,三次实验峰值的变异系数;恢复时间,撤走尘源后读数回到基线 1.5 倍以内所需的时间。这三个值写进 B测报告,比单纯贴几张曲线图更有说服力,评估时可以参考下面这个范围:

指标参考范围超标时先查
T90 响应时间30 秒以内滤波窗口过大、风扇气流不足
峰值变异系数小于 15%尘源燃烧不均匀、箱内混合不充分
恢复时间3 分钟以内箱体残留颗粒、传感器风扇积灰

标定过程中有一个经常被忽略的坑:线香燃烧会产生较大颗粒,浓度会冲到几百甚至上千 ug/m³,传感器量程上限一般也在这个范围,超量程后数值会饱和平顶,这时记录到的 T90 和峰值都不可信。所以尘源注入量要控制,一次燃烧 30 秒到 1 分钟就足够,别让浓度顶到饱和区。还有传感器风扇积灰问题,标定实验做完后要用毛刷或压缩空气清理进风口,否则下一次基线就漂了。

我现在的习惯是每套传感器上手先做一遍这套尘源标定,记录基线、峰值和恢复时间,再拿去测真实环境。这个习惯是被教训换来的:有一批板子不做标定直接测,结果拿回来的数据和参考仪器偏差大到没法看,重新做标定才发现是其中一颗传感器的基线偏了十几个点。从那以后,标定箱成了工位上常驻的东西,数据源本身不干净,后面滤波、上报、显示做得再漂亮都没用。希望这篇笔记能让你少走这段弯路,做雾霾检测前先把标定这一课补上。

本文还有配套的精品资源,点击获取

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

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

立即咨询