1. 项目概述与整体设计思路
1.1 这项目到底能做什么
环境质量监测,听起来像个挺大的词,但落到实际场景里,其实就是我们身边最常见的几个痛点:办公室空气闷不闷、新装修的房子甲醛担忧(用传感器做间接判断)、机房温湿度是否超限、鱼缸或温室的环境参数监控。这套基于STM32的监测系统,就是在这些需求上做的一个标准化硬件方案。
整个系统以STM32F103C8T6为核心,外围挂上温湿度传感器、空气质量传感器,采集数据后在OLED屏上实时显示,一旦指标超限就触发蜂鸣器报警,同时通过串口把数据发出去。我去年帮朋友改造工作室的时候做了这套原型,后来整理代码时发现它非常适合拿来开源——硬件结构简单,代码逻辑清晰,而且仿真和实物都能跑通。
这个项目最适合三类人看:一是正在学STM32、想找一个完整实战案例的初学者,二是需要做课程设计或毕业设计的学生,三是不想从零画板子、想快速搭一套环境监测原型的工程师。它不像网上那些纯理论教程那样零散,而是从原理图到代码到仿真一步到位,照着做完,你对单片机开发的全流程就有完整概念了。
1.2 系统架构与方案选型
这套系统的数据流其实很简单:传感器负责感知物理量,单片机负责采集和处理数据,输出设备负责呈现和告警。用专业一点的话说,就是"感知层-控制层-输出层"的三层架构。
架构虽简单,选型却得抠细节。首先是主控芯片,我选了STM32F103C8T6而不是国产替代或更高级的F4系列,原因很直接:C8T6性价比高、资料多、Proteus仿真模型成熟。蓝色药丸板几十块钱一片,I/O口够用,72MHz主频跑这些传感器绰绰有余。对于环境监测这种低速数据采集场景,你用H7反而属于性能浪费。
传感器方面,温湿度我选了DHT11。有人会问为什么不上DHT22或SHT30,精度不是更高吗?我的看法是,做项目要看定位:如果做高精度仪器级产品,当然要上SHT30,但作为一套教学和原型验证性质的系统,DHT11完全够用,而且Proteus里自带DHT11模型,这决定了它才是仿真最顺畅的选择。空气质量检测我用的是MQ-2烟雾传感器,原因同样是它在仿真环境里有成熟模型,而且原理简单——本质就是个电阻值随气体浓度变化的分压电路,通过ADC读取电压即可。
显示部分采用0.96寸OLED,I2C接口,4根线就能搞定,在仿真里表现也稳定。报警部分用有源蜂鸣器加一个NPN三极管驱动,GPIO输出高电平就能控制。整体下来,这套系统的元器件成本控制在五十块钱以内,非常适合做入门项目。
1.3 为什么仿真和实物要一起做
很多初学者有一个误区:只追求实物,觉得仿真多此一举。说实话,这种想法耽误了不少人。仿真的核心价值不在于替代实物,而在于让你在没有硬件的时候把代码逻辑完全跑通,以及排查问题时多一只眼睛。
我做这个项目时,Proteus仿真和真实板子的调试流程是并行的:先对照原理图在Proteus里把电路搭出来,把固件逻辑在仿真环境里验证通过,再去焊接实物。这样做的好处非常明显——当实物出现问题的时候,我至少能确认代码逻辑没问题,问题大概率出在接线或者元器件本身,排查范围直接缩小一半。实际上我在调这个项目的过程中,确实靠仿真定位了一个棘手问题,这个后面在常见问题章节里细说。
2. 硬件电路设计(原理图拆解)
2.1 引脚分配与核心资源规划
画原理图之前,先把资源分配想清楚,这是很多新手容易忽略的环节。我见过不少同学拿到芯片就凭感觉接外设,结果GPIO冲突、复用功能打架,最后代码怎么调都不对。STM32F103C8T6有48个引脚,但实际可用的GPIO也就37个左右,好钢要用在刀刃上。
我这套系统最终的引脚分配如下:
| 外设 | 引脚 | 说明 |
|---|---|---|
| DHT11温湿度 | PA0 | 单总线数据,需外部上拉 |
| MQ-2烟雾传感器 | PA1 | ADC1通道1,读取模拟电压 |
| OLED(I2C) | PB6(SCL)/ PB7(SDA) | 硬件I2C1,需4.7k上拉 |
| 蜂鸣器 | PB0 | 有源蜂鸣器,高电平触发 |
| LED指示灯 | PB1 | 状态指示,低电平点亮 |
| 串口 | PA9(TX)/ PA10(RX) | USART1,用于数据输出 |
这里有一个很重要的原则:ADC引脚和I2C引脚尽量不要复用。虽然你可以通过切换模式来实现分时复用,但那样代码复杂度会上升,而且容易出问题。做项目不是考试,没必要给自己增加难度。另外,PA0我特意选了带WKUP功能的引脚,后续如果想扩展低功耗模式,这个引脚可以派上用场。
2.2 最小系统电路:别在细节上翻车
STM32最小系统包括电源、晶振、复位、BOOT和下载电路。很多初学者以为这部分直接用现成的最小系统板就行,不用自己画,这话对一半——用开发板当然可以,但如果你不亲手画一遍最小系统,你对MCU工作的"基础条件"就没有深刻理解。
电源部分我用AMS1117-3.3把USB的5V降到3.3V。输入输出各接一个10uF电解电容和100nF陶瓷电容做滤波,这是数据手册推荐的做法,别省。我见过有人为省两个电容,结果系统一上电就复位,测量发现电源纹波高达几百毫伏。单片机对电源的要求比你想的苛刻,电源不稳后面全是坑。
晶振电路采用8MHz主晶振加两个20pF负载电容。注意,电容值不是随便选的,要根据晶振的负载电容参数计算。如果晶振规格书的负载电容是12pF,那并联的两个电容一般在15-22pF之间。32.768kHz的RTC晶振我这里没有画,因为F103C8T6的RTC时钟可以用内部低速时钟,虽然精度差一些,但对环境监测这种场景没影响。
复位电路就是一个10k上拉电阻加一个0.1uF电容到地。BOOT0通过一个10k下拉到地,确保从Flash启动。下载电路留了SWD接口——4个排针,分别是VCC、GND、SWDIO(PA13)、SWCLK(PA14)。这里我强烈建议用SWD而不是串口ISP下载,因为SWD不占用USART资源,后面你想用串口做通信调试的时候会非常方便。
2.3 传感器接口电路与信号调理
DHT11的接口电路看起来简单——一个数据引脚接PA0,再挂一个上拉电阻。但这个上拉电阻的取值有讲究。DHT11的数据手册要求上拉电阻在4.7k到10k之间,我选了5.1k,实测波形最稳定。如果上拉太小,传感器可能拉不动总线;太大,信号上升沿变缓,时序容易出错。别小看这个电阻,我后面调试时遇到过一次通信不稳定,最后发现就是上拉电阻用成了100k,简直是个隐形杀手。
MQ-2传感器模块的输出其实是模拟量,但很多人没注意它内部有一个比较器电路。模块上通常有一个电位器可以调节阈值,输出端有两路:一路是数字量的DO,一路是模拟量的AO。我们这里要用的是模拟量AO,因为它接在LM393比较器的输入端之前,输出的电压范围大致是0到5V,对应传感器检测到的气体浓度。这个信号直接接到STM32的ADC引脚之前,需要用电阻分压把电压降到0到3.3V范围,因为STM32的ADC参考电压是3.3V,直接接入5V会烧引脚。我用了两个电阻,一个10k和一个6.8k组成分压电路,实测能把4V左右的峰值电压降下来,同时不影响精度,因为后端ADC的输入阻抗足够高,不会对分压比造成明显影响。
温湿度传感器的数据引脚接法更简单,数据线直接连PA0,外部上拉到3.3V即可。这里有一个容易忽略的点:DHT11的供电电压是3.3V到5.5V都可以,但数据引脚的电平取决于供电电压。如果你用5V给它供电,那数据引脚的高电平可能就是5V,同样不能直接进STM32。所以我的做法是统一用3.3V给DHT11供电,省去了电平转换的麻烦。
2.4 输出设备与报警电路
OLED显示屏用的是I2C接口,SDA和SCL分别接PB7和PB6。OLED模块本身一般自带上拉电阻,但我还是习惯在外部再挂两个4.7k上拉,宁可多此一举,也不能让总线因为上拉不足而出问题。OLED的供电是3.3V,刚好和主控共用一组电源。
蜂鸣器电路就是个典型的三极管开关电路。我用了S8050 NPN三极管,基极串联一个1k电阻连接到PB0,发射极接地,集电极接蜂鸣器的负极,蜂鸣器正极接3.3V。当PB0输出高电平时,基极电流约3mA,三极管饱和导通,蜂鸣器通电发声。这个1k电阻是限流用的,计算方式是:基极电流 = (3.3V - 0.7V) / 1k = 2.6mA,而S8050的放大倍数在100以上,集电极电流足够驱动一个30mA左右的蜂鸣器。
注意一个细节:蜂鸣器的工作电压要和它的规格匹配。我手头这个蜂鸣器是3.3V有源蜂鸣器,所以接在3.3V上没问题。如果你买到的是5V有源蜂鸣器,接在3.3V上音量会小很多,甚至不响。采购元器件时一定要看清楚规格,这是老生常谈的坑了。
LED指示灯电路就简单了,PB1接一个1k限流电阻到LED正极,LED负极接地。PB1输出高电平时LED点亮。等一下,我前面引脚分配表里写的是低电平点亮?这里统一一下:经典接法是GPIO高电平点亮,即输出1时LED亮。如果LED接法和这里一致,代码里直接HAL_GPIO_WritePin输出高电平即可,别让代码和硬件打架。
电源入口我加了一个自恢复保险丝和一个防反接二极管。防反接二极管用的是1N5819肖特基,压降只有0.3V左右,对5V电源来说损失不大。自恢复保险丝选500mA规格,防止意外短路烧USB口。这两个器件成本不到五毛钱,但对系统的安全性提升非常大。很多开发板不带这些保护,一旦插反就放烟花,不值当。
3. 固件开发与核心代码实现
3.1 开发环境搭建:Keil+CubeMX组合拳
这套系统的固件开发,我的建议是用STM32CubeMX生成初始化代码,然后在Keil MDK里写业务逻辑。为什么不用纯寄存器开发或者纯标准库?因为这个项目的重点是环境监测系统的整体逻辑,不是研究寄存器。用CubeMX可以快速生成时钟树、GPIO和ADC的初始化代码,把时间花在业务代码上,而且生成的代码结构清晰,对初学者来说也是个学习模板。
CubeMX中的关键配置项有这些。时钟树:外部8MHz晶振,倍频到72MHz系统时钟。ADC1:开启通道1(对应PA1),采样时间选择55.5周期,分辨率12位,连续转换模式关闭。这里说下采样时间为什么要选55.5周期。ADC采样时间越长,采样结果越稳定,但转换速度越慢。环境监测的数据更新频率不高,每秒采一次足够,所以采样时间选长一点没有性能压力,结果是数据更平滑。
I2C1配置为100kHz标准模式,这个速率对OLED显示足够了。USART1配置为115200-8-N-1,用于调试输出。GPIO就不用说了,PA0、PB0、PB1全部设置为输出模式,唯一需要注意的就是PA1必须设置成模拟模式,否则ADC读取会异常——很多人在这里栽过跟头,GPIO配置成复用模式去读ADC,结果读出来永远是0或者乱跳。
硬件I2C和软件模拟I2C之争也是个老话题。F103的硬件I2C确实名声不太好,网上抱怨的人一大把,问题主要集中在总线阻塞和错误处理上。但如果你用的是HAL库并处理好错误回调,硬件I2C其实是可以用的,而且不占用CPU资源。为了在仿真里更稳定,这个项目我用的是模拟I2C方式,只在CubeMX里把PB6和PB7设为普通推挽输出,然后用代码模拟时序。
3.2 DHT11驱动:单总线时序的坑与对策
DHT11用的是单总线协议,一根线既要发命令又要收数据,时序要求非常严格。整个通信过程大概是这样的:
主机先把总线拉低至少18ms,然后释放并延时20-40us,DHT11收到起始信号后响应,回一个80us的低电平,再回一个80us的高电平作为握手。之后传感器开始输出40位数据:8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。每一位数据的编码方式是通过高电平的持续时间来区分的:高电平26-28us代表0,高电平70us代表1。
用代码实现这套时序,最关键的就是延时函数要准。HAL库的HAL_Delay()只能精确到毫秒级,而DHT11时序需要微秒级延时,所以需要自己写一个微秒延时函数。我通常用一个简单的循环空转实现,配合DWT->CYCCNT做精确定时,F103跑72MHz,一个时钟周期约13.9ns,这样微秒级延时可以做得非常精确。
另一个要点是GPIO的方向切换。DHT11通信过程中引脚要不断在输出和输入之间切换:主机发信号时是输出模式,等传感器响应时又要切回输入模式。用HAL库的操作是HAL_GPIO_WritePin设置电平,再用HAL_GPIO_ReadPin读取数据。每个切换之间都必须严格遵守时序,比如发送起始信号后,必须在20-40us内释放总线并切换为输入,这个窗口很短,代码顺序错了就会导致通信失败。
我封装了一个DHT11_Read()函数,返回温度值和湿度值。函数内部有个超时保护机制,如果总线上一直没数据,超过10ms就返回错误码。这个设计非常有必要,因为DHT11有时候会不响应,如果代码没有超时保护,主程序就会卡死在这里。我在实际调试中就遇到过一次,传感器没插好导致程序死等,最后就是靠超时判断定位出问题。
3.3 模拟量采集:让STM32"看懂"电压
MQ-2的输出经过分压后进入ADC1通道1,我们在代码里要做的就是把ADC的采样值换算成实际电压,再通过电压变化判断气体浓度变化。
ADC是一个12位模数转换器,采样值范围是0到4095,对应电压0到3.3V。换算公式很简单:电压 = 采样值 * 3.3 / 4095。在这个项目里,我们不关心具体的ppm浓度值,因为MQ-2本身的精度达不到定量测量的水平,它更适合做"定性判断"——电压越高,说明气体浓度越高。所以我在代码里设置了一个阈值判断:ADC采样值超过800(大约对应0.64V电压),就认为空气质量异常,触发蜂鸣器报警。
在读取ADC的时候,有个很重要的工程实践:做多次采样取平均,而不是一次采样就直接用。因为ADC采集过程中会混入噪声,尤其是电源电压波动的时候,单次采样值可能偏差很大。我一般连续采样10次,去掉最大值和最小值,再取平均,这样得到的结果就比较稳定。这本质上是一个简单的软件滤波算法,效果非常明显。实测下来,滤波前后的抖动幅度从±50 LSB降到了±5 LSB以内。
模块在刚上电的时候有个预热过程,大约1分钟左右,这期间输出电压会漂移。所以在代码初始化阶段,我会做一次校准读取,把上电30秒后的ADC平均值作为基准值,后续判断时用当前值减去基准值的差值和阈值进行比较,这样能有效消除温漂和器件个体差异的影响。
3.4 逻辑主循环与报警状态机
主程序的逻辑结构采用了经典的单片机循环架构,一个while(1)循环里依次完成数据读取、数据显示、报警判断和串口输出。这种简单的轮询结构对这种系统足够了,没必要上RTOS。
关键点在报警判断的逻辑。如果只是简单地"超了就报警",那蜂鸣器会响个不停,使用体验很差。我加了一个状态机:正常状态、报警状态、恢复状态三种。当检测值超过上限时进入报警状态,蜂鸣器持续鸣叫;当检测值降到上限以下但仍在迟滞区间时,保持报警状态一段时间(比如30秒),防止指标在阈值边缘抖动导致蜂鸣器频繁开关。这个设计在工业现场叫"滞回控制",逻辑上类似于温控器的回差设置。
OLED显示部分,我用了一个轻量级的简化字库,在屏幕上轮播显示温湿度、气体浓度状态和系统运行时间。每2秒刷新一次显示内容,刷新频率太高反而会看到闪烁,因为OLED的驱动芯片SSD1306刷新全屏需要一定时间。
串口输出部分,我设计了简单的数据帧格式:帧头+长度+数据+校验。帧头是0xAA 0x55,长度是固定值,数据区包含温度和湿度的整数部分和小数部分、ADC采样值、报警状态,校验用的是累加和。这个协议格式虽然简单,但已经具备完整的数据帧特征,后续如果想接上位机或者物联网云平台,直接在这个基础上扩展就行,不需要推倒重来。
4. 仿真搭建与联调验证
4.1 Proteus环境下的电路搭建
仿真部分我用的是Proteus 8 Professional,版本8.9以上都支持STM32F103系列模型。在添加元件的时候,可以通过关键字搜索"STM32F103C8"找到对应型号,另外需要添加的元件清单包括:DHT11、MQ-2(Proteus里可能叫MQ2或者GAS-MQ2)、OLED显示屏(搜索"OLED 12864")、有源蜂鸣器、LED、电阻若干、电源端子。
典型的连接方式和实物一致。有一个地方需要特别注意:Proteus里的MQ-2模型不像实物那样需要预热,它的输出是直接根据输入浓度参数变化的,比实物"听话"得多。在仿真时你可以通过调节元件属性面板里的"GAS_CONCENTRATION"参数来模拟气体浓度变化,观察系统报警逻辑是否正常。但正是因为它太"听话"了,仿真顺利不能完全说明实物也顺利,这我在后面的问题排查章节会展开说。
OLED在Proteus里的连接稍微有点特殊。Proteus的OLED模型I2C地址默认是0x3C,和实物一致。如果你的代码里写的地址是0x3D,会出现屏幕无显示的"故障"。这其实是最常见的仿真翻车原因之一。
蜂鸣器在Proteus里要选择带"ACTIVE"标记的型号,也就是有源蜂鸣器模型,否则不会发声。
仿真电路连接完成后,把Keil编译生成的HEX文件加载到Proteus的MCU元件里就能运行了。双击STM32芯片,在Program File里选择编译输出的Hex文件,同时设置外部晶振频率8MHz。如果你在CubeMX里配置的外部晶振是8MHz,仿真里也设置成8MHz,两边不一致会导致串口波特率计算错误。
4.2 虚拟终端与逻辑分析:让数据"看得见"
Proteus里有个非常实用的工具叫Virtual Terminal,也就是虚拟串口终端,可以用来替代实物的USB转串口模块。把虚拟终端的RXD接到STM32的PA9(TX引脚),TXD接到PA10(RX引脚),波特率设置成115200,匹配代码里的配置,就能在仿真界面上实时看到串口输出。
另一个值得推荐的调试工具是Proteus的Logic Analyzer逻辑分析仪。调试DHT11时序的时候,把探针挂在PA0引脚上,可以直观地看到单总线上的电平变化波形。我第一次把这段波形放大看的时候,DHT11返回的数据位的高电平持续时间清晰可见,哪种高电平是0、哪种是1,一眼就能分辨——比自己用示波器打波形还直观。
仿真还帮我发现了一个只有逻辑分析才能暴露的问题:DHT11的数据位顺序。DHT11传输数据时,湿度高字节在前、湿度低字节在后、温度高字节、温度低字节、校验和最后。如果有人把字节顺序搞反,显示出来的温度和湿度就会完全错乱。在实物上这个错误很难察觉,因为你可能根本不知道真实温度是多少,但在仿真里用逻辑分析仪看清每一位的顺序后,这个问题就无处遁形了。
4.3 仿真通过≠万事大吉:两个反例
我第一次做这套系统仿真时,一切看起来都很完美:OLED显示正常,DHT11读数合理,调节MQ-2的气体浓度参数后蜂鸣器能正确报警。但等实物焊接完成后,问题接踵而至。
第一个问题:OLED在实物上显示正常,但蜂鸣器不响。我排查了接线,没问题;量了GPIO电平,程序设置高电平时引脚确实有3.3V输出。问题出在三极管基极的1k电阻变成了10k,基极电流只有0.26mA,三极管没法完全导通,蜂鸣器得到的电流不够,自然不响。这个坑在仿真里是发现不了的,因为Proteus不会模拟三极管的临界导通状态。所以别以为仿真通过就等于硬件正确,元器件的实际参数差异、焊接质量,这些仿真一概无能为力。
第二个问题:实物上DHT11读数极不稳定,甚至经常读取失败。我在仿真里用逻辑分析仪确认了代码时序没问题,怀疑是上拉电阻阻值不对。测量后发现,板子上本应是5.1k的上拉电阻被换成了100k,原因是我打样时料单弄错了。换上正确的5.1k电阻后,问题立刻消失。仿真通过是"代码逻辑正确"的必要条件而非充分条件,这个经验我算是刻在脑子里了。
5. 常见问题与排查技巧实录
5.1 DHT11通信异常:从时序到硬件一网打尽
DHT11报错是这套系统里最常见的故障,表现形式一般是读出来的温湿度全是0,或者读取函数一直返回超时。排查思路我总结成了一套流程,按照这个顺序走,大部分问题都能解决。
先检查硬件接线。DHT11的数据引脚有没有接对PA0,上拉电阻有没有接,阻值是否在4.7k到10k之间。这个看起来简单,但实际项目中,杜邦线松动、虚焊、上拉电阻虚焊是最常见的原因。
再检查GPIO配置。PA0有没有正确初始化为推挽输出模式,读取阶段有没有正确切换为输入模式。CubeMX里如果配置成了开漏输出,DHT11的低电平响应信号会被上拉电阻拉高,数据自然读不出来。
接着检查微秒延时函数是否准确。很多初学者用HAL_Delay(1)来延时1微秒,这完全不对——HAL_Delay的最小单位是毫秒。我用DWT计数器实现的微秒延时函数,精度可以达到几十纳秒,用逻辑分析仪实测过,误差在1%以内。
最后检查传感器供电。DHT11的供电电压允许范围是3.3V到5V,但如果供电电压太低(比如电池供电的板子电压已经掉到3V以下),传感器内部逻辑就乱了,表现为数据完全不对。这类问题在低功耗场景中特别坑人,因为代码和接线都没问题,纯粹是电压不够。
5.2 OLED白屏或花屏:I2C地址和上拉电阻的博弈
OLED不出字是另一个高频问题。首先要区分两种情况:完全没反应和显示花屏。
完全没反应,优先检查I2C地址。SSD1306驱动芯片的I2C地址是可以配置的,取决于模块上地址选择电阻。绝大多数OLED模块默认地址是0x3C,但如果你买到的模块是0x3D地址,代码里用的还是0x3C,那屏幕肯定是没反应的。我写驱动的时候会把地址做成宏定义,万一不行改一个宏就行。
如果地址没错但屏幕还是不亮,检查SDA和SCL的上拉电阻。I2C总线是开漏结构,必须有上拉电阻才能工作。有些OLED模块板载了上拉电阻,有些没有。如果你的模块既没板载上拉,外部也没接,那通讯根本建立不起来。用一个万用表量一下SDA或SCL引脚电压,如果稳定在3.3V附近说明上拉工作正常,如果电压被拉低到1V以下,基本上就是上拉电阻缺失。
花屏问题就更好定位了:一般是I2C速率过高或者信号线上有干扰。把I2C时钟从400kHz降到100kHz,大部分花屏现象会消失。F103的硬件I2C在400kHz下对PCB布线要求其实挺高的,如果你用的是杜邦线飞线,那400kHz就会出问题。仿真里倒是没有这个困扰,因为Proteus的虚拟总线不会受物理布线影响。
5.3 ADC读数跳变与MQ-2数据漂移
MQ-2的ADC读数在实物上跳变,第一个要排除的是电源噪声。MQ-2内部有个加热丝,工作时消耗约150mA电流,这个电流的波动会通过电源网络传导到ADC参考电压上,导致采样值跟着跳。解决办法有几种:一是给MQ-2单独用一个电源引脚供电,不从主控电源取电;二是在MQ-2模块的供电端加一个100uF电解电容,用储能来缓冲加热丝的电流冲击;三是ADC读取时引入软件滤波,多次采样取平均。
还有一个容易被忽略的因素是ADC参考电压不稳定。F103的ADC参考电压是VREF+引脚,在C8T6芯片上通常直接和VDD绑在一起。如果整个系统的3.3V电源有波动,ADC的参考电压也在波动,采出来的数据自然不准。如果你对测量精度有更高要求,可以外接一个基准电压源给VREF+,但那样电路就复杂了。对这个项目来说,软件滤波已经足够。
数据漂移则是另一个话题。MQ-2刚上电时,内部加热丝还没达到工作温度,输出会很低,随着加热时间增长,输出慢慢稳定下来。我刚做完实物时,发现ADC读数在上电后十分钟内持续上升,一度怀疑硬件出了问题。查了资料才知道这是MQ系列传感器的正常现象——需要预热。我后来在代码里做了校准流程:上电后先运行30秒,把这时的ADC读数作为基线存储起来,后续判断空气质量时,用的是当前读数减基线的差值。这样处理以后,环境空气质量变化引起的电压波动就能被准确捕获,加热丝老化导致的基线漂移也被抵消了大部分。
5.4 从仿真到实物移植的"水土不服"
最后聊聊仿真和实物之间的差别。Proteus仿真是个理想化的环境:电压永远是完美的3.3V,晶振永远按时起振,元器件参数永远和标注一致。实物世界完全不是这样。
我遇到过最典型的问题是晶振起振失败。仿真里MCU肯定会用外部晶振跑起来,但实物上如果晶振虚焊或者负载电容选择不当,晶振可能就不起振,程序根本不运行。排查方法是:把示波器探头点在晶振脚上,看有没有振荡波形。如果没有,检查晶振是不是插反了(无源晶振不分正反但有些贴片有脚位),或者换一个晶振试试。
还有个经典坑是电源上电时序。实物系统如果主控3.3V已经稳定了但传感器还没供电,传感器输出引脚可能会有不确定电平,灌入主控引脚导致芯片锁死。解决办法是让传感器的电源用主控的GPIO控制——像DHT11的VCC通过一个MOS管开关,主控上电后延时200ms再打开传感器电源,保证主控先稳定再让传感器工作。这个设计在低功耗项目中尤其重要。
最终心得
我做这套系统最大的收获并不是代码本身,而是养成了一个习惯:把仿真当成调试工具,而不是验证手段。仿真不只是用来"检查有没有画错"的工具,它更像一个带时间旅行功能的示波器——你可以暂停时间、放大波形、注入故障,这些在实物上调不了的操作,在仿真里都是举手之劳。这个项目我已经把除MQ-2传感器(因为选型差异比较大)之外的核心代码、原理图源文件、Proteus仿真工程整理好了,环境和逻辑部分大家可以直接复用。如果你也是从零开始接触STM32,强烈建议按这个流程走一遍——先仿真、再实物、遇到问题别急着网上乱搜,先把仿真和实物的差异比对清楚,你会发现很多问题自己就能定位出根源。希望这篇记录对你有用。