
简介一份基于STM32的空气质量监测系统完整设计文档共1个PDF文件大小41.36MB目前已有84人学习。内容以项目开发全流程为主线从需求分析与方案设计入手完整覆盖STM32F103RCT6主控与SHT30温湿度、PM2.5粉尘、MQ135有害气体、MS1100VOC甲醛等传感器选型连接并结合ESP8266 WiFi模块实现TCP数据传输。文档同时给出系统框架图、原理图、实物图、硬件组装过程、KEIL工程代码分析以及Qt手机APP上位机的开发流程涵盖OLED显示、蜂鸣器报警、锂电池供电等关键设计并讲解了从传感数据采集、无线上传到手机端展示的完整交互链路。对于嵌入式初学者、物联网项目开发者或毕业设计选题者而言这套资料提供了可借鉴的硬件选型清单、通信协议设计、报警提醒逻辑和低功耗供电方案适合作为从单片机基础到综合实战项目的重要参考。 空气质量监测系统这个题目每年都有大量同学在做但能把数据做“可信”的其实不多。常见的套路是传感器买回来程序烧进去OLED 上有数了就觉得大功告成。直到被问一句“你这个 PM2.5 数值和天气 App 差了好几倍误差怎么解释”前面省掉的每一步后面都会加倍还回来。这篇就围绕 STM32 主控从传感器选型、硬件电路、软件分层、调试排错一直讲到数据校准和联网扩展把我实际项目里踩过、填平的坑都摊开说。无论是做毕设、课设还是想给家里做个实时空气质量盒子这套思路都能直接接得住。1. 系统方案与器件选型把“测空气质量”拆成能落地的模块1.1 监测参数拆解与传感器选型很多新人拿到题目第一反应是“我要测空气”然后就去淘宝搜传感器。但空气好不好是个复合指标起码拆成三类来看温湿度基础环境参数直接影响体感还参与其他传感器的温漂补偿。颗粒物PM2.5、PM10这类东西是“看得见”的污染也是大家最关心的数值。气态污染物CO2、TVOC、甲醛。CO2反映通风状态TVOC/甲醛更偏向装修污染。我按这几类参数整理了常用传感器的横向对比给你选型时做参考参数常见型号接口实际体验参考价温湿度DHT11单总线精度差采样周期必须1s以上容易互相干扰3元温湿度SHT30I2C±0.3℃/±2%RH很稳读一次就几ms15元PM2.5GP2Y1010AU0FPWM模拟量便宜但电路麻烦采样要和LED驱动同步20元PM2.5PMS5003UART直接输出ug/m³数据稳定省心80元CO2/TVOCMQ135模拟量预热久、交叉敏感只能定性用5元CO2/TVOCSGP30I2C输出eCO2/TVOC需要基线校准50元CO2MH-Z19UART红外原理精度高功耗大偏贵200元这里要特别说一句MQ 系列MQ135、MQ2 这些在毕设里出现频率很高但它里面是半导体气敏材料对酒精、丙烷、CO2 都敏感别人在旁边喝口酒读数就能飙上去。而且基准电压会跟着温湿度一起漂拿来演示“数值会变化”没问题但论文里这个数据经不起质询。我一般把它定位成“定性检测/超标报警”不参与定量计算。1.2 主控选型与系统框架划分主控用 STM32F103C8T6 就够了这是目前最成熟的组合。选它不是因为性能强而是资源刚好踩在项目的甜点上Cortex-M3 72MHz、64KB Flash、20KB RAM跑一套环境监测固件加 OLED 显示完全够用外设方面有 ADCDMA、两个 I2C、三个 USART一个接调试串口、一个接 PMS5003、一个留给 ESP8266刚刚好。接口分配我会这样规划ADC1IN0 接 GP2Y1010 模拟输出IN1 留作扩展或备用分压通道。I2C1OLED SHT30 共用SHT30 地址 0x44OLED 地址 0x3C。USART1调试打印接 CH340 转 USB。USART2PMS5003 或 ESP8266按方案切换。如果是课程设计可以加几个独立按键、一个蜂鸣器做超标报警整体结构更完整。STM32F103ZET6 那种一百多个引脚的板子没必要C8T6 的核心板十块钱左右资料还多坏了也不心疼。2. 硬件电路传感器与主控搭配时最容易翻车的几个细节2.1 电源系统的纹波处理这个系统里最容易被忽视的就是电源。USB 的 5V 纹波其实不小如果直接用 AMS1117-3.3 转 3.3V前端必须加电容输入端 10uF 电解加 100nF 瓷片输出端同样要 10uF 加 100nF。不加电容AMS1117 可能自激振荡表现为 3.3V 带负载后电压掉到 2.8V 以下STM32 就开始随机复位。还有个常见问题GP2Y1010 驱动LED时的脉冲电流接近 300mA如果这个电流和 STM32 的 3.3V 共用一路电源ADC 采出来的数值一定满屏跳。我的做法是用一个 PNP 三极管单独驱动 LED并在传感器电源输入端串个 10Ω 电阻加 100uF 电容做局部去耦和数字电路形成水塘隔离效果立竿见影。2.2 模拟量前端分压电阻与采样电容GP2Y1010 的输出电压范围大致是 0~3.6V而 STM32 的 ADC 量程是 0~3.3V直接接过去会削顶。正确做法是先用电阻分压把量程压进 3.3V 以内比如 3.3k 串 33k分压比约 0.1然后再进 ADC 引脚。分压之后还要注意输出阻抗。传感器内部输出阻抗本来就不低加上分压电阻会更高所以引脚处要放一个 RC 滤波1kΩ 串联电阻加 1uF 电容到地既滤波又降低源阻抗。如果你跳过这个电容ADC 采出来的值会偏小而且不稳定前后两次读数能差几十个数。2.3 I2C 总线上拉与多设备地址规划SHT30 和 OLED 共用 I2C1 时总线上必须有上拉电阻一般取 4.7kΩ。如果用的是杜邦线飞线线长超过 20cm上拉要降到 2.2kΩ否则 I2C 通信会出现“有时读得到、有时读不到”的诡异问题而且示波器肉眼不容易抓。地址方面SSD1306 的 OLED 常见地址是 0x3CSHT30 是 0x44SGP30 是 0x58三者不冲突。如果你后面想挂更多的 I2C 设备建议每挂一个新设备先扫描一遍总线地址避免冲突之后白调半天。3. 软件实现采样、滤波、显示与上报的分层思路3.1 ADC 多通道 DMA 采样的正确打开方式如果用 CubeMX 配置 ADC1 的双通道 DMA有几个必须注意的点开启 Scan Mode、Continuous Conversion、DMA 的 Circular Mode数据长度要设成“通道数 × 采样次数”。比方说两个通道各采 32 次DMA 缓冲区就是 64 个 uint16_t内存地址自增这样每次转换完成环形队列里就有两组通道的数据。数据对齐要选右对齐读出来的值就是 0~4095。很多人忘记这一点直接把 16 位寄存器左移造成结果整体偏大。回调里做平均的代码大概是这样#define PM_CH 0 #define EXT_CH 1 #define SAMPLE_N 32 uint16_t adc_buf[2 * SAMPLE_N]; volatile uint8_t adc_updated 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc ! hadc1) return; uint32_t sum0 0, sum1 0; for (int i 0; i SAMPLE_N; i) { sum0 adc_buf[i * 2 PM_CH]; sum1 adc_buf[i * 2 EXT_CH]; } g_pm25.adc_raw sum0 / SAMPLE_N; g_extra.adc_raw sum1 / SAMPLE_N; adc_updated 1; }采样时间也要拉到最大HAL 库配置里把每个通道的采样周期从默认几个周期改成 55.5 周期或更多这样对高阻抗源更友好数值稳定性会明显提升。3.2 轻量滤波滑动平均与一阶低通怎么选传感器数据是慢变量不需要上卡尔曼滑动平均就够了。维护一个长度 8 到 16 的窗口每次新采样入队、最老的出队输出窗口均值。注意累加类型要用 uint32_t16 次 × 4096 已经超过 uint16_t 的 65535 上限了。代码示意#define WINDOW_SIZE 16 static uint16_t buf[WINDOW_SIZE]; static uint8_t idx 0; static uint32_t sum 0; uint16_t moving_average(uint16_t new_sample) { sum - buf[idx]; sum new_sample; buf[idx] new_sample; idx (idx 1) % WINDOW_SIZE; return sum / WINDOW_SIZE; }如果系统 RAM 很紧张可以用一阶低通替代y y_old * 0.85 x * 0.15只需要两个变量实时性还更好但要注意系数选择。滑动平均适合画趋势图一阶低通适合做实时响应两者都够用我一般按需求二选一不会同时上两套滤波。3.3 用定时器做时间片别在主循环里 sleep空气质量系统同时要做的事不少2秒读一次温湿度1分钟读一次颗粒物500ms 刷新一次 OLED200ms 打一条串口日志。如果全用 HAL_Delay 串行处理一个等一个后续任务全被拖死。正确做法是维护一个毫秒级的 tick主循环里用时间差判断任务是否到期。static volatile uint32_t s_tick 0; void SysTick_Handler(void) { s_tick; } uint32_t now_ms(void) { return s_tick; } while (1) { uint32_t now now_ms(); if (now - last_sht30 2000) { sht30_read_all(temp, humi); last_sht30 now; } if (now - last_display 500) { oled_refresh(temp, humi, pm25_value); last_display now; } if (now - last_uart 200) { debug_printf(temp%.1f humi%.1f pm25%d\r\n, temp, humi, pm25_value); last_uart now; } }这里用无符号减法做时间差自然处理绕卷不用担心 tick 溢出。核心逻辑是所有任务都是“检查是否到期到期就执行”其余时间 CPU 可以处理中断或进入低功耗等待。这个结构一换到别的传感器项目里也能直接用我后面几个项目基本就是复制这套调度框架改任务周期而已。4. 调试踩坑从“编译通过”到“数据能看”的完整链路4.1 下载失败no stm32 target found 的排查顺序热词榜单里 “error: no stm32 target found” 常年霸榜说明这是几乎每个人都会遇到的问题。我现在的排查顺序基本固定先查供电。ST-LINK 的 3.3V 能否带动整个板子如果带不动改用外部 5V 供电并把 GND 和 ST-LINK 共地。再查接线。SWDIO、SWCLK、GND 三条线是不是一一对应有没有拿 JTAG 线序去接 SWD。然后查复位。NRST 引脚的电容如果太大100uF 级别ST-LINK 连接时拉低复位信号会失败。常规 104 电容没问题但别手滑用大电容。最后考虑 BOOT0 和读保护。如果芯片之前被写入了关掉 SWD 的代码或者读保护开启连接就会失败。把 BOOT0 拉到 1重新上电用 STM32CubeProgrammer 解除读保护再拉回 0 就能恢复正常。新料里提到的 Debug Authentication 多出现在部分新系列芯片上F103 本身没有这个机制但如果你用的核心板是较新的批次已经把 SWD 引脚默认复用掉了思路是一样的先从供电和接线查起再处理 BOOT0 和读保护。4.2 delay 卡死SysTick 被谁抢了HAL_Delay 卡死的案例我见过几类。最常见的是重写了 SysTick_Handler 却没调用 HAL_IncTick结果 uwTick 永远不增长HAL_Delay 死等。还有一种是中断里面调用了 HAL_Delay而该中断优先级比 SysTick 高抢占后 SysTick 无法触发直接卡在中断里出不来。另外在 FreeRTOS 工程里把 HAL_Delay 和 vTaskDelay 混着用也可能互相踩tick。我的建议很简单不要依赖 HAL_Delay 做业务逻辑里的延时。用上面说的时间片调度代替所有“等一段时间”全部改成“到期检查”。这样既不会卡死也让系统的时序一目了然。如果一定要用中断里的延时那也是短延时比如 GP2Y1010 在 LED 脉冲开始后要等 280us 再采集这种用 DWT 或定时器的 us 级延时实现避免整毫秒阻塞。4.3 串口乱码与 ADC 数值跳变串口打印中文乱码先别怀疑编码问题九成是时钟配置不对。CubeMX 默认用外部 8MHz 晶振如果板子实际上没焊晶振或者晶振没起振HAL 的 HSE 初始化失败后会回退到 HSI 8MHz。这时 SystemCoreClock 显示 8000000而代码按 72MHz 去配波特率串口助手收到的自然全是乱码。排查时先读一下 SystemCoreClock如果是 8000000问题就锁定在时钟树或晶振上。ADC 数值跳变的常见原因有三个传感器供电纹波大、ADC 引脚没加滤波电容、采样时间太短。其中供电纹波最阴险USB 口的 5V 噪声能直接耦合到传感器导致读数跟着电脑负载波动。我的经验是先用充电宝供电对比一下如果充电宝供电时数据稳定基本就是 USB 电源的锅。5. 数据校准与功能扩展让系统从“能跑”变成“能用”5.1 廉价传感器的预热、漂移与基线校准把硬件调通只是完成了 60%剩下 40% 是让数据可信。MQ 系列上电后必须预热我实测 MQ135 至少要通电 12 小时以上输出才基本稳定48 小时后才敢用来算基线。GP2Y1010 也有离散性每片传感器的洁净空气输出电压 V0 和灵敏度系数 K 都不同所以校准是必须的不是可选项。我的校准做法是三个步骤配合先在洁净环境比如室外通风处采 10 分钟数据取平均作为零点 V0。然后用参考设备家里现成的空气质量检测仪、气象站数据都行在同一环境同步采 10 分钟取平均做线性回归得到斜率 a 和截距 b。最后把 V0、a、b 的值存到 STM32 内部 Flash 的最后几页代码里直接读取使用断电不丢。SGP30 这种芯片更讲究首次使用要老化 12 小时之后在 400ppm CO2 的洁净空气里校准基线。不校准就直接用eCO2 的绝对值可能偏 30% 以上但是趋势是准的所以判断“开窗通风后是否下降”没问题报精确浓度不靠谱。5.2 从单机版到联网版的三个扩展方向单机版调通了下一步通常是联网。我按难度从低到高排三个方向本地串口上位机用 CH340 接 STM32 的 USART1QT 或 C# 写个串口小工具把数据画成曲线。这是最稳的保底方案调试时还能顺便看日志。ESP8266 上报云平台USART2 接 ESP8266AT 固件连 WiFi用 HTTP POST 把 JSON 包发到巴法云、OneNET 这类平台。AT 指令流程不复杂但要注意 ESP8266 耗电峰值接近 300mA不能直接用 STM32 的 3.3V 引脚给它供电必须单独用一片 3.3V LDO。低功耗电池版把主控换成 STM32L0 或 L4 系列RTC 定时唤醒每次上电采样一两秒后继续睡眠。GP2Y1010 的 LED 平时也要断电只在测量期间供电。这套方案能把平均功耗压到毫安级以下适合做长期摆放的监测节点。如果后面想做成自动新风系统还需要加一路继电器控制风机并用 PID 根据 PM2.5 偏差调节风量。那是另一个故事但底层的采样与调度框架是可以直接复用的。5.3 我自己的落地体会按这套思路做下来你得到的不是一份一次性课设代码而是一个“换传感器、换场景都能复用”的嵌入式框架。我现在手头这套系统后来加过甲醛传感器、接了一路继电器控制新风还在试着用 PID 控风机转速。真要说做这类项目最深的体会就是别急着堆硬件先把“要测什么、数据给谁看、怎么证明数据准”这三件事想明白后面所有电路和代码都不太会跑偏。数据可信度这一点答辩时几乎是必问的也是区分“应付”和“认真做”的分水岭。本文还有配套的精品资源点击获取