尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32环境质量监测系统设计与Proteus仿真完整教程

STM32环境质量监测系统设计与Proteus仿真完整教程 做嵌入式这些年我陆陆续续接触过不少入门项目但说句实话大部分教程要么只讲代码不讲硬件要么给了原理图却没有仿真实例学生和刚转行的朋友学起来特别容易卡在半路。这个 STM32 环境质量监测系统算是我见过的比较“完整”的开源项目之一——主控用 STM32采集温湿度和空气质量数据LCD 显示超阈值报警还配套了原理图、源码和 Proteus 仿真工程。也就是说你拿到手之后可以先在电脑上把整个系统跑通再照着原理图去焊实物板从软件到硬件一条线打通非常适合拿来练手或者做课设参考。这篇文章我就把这个项目的设计思路、硬件细节、代码逻辑和仿真调试过程完整拆一遍尽量把每个关键点都讲透。1. 项目整体设计与方案选型解析1.1 为什么选择 STM32 作为主控很多朋友第一次看到这类项目会问测个温湿度、看个空气质量用 51 单片机不就行了干嘛非得用 STM32这个问题的答案其实取决于你想学什么。如果只是跑通一个 demo51 确实够用但代价是你基本学不到现代嵌入式开发的常规套路。STM32 的 Cortex-M 内核、标准外设库 / HAL 库、中断系统、ADC 多通道采集、定时器 PWM 输出这些都是目前工业界和消费电子领域最常用的技能点。环境质量监测系统看起来是个“小项目”但它涉及到的外设资源其实不少GPIO 模拟时序、ADC 采样、串口打印、LCD 显示控制再加上蜂鸣器报警和按键交互正好能把 STM32 的常用外设都过一遍。选型上比较推荐 STM32F103C8T6也就是大家常说的“蓝丸”核心板。它便宜、资料多、Proteus 里也有对应的仿真模型无论是做实物还是搭仿真都很方便。Flash 64KB、RAM 20KB 对于这个项目来说完全够用而且主频 72MHz 跑 DHT11 这种低速传感器时序余量非常充足。1.2 传感器选型DHT11 与 MQ 系列的组合逻辑环境质量监测听起来是个很大的概念但具体到嵌入式这种小系统上核心就两件事温湿度和空气污染程度。温湿度这部分DHT11 是绕不开的入门选择。它的优势是便宜、单总线协议、代码实现简单一个 GPIO 引脚就能读完温湿度数据。虽然精度一般湿度 ±5%RH、温度 ±2℃但对于环境监测这种场景已经足够。如果你手头预算充足也可以换成 DHT22 / AM2302代码底层时序基本兼容只需要改一下数据换算的公式。空气质量部分常见方案有两种一种是 MQ 系列模拟输出传感器比如 MQ-2可燃气体/烟雾、MQ-135空气质量/VOC它们输出的是模拟电压信号直接接到 STM32 的 ADC 引脚就能读取另一种是所谓的“数字模块”板载了 LM393 比较器输出的是开关量只能判断“超标/不超标”读不到具体浓度趋势。从项目完整度考虑我更推荐用模拟输出的 MQ 传感器这样你能在屏幕上看到 ADC 值的变化曲线而不是只看到“正常/报警”两个状态。1.3 功能拆分与整体数据链路整个系统的数据流可以分成四段传感器采集 → 主控处理 → 显示/报警 → 用户交互。传感器采集端DHT11 通过单总线协议把 40bit 数据发给 STM32MQ 传感器把气体浓度转换成电压信号送进 STM32 的 ADC。主控拿到这些原始数据之后先做滤波和换算把 ADC 原始值映射成可读的浓度百分比同时跟预设的报警阈值做比较。显示端我用的是 LCD1602带 IIC 转接板一来 Proteus 里有现成模型二来 IIC 只需要两根线能省下不少 GPIO。报警端是一个无源蜂鸣器超过阈值就通过 PWM 输出不同频率的提示音。用户交互则保留了一颗按键用来切换显示页面或手动消音。这套链路覆盖了嵌入式系统中最典型的“输入-处理-输出-交互”闭环而且是完全模块化的——每一部分拆出来都能单独写一篇笔记合在一起又是一个五脏俱全的小系统。2. 硬件电路设计原理图核心模块拆解2.1 最小系统与电源树设计既然是开源项目原理图部分就必须经得起推敲。STM32F103C8T6 的最小系统并不复杂一颗 8MHz 晶振作为 HSE 时钟源两个 20pF 负载电容一个 10K 上拉电阻接 NRST 复位引脚再加上 BOOT0/BOOT1 引脚的启动模式配置基本就是全部内容了。很多新手容易忽略的是 VDDA/VSSA 引脚它给 ADC 提供模拟参考电压必须单独接一个 1uF 和 0.1uF 的去耦电容否则 ADC 采出来的数据会跳动得非常厉害。电源部分我建议采用 USB 5V 供电然后通过 AMS1117-3.3 线性稳压降到 3.3V。为什么不用开关电源因为这个系统整体功耗很低线性稳压的纹波更小对 ADC 采样更友好。AMS1117 输入输出各加一个 10uF 钽电容和 0.1uF 陶瓷电容做滤波能在负载变化时稳住电压。注意 MQ 传感器的加热丝需要 5V 供电所以整个系统其实是两路电源轨5V 给传感器加热和蜂鸣器驱动3.3V 给 MCU 和 LCD。两路电源之间不需要隔离但 5V 转 3.3V 的转换点一定要放在 MQ 传感器之后避免加热丝的大电流波动直接干扰 MCU。2.2 DHT11 与 MQ 传感器的接口电路DHT11 是单总线器件数据引脚需要外接一个 4.7K~10K 的上拉电阻到 VCC。有些便宜的 DHT11 模块板上已经集成了上拉电阻和滤波电容直接用就行但如果你是自己买裸传感器焊这个电阻千万别省。DHT11 的上拉电阻不是随便选的阻值太小总线拉低时的灌电流会偏大阻值太大上升沿会变缓在高温高湿环境下容易造成时序读取失败。4.7K 是一个在功耗和时序之间折中的值。MQ 系列传感器模块上电之后有个特点——加热丝需要至少 30~60 秒预热输出电压才会逐渐稳定。所以原理图上我建议在 MQ 的模拟输出端和 STM32 的 PA 引脚之间串一个 1K 电阻再并联一个 0.1uF 电容到地构成一个简单的 RC 低通滤波器。这能滤掉传感器输出上的高频噪声让 ADC 采样值更平滑。但要注意RC 滤波会引入少量延迟对于空气质量监测这种慢变信号来说毫无影响但如果以后你要做快速响应类项目这个滤波就不能随意加了。2.3 显示、报警与按键电路设计LCD1602 IIC 模块的四根线VCC、GND、SDA、SCL直接接到 STM32 的 PB6/PB7 即可IIC 上拉电阻一般模块上已经带了。如果没有需要在总线上各加一个 4.7K 上拉电阻。很多人问 IIC 和 SPI 的 LCD 该选哪个我的建议是 IIC原因只有一个——省引脚。STM32F103C8T6 一共才 37 个可用 GPIO省下来的引脚可以留给串口调试、按键和扩展功能。蜂鸣器电路是比较容易翻车的地方。有源蜂鸣器内部有振荡源给高电平就响但声音频率固定无源蜂鸣器需要外部提供 PWM 方波才能发出声音好处是可以调节音调。这个项目我用的是无源蜂鸣器通过三极管 S8050 驱动。GPIO 输出高电平到三极管基极三极管导通蜂鸣器通电发声。基极串一个 1K 限流电阻蜂鸣器两端反并联一个 1N4148 续流二极管——蜂鸣器本质上是感性负载断电瞬间会产生反向电动势没有这个二极管容易击穿三极管。按键电路更简单一个 10K 下拉电阻加一个轻触开关单片机引脚读到高电平表示按下。这里有个细节按键要不要加硬件消抖如果只做一个按键软件里用 20ms 延时消抖就够了不需要额外加 RC 电路省事且可靠。3. 软件架构与核心代码实现3.1 分层结构与代码目录组织如果这只是一篇“抄代码就能跑”的教程那我确实不用花篇幅讲架构。但这个项目既然定位为开源项目代码结构就得对得起“开源”这两个字——别人拿到手应该在五分钟内知道每个文件是干什么的而不是在一坨 main.c 里翻几百行。我采用的目录结构比较常规Core/ Inc/ main.h gpio.h tim.h adc.h i2c.h Src/ main.c gpio.c tim.c adc.c i2c.c Drivers/ BSP/ BSP_DHT11.c/h BSP_MQ135.c/h BSP_Buzzer.c/h BSP_Key.c/h BSP_LCD1602_I2C.c/h Middlewares/ lcd1602_i2c_lib.c/h delay.c/h用标准库还是 HAL 库我选择了标准库原因有两个第一Proteus 仿真加载标准库编译出的 hex 文件兼容性更好第二DHT11 这种对时序敏感的外设标准库的寄存器操作更直接能让你更清楚地看到时钟翻转的过程。当然如果你已经在用 CubeMX HAL 库这个逻辑一样成立核心改动只在外设初始化部分。3.2 DHT11 驱动时序与代码实现DHT11 的通信协议是单总线但它的时序和 DS18B20 并完全不一样。完整的读取流程分四步主机拉低总线 ≥18ms 发起起始信号 → 释放总线等待 DHT11 响应 → DHT11 拉低 80us 响应信号 → 连续输出 40bit 数据高位在前。数据位的高电平持续时间决定了它是 0 还是 126~28us 的窄脉冲是 070us 左右的宽脉冲是 1。所以代码的核心就是循环读取这 40 个 bit每次都测量高电平持续的时间超过某个阈值就判定为 1。我基于 HAL 库写的核心代码如下uint8_t DHT11_ReadData(uint8_t *temperature, uint8_t *humidity) { uint8_t data[5] {0}; // 拉低总线发起起始信号 DHT11_DATA_GPIO_PORT-BSRR DHT11_DATA_PIN; HAL_Delay(1); DHT11_DATA_GPIO_PORT-BRR DHT11_DATA_PIN; // 释放总线切换为输入模式 GPIO_InitTypeDef gpioInit {0}; gpioInit.Pin DHT11_DATA_PIN; gpioInit.Mode GPIO_MODE_INPUT; gpioInit.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_DATA_GPIO_PORT, gpioInit); // 等待响应信号低电平 uint32_t timeout 10000; while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { if (--timeout 0) return 1; } // 等待响应结束高电平 timeout 10000; while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET) { if (--timeout 0) return 1; } // 读取 40bit 数据 for (int i 0; i 40; i) { while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_PORT, DHT11_DATA_PIN) GPIO_PIN_RESET); uint32_t t_high 0; while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_PORT, DHT11_DATA_PIN) GPIO_PIN_SET) { t_high; } if (t_high 30) { data[i / 8] | (0x80 (i % 8)); } } // 校验 if ((data[0] data[1] data[2] data[3]) data[4]) { *humidity data[0]; *temperature data[2]; return 0; } return 1; }这里有一个我踩过很多次的坑DHT11_ReadData 这个函数里涉及到的延时和循环变量依赖 CPU 主频和编译优化等级。如果你换了一块主频不同的板子t_high 的阈值参数需要重新标定。这也是为什么推荐在 Proteus 里先用默认 72MHz 跑确认逻辑通了再上实物。3.3 MQ 传感器 ADC 采集与浓度换算MQ 传感器的模拟输出引脚出来的电压和气体浓度之间并不是完全线性的它更像是一个指数关系——浓度越高输出电压越低或者越高取决于模块上的负载电阻接法。所以代码里我做了两个处理一是多次采样求平均滤波掉传感器固有的噪声二是把电压值映射到一个 0~100 的“污染指数”上方便 LCD 显示。ADC 部分的初始化用 CubeMX 生成就没问题配置 PA0 为 ADC1_IN0采样周期拉到最长239.5 cycles这样采样电容充电更充分数据更稳。连续采样 10 次取平均值#define MQ_SAMPLES 10 uint16_t MQ_GetAverage(void) { uint32_t sum 0; for (uint8_t i 0; i MQ_SAMPLES; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); sum HAL_ADC_GetValue(hadc1); HAL_Delay(10); } return sum / MQ_SAMPLES; }然后换算成电压再映射成污染指数float voltage (float)adc_value * 3.3f / 4096.0f; uint8_t air_quality (uint8_t)(voltage / 3.3f * 100.0f);严格来讲要获得真实的 PPM 浓度需要查 MQ 系列的数据手册里那张灵敏度特性曲线然后做对数拟合。但在这个项目里我们只需要一个相对值来判断空气质量所以线性映射足够了。如果后续要扩展可以把拟合公式写进一个MQ_Calibration.c里。3.4 主循环与系统状态切换主循环我采用了“轮询式调度”每个循环周期内依次读取传感器、刷新显示、检测按键、判断报警。这个项目没有复杂的状态机用超级循环就够了不需要上 RTOS。报警逻辑需要一点小设计。如果只是简单地在污染指数超过阈值时响蜂鸣器那系统会非常吵。我给蜂鸣器加了一个“间歇报警”的逻辑超标时 PWM 持续输出 1 秒然后停 1 秒循环同时用按键可以临时消音 5 分钟。这种体验在实物演示时非常有用也是从“能跑”到“好用”的一个小改进。LCD 显示方面我用两行来布局第一行显示温度和湿度第二行显示空气质量指数。配合按键切换还能显示 ADC 原始值和报警阈值方便调试时观察数据变化趋势。4. Proteus 仿真环境搭建与联调步骤4.1 仿真工程的创建与原件的选用在 Proteus 里搭这个系统不需要像画实物原理图那样讲究封装和 3D 模型但元件选型必须准确。我用的版本是 Proteus 8 Professional元件清单如下元件Proteus 搜索关键字参数/型号主控STM32F103C8蓝丸芯片选 DIP 封装温湿度传感器DHT11自带模型气体传感器MQ-2 / MQ-135选带模拟输出的型号LCD 显示器LM044L / LCD 1602带 IIC 的模型电位器POT-HG用于模拟传感器电压变化晶振CRYSTAL8MHz电容CAP20pF / 0.1uF / 10uF电阻RES按原理图取值蜂鸣器BUZZER无源选 ACTIVE 类型按键BUTTON轻触开关元件放置好后关键是连线。STM32F103C8 在 Proteus 里的引脚名称和实物一致PA0 对应引脚名 PA0PB6/PB7 对应 IIC 接口。建议先在原理图上把所有引脚标号写清楚再在 Proteus 里对照连线能省下大量查错时间。4.2 固件编译与烧录流程用 Keil5 编译工程之前需要确认 Target 选项里 Device 是 STM32F103C8并且在 Utilities 设置里勾选“Use Debug Driver”或直接输出 hex 文件。输出路径默认在工程目录的 Objects 文件夹下。编译通过后双击 Proteus 里的 STM32F103C8 元件在 Program File 一栏选择生成的 hex 文件然后点击左下角的运行按钮。如果代码逻辑没有低级错误系统会立刻开始工作LCD 上会显示温湿度。这里要特别提醒Proteus 里的 DHT11 模型和实物 DHT11 在时序上不完全一致。仿真的响应时间非常快几乎瞬间就返回 40bit 数据实物的响应则需要几百毫秒。所以从仿真移植到实物时主循环里读取 DHT11 的间隔建议设置在 1 秒以上否则会连续读到同一个数据甚至读取失败。4.3 仿真调试时的特殊技巧仿真调试有一个实物没有的优势——你可以直接操作外部元件来观察系统反应。比如用电位器代替 MQ 传感器的模拟输出旋转电位器就能看到 ADC 值变化继而观察报警逻辑是否触发。这种方式调逻辑比烧录实物快得多。另一个技巧是给 ADC 采样加上观察窗口。Keil 的 Debug 模式配合 Proteus 的 VSM 仿真可以直接在代码里设置断点然后打开 Peripherals 里的 ADC 窗口实时查看各个通道的转换值。这比串口打印更直观尤其适合排查 ADC 初始化配置错误导致一直读 0 的问题。Proteus 仿真时晶振参数不用太精确8MHz 或 72MHz 都能跑因为仿真器使用的是理想时钟模型。但如果你在代码里用了 HAL_Delay 做时序比如 DHT11 的 18us 起始信号务必保证仿真主频设置和 Keil 工程里 HSE_VALUE 一致否则延时比例会偏。5. 常见问题与排查技巧实录5.1 现象、原因与解决对照表这部分内容不是网上抄来的是我在这个项目调试过程中真实遇到的问题整理。每一条背后都对应着一次从“一脸懵”到“盯了半小时电路图”的经历。现象可能原因排查与解决办法LCD 白屏无字符IIC 地址错误LCD1602 IIC 模块地址一般是 0x27但也有 0x3F 的扫描一遍确认DHT11 读取失败显示 0% / 0℃起始信号时长不足把 DHT11_ReadData 里的延时从 1ms 改成 18ms再试ADC 值跳动巨大缺少滤波或采样周期过短改采样周期为最慢连续采样 10 次取平均蜂鸣器不响但有电流三极管基极限流电阻太大基极电阻 1K 比较稳10K 可能驱动不足仿真运行但按下按键无反应按键引脚上下拉不对Proteus 里按键默认接 VCC需要下拉电阻或代码里改成内部下拉空气质量指数始终 100 或 0传感器电压换算方向反了MQ 模块输出电压随浓度升高而降低换算公式需要取反程序烧录后系统跑飞主频配置和晶振不匹配检查 HSE_VALUEProteus 8MHz 对应源码里要设置为 80000005.2 几个容易被忽视的坑第一个大坑是 DHT11 的上拉电阻。很多便宜模块上自带上拉电阻但如果你自己焊最小系统板上拉电阻忘接的话DHT11 的数据引脚会一直处于浮空状态读取结果时好时坏。排查办法也简单用万用表量数据线对地电压正常空闲状态下应该是 3.3V 左右如果接近 0 或者抖动大概率就是上拉没接。第二个坑是 MQ 传感器的预热时间。实物上电后前 30 秒读数会是满偏的看起来像“空气质量极差”实际上只是加热丝还没热稳定。代码里加一个开机 30 秒倒计时提示让系统在预热完成之后才开始报警判断这个细节能避免很多误报警。第三个坑是 Proteus 仿真和实物的 LCD IIC 时序差异。Proteus 里的 PCF8574 模型对 IIC 时序要求非常严格如果你用了软件模拟 IIC延时差一点点就可能显示乱码。解决办法是把这个项目里 IIC 时钟的延时值调大一点优先保证仿真能过到实物上时再根据实测微调通常实物对时序的容忍度反而比仿真更高。5.3 从仿真到实物移植的三点建议虽然“仿真通过”能证明系统逻辑基本正确但从 Proteus 搬到洞洞板或 PCB 上依然会遇到新问题。我的经验是从仿真到实物要经历三个层面的变化时序层面、电气层面和干扰层面。时序层面刚才已经提到了DHT11 和高精度延时相关的参数都要重新标定。电气层面仿真里所有器件都是理想模型不存在压降和电流限制但实物上蜂鸣器和 LCD 的背光会拉低电源电压如果 AMS1117 的输入输出电压差不足MCU 会频繁复位。干扰层面最明显的就是 ADC 读数实物板走线不合理时电机或继电器动作瞬间ADC 值会瞬间跳到最大这时候就需要在硬件上加强滤波或者在软件里加一个限幅滤波一阶惯性滤波来抑制突变。我个人在实际焊板子的时候最深刻的一个体会是不要一次把所有外设都焊上去。先焊最小系统 串口通过串口打印确认 MCU 和时钟跑起来了再焊 LCD确认显示正常最后才上 DHT11 和 MQ 传感器。这样每一层都有明确的验证标准出了问题也能快速定位到具体模块而不是对着完整板子发愁。这种“分步点亮”的调试思路比任何一条调试技巧都管用。另外再分享一个小技巧针对 MQ 传感器的校准环境监测项目在首次上电时可以在“干净环境”下记录一个 ADC 基准值然后把这个基准值存储到 STM32 的内部 Flash 备用区域。后面再判断空气质量时用实时值和基准值做差值而不是直接用绝对电压算百分比。这样即使换了传感器模块或者环境湿度发生变化系统的报警阈值也不会漂移得太离谱。这个小改动虽然只花十分钟但能让项目的实用性和稳定性提升一个档次。
返回列表