ARTICLE DETAIL

资讯详情

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

基于STM32的环境质量监测系统:从硬件设计到Proteus仿真全解析

基于STM32的环境质量监测系统:从硬件设计到Proteus仿真全解析 前阵子收拾之前的开发板翻出来一块吃灰很久的STM32F103C8T6最小系统板。想着别浪费不如把手头一直想做的环境质量监测项目给落地了。这项目断断续续搞了两周从画原理图、调驱动、烧代码到Proteus里搭仿真踩了不少坑也攒了不少经验。现在把完整的代码、原理图和仿真文件都整理出来了放出来给有需要的朋友参考。这块内容聊深了其实挺有意思不单是点灯和读传感器的事里面涉及GPIO模拟时序、ADC采样滤波、低功耗设计思路、上位机协议对接等等做一遍下来对STM32整个开发流程会有很直观的认识。1. 项目概述与整体方案设计思路1.1 这个系统到底能干什么先说说功能。这套环境质量监测系统简单讲就是一块STM32单片机采集周围环境的温湿度、空气质量主要是有害气体浓度和光照强度然后实时显示在OLED屏上。当某个指标超过你设定的阈值蜂鸣器会报警同时一个继电器可以联动外部设备——比如你设定PM2.5超标时自动开启空气净化器或者温湿度超限时自动打开排风扇这个就看你现场怎么接。还有个很实用的设计通过串口把数据上传到电脑我配套写了一个简单的Python环境监测工具源码里也放了电脑上能看到实时曲线数据自动记录到CSV文件方便后续做数据分析。如果你想往物联网方向扩展这套代码里串口协议部分可以直接改成ESP8266模块通讯上云或者推送到手机都非常顺。核心参数方面温度测量范围0~50℃精度±0.5℃湿度20~90%RH精度±5%RH空气质量用的是ADC采样输出0~4095的原始值再加浓度分级判断光照强度也是ADC采集分四个等级显示。这些指标在大多数室内环境监测场景下足够用而且传感器便宜、易买、资料多非常适合刚入门的学习者。1.2 为什么选STM32F103C8T6而不是其他方案这个选择说实话没什么悬念。STM32F103C8T6是Cortex-M3内核主频72MHzFlash 64KBSRAM 20KB在同类MCU里性能很均衡。价格方面国产替代芯片比如GD32、APM32已经把它打到几块钱一片项目成本极低。具体到本项目它有几个不可替代的优势。第一ADC资源丰富它有2个12位ADC、多达10个外部通道我同时采集空气质量传感器和光照传感器剩下的通道还能做电池电压检测第二I2C接口是硬件外设接OLED屏不用软件模拟I2C协议虽然我也提供了一份GPIO模拟版本作为备选第三个是生态太成熟了标准外设库和HAL库都有大量现成代码可以参考遇到问题很容易搜到解决方案。有些人可能会问这种简单应用用51单片机不就行了吗确实如果只是采个温湿度显示STC89C52也跑得动。但这个项目要同时驱动OLED、采集两路ADC、处理DHT11时序还要处理串口打印51会非常吃紧——光是跑一个浮点湿度换算就够呛。STM32的硬件I2C、DMA和定时器资源能帮你省掉很多底层功夫让你把精力放在业务逻辑上。1.3 传感器选型分析与对比传感器选择这块我当时整理了一张对比表直接贴在下面方便大家选型时参考传感器类型输出方式优点缺点参考价格DHT11温湿度单总线便宜、库多、接线简单精度低、采样周期至少1s2~3元DHT22(AM2302)温湿度单总线精度高、量程大贵、时序要求更严格15~25元SHT30温湿度I2C精度高、速度快需要外部电路10元左右MQ-2可燃气体/烟雾模拟量数字量灵敏度高、响应快功耗大、需要预热5~8元MQ-135空气质量模拟量数字量适合检测有害气体功耗大、需要预热5~8元BH1750光照强度I2C量程宽、精度高比光敏电阻贵3~5元光敏电阻光照模拟量超级便宜非线性、一致性差0.5元最终我选了DHT11 MQ-135 光敏电阻这套组合。原因很直白全项目物料成本可以压在30元以内DHT11的精度对于判断“是否舒适”“是否需要开窗”这种场景足够用MQ-135的电阻式传感器反应灵敏测烟雾、酒精蒸汽、甲醛等有害气体都能给到参考光敏电阻虽然线性度一般但做四个光照等级区分完全够了。注意如果你要上电就立刻读到准确读数那MQ-135需要提前预热至少3分钟。代码里我做了个策略——上电后前3分钟只显示“传感器预热中”避免用户看到乱跳的数据误判。2. 硬件设计与原理图核心拆解2.1 最小系统电路的几个关键点原理图是从STM32F103C8T6最小系统展开的。最小系统这Part如果你用的是现成的最小系统板可以跳过不画但如果你打算自己做板子这几点必须拿捏住电源滤波要到位晶振电路布线要小心复位电路不能省。电源部分我用了两种供电方案并联一是USB的5V输入经过AMS1117-3.3稳压到3.3V供MCU和传感器二是预留一个3.7V锂电池接口通过同一个稳压芯片供电。AMS1117的输入输出端都加了10uF和100nF电容组合一大一小的搭配是为了同时滤掉低频纹波和高频噪声。实测下来纹波可以控制在50mV以内ADC采样能稳定很多。晶振电路用的8MHz无源晶振两个20pF负载电容。这里有个很多人容易忽略的细节负载电容的计算公式是CL (C1*C2)/(C1C2) Cstray其中Cstray是引脚和布线的寄生电容一般取3~5pF。我用的20pF电容配合8pF左右的晶振匹配电容算下来整体容值比较接近。如果你完全不懂这个前期直接用市面上STM32最小系统板的典型值两个20pF基本不会出问题。晶振走线尽量短且对称两边不要走信号线这一点在画PCB时尤其要注意。复位电路就是经典的10K上拉电阻100nF电容到地外加一个按键。STM32是低电平复位按下去复位引脚被拉低松开后电容充电延时到高电平完成复位。这个RC时间常数约1ms能确保可靠复位。2.2 传感器接口电路的设计细节DHT11单总线的接线看起来很简单一条数据线加上拉电阻接PA0。但这里有个细节DHT11数据线上需要外接一个4.7K~10K的上拉电阻到3.3V。如果你用的是带插座的DHT11模块板上可能已经集成上拉了如果买的是裸传感器那就必须自己加上这个电阻否则数据读出来全是乱码。MQ-135模块我买的是带比较器的版本板上自带一个电位器可以调节阈值。不过我建议直接用模拟量输出接PA1这样能实时读浓度值灵活度更大。MQ-135模块的五根针脚分别是VCC可以接5V也可以接3.3V、GND、DO数字输出、AO模拟输出。需要留意的是模块工作在3.3V时模拟量输出范围大概是0~2.5V左右还在STM32 ADC的识别范围内如果你买的是必须接5V的版本输出超过3.3V就会超过ADC量程这个时候需要用电阻分压——比如一个10K串联、一个6.8K并联把5V信号降到3.3V以内。光敏电阻这部分最简单它和10K电阻组成分压电路中间抽头接到ADC引脚PA2。环境光线越亮光敏电阻阻值越小分压点电压越高。代码里通过这个电压值来划分四个光照等级。2.3 OLED显示、蜂鸣器和继电器驱动OLED用的是最常见的0.96寸、128x64分辨率、I2C接口的SSD1306。SDA接PB7SCL接PB6I2C1。注意I2C上拉电阻——如果模块上没有集成需要你在原理图上加上两个4.7K上拉电阻分别到3.3V。没有上拉的话I2C通信大概率会卡死在忙检测上这是新手最容易忽略的问题。蜂鸣器我建议用有源蜂鸣器而不是无源蜂鸣器。有源蜂鸣器内部带振荡源只要给高电平就响控制简单无源蜂鸣器需要提供特定频率的方波才能发声会占用一个定时器资源。如果你买的是无源蜂鸣器就得用定时器PWM输出一个2.7kHz左右的方法去驱动也不是不行只是代码复杂度上来一点。有源蜂鸣器接PA3通过NPN三极管驱动SS8050低电平导通。为什么不用单片机直接驱动因为蜂鸣器正常工作电流有20~30mA超过了STM32 GPIO最大灌电流的限制单个引脚最大25mA直接接会加速引脚老化、也可能导致复位异常。继电器模块同样不能MCU直驱必须加三极管或达林顿管。我用的是一路5V继电器模块IN脚低电平触发通过PC13控制。PC13是STM32F103上少数几个可以做普通输出但是驱动能力较弱的引脚用它来控制三极管完全没问题。继电器的VCC接5VCOM口接外部设备比如220V的净化器就通过这个继电器的触点来控制通断——注意这里隔离极其重要低压控制部分和高压负载部分不要在原理图上太过靠近走线间距最好保持3mm以上你在自己的项目里做实物接线也一定要注意安全。3. 软件架构与核心代码实现3.1 工程结构和代码组织方式代码工程我基于**标准外设库(SPL)**写的没有用HAL库。原因倒不是说SPL比HAL好——事实上新项目用HAL库开发效率更高。但网上大量教程、例程和坑的解决方案都是基于标准库的对于学习来说SPL的寄存器操作更直观能帮你真正理解MCU的工作方式。工程目录结构如下Project/ ├── USER/ # 主函数、中断处理 │ ├── main.c │ ├── stm32f10x_it.c │ └── system_stm32f10x.c ├── HARDWARE/ # 外设驱动 │ ├── OLED/ # SSD1306显示驱动 │ ├── DHT11/ # 温湿度传感器驱动 │ ├── MQ135/ # 空气质量传感器 │ ├── LIGHT/ # 光敏电阻ADC采集 │ ├── BEEP/ # 蜂鸣器驱动 │ └── RELAY/ # 继电器控制 ├── CORE/ # 启动文件和内核相关 ├── SYSTEM/ # 延时、串口等系统级代码 └── OBJ/ # 编译生成的文件各模块独立成文件、提供统一的初始化函数和读写接口这样代码可读性高、也方便移植。比如以后想换SHT30温湿度传感器只需要把DHT11的.c和.h文件换成SHT30的然后修改应用层的调用接口就行不用整个工程推倒重来。主循环的结构是典型的前后台架构一个while(1)大循环轮询采集数据、刷新显示SysTick定时器提供1ms时基串口中断处理数据的收发。这个架构虽然“土”但胜在稳定、容易理解和调试非常适合这种中小型项目。等以后上了RTOS会发现把设备驱动都封装成独立文件是多重要——每个传感器跑一个任务信号量传递数据改动极小就能从裸机迁到RTOS上。3.2 DHT11单总线时序解析与踩坑DHT11这块是整个项目最需要耐心的部分。它的单总线协议完全靠GPIO的输入输出模式和延时配合来读时序信号线拉低拉高的时间差就代表了0和1。整个读取流程是这样的主机发起起始信号主机把数据线拉低至少18ms再拉高20~40us然后释放总线设置成输入模式DHT11响应信号它会拉低80us再拉高80us表示“我准备好传数据了”数据传输DHT11连续发送40位数据5个字节分别是湿度整数、湿度小数、温度整数、温度小数、校验和。每一位数据以50us的低电平开始紧接着的高电平时间决定这一位是0还是1——高电平持续26~28us表示0持续70us表示1校验前四个字节相加取低8位跟第五个字节比较相同则数据有效核心代码实现uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for(i 0; i 8; i) { while(DHT11_IN() 0); // 等待50us低电平结束 delay_us(30); // 延时30us再判断电平 if(DHT11_IN() 1) { // 此时还为高说明是“1” data | (1 (7 - i)); while(DHT11_IN() 1); // 等待剩余高电平结束 } } return data; }注意代码里delay_us(30)这个数字很关键。网上很多博客写的是20us或者25us实际测试下来30us这个值最稳妥。为什么DHT11数字“0”的高电平典型值是27us左右“1”的高电平典型值是70us左右。你在大概30us的时候去采样读“0”时已经过了它的高电平窗口此时为低读“1”时它还持续在高位采样结果为高。如果你用的延时函数偏快或偏慢或者系统主频因为某些代码路径变了这个阈值就可能踩到两个窗口的边界导致数据错乱。还有个大坑DHT11两次读取间隔必须大于1秒。如果读得太快传感器没准备好返回的全是0x00校验也过不了。我的代码里专门加了个时间戳判断最小间隔1.5秒实测稳定了很多。另外DHT11对环境温度也敏感在湿度很大的环境中比如浴室数据也会漂移这个属于传感器物理特性软件上只能在代码里做一点简单的平滑处理来解决——我用了滑动平均滤波取最近3次有效数据的平均值稳定性提升了一个档次。3.3 ADC采样与滑动滤波实现MQ-135和光敏电阻的模拟量都是通过ADC1的通道0、1来采集的。STM32F103的ADC是12位的参考电压3.3V所以采样值的换算公式就是实际电压 ADC采样值 * 3.3 / 4095代码里我设置了ADC工作在扫描模式同时采样两个通道void ADC_Init_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // PA1和PA2配置为模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // ADC1配置 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 2; ADC_Init(ADC1, ADC_InitStructure); // 通道顺序和采样周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); }有个经验值得在这里多说一句ADC的采样周期尽量设置长一点。我用的239.5个周期对应的采样速度在1MHz ADC时钟下大约是39.7kHz对于这种缓慢变化的传感器信号来说绰绰有余。采样周期越长采样电容充电越充分结果越稳定。之前用短采样周期比如1.5个周期采集光敏电阻的时候数据跳得厉害同一光线下读数能差100多个LSB加长采样周期后基本稳定在5个LSB以内。软件上我也叠加了一层滑动平均滤波维护一个长度为8的环形缓冲区每次新读数进来就覆盖最老的数据然后算平均值。这比简单的中值滤波更适合这种有连续噪声的信号既能平滑突变响应速度也还够快。空气质量传感器本身就有一个好几秒的响应时间所以滤波并不会让系统显得迟钝。3.4 OLED显示与多页面状态切换OLED驱动用的SSD1306标准库里有个经典的移植版本我直接在它的基础上改的。显示逻辑分成两层底层是SSD1306的驱动函数初始化、清屏、画点、显示字符、显示字符串上层是应用界面界面布局、数值刷新、图标显示。主界面显示分三个区域顶部显示标题和当前模式中间显示温湿度和空气质量数据底部显示光照等级和告警状态。OLED是逐页Page内存映射的共8页每页128字节一共8*128 1024字节的GRAM。你每次写入一个字节实际上就改变了8个像素点一个Page里的一列。显示刷新的性能优化思路是不要对整屏刷新只对有变化的数字区域做局部更新。比如温度从25.3变成25.4只需要更新那4个字符宽24像素高16像素的区域这样每次刷新只需写96个字节OLED的I2C总线压力小很多128x64全屏刷新要传1KB数据在400kHz I2C下都是将近10ms的耗时。实测下来局部刷新后整体帧率能到30fps以上肉眼看不到闪烁。界面还做了一个自动轮播切换三页屏幕每5秒自动切换一页内容包括实时数据、历史趋势条、系统设置参数。按键部分我留了一个向上键和一个确认键长按确认键进入阈值设置界面可以调报警阈值。按键扫描放在了定时器中断里用了消抖算法——检测到按下状态持续20ms以上才认为真的按下了杜绝了机械按键抖动导致的误操作。4. 仿真搭建与调试记录4.1 Proteus工程结构和元件配置仿真这块我用的是Proteus 8.9版本配合Keil MDK5做联调。这里提醒一下Proteus对STM32F103C8T6直接仿真其实是有限制的它提供的模型并不算完整所以我最终用Proteus做的是“核心逻辑验证”也就是不依赖真实硬件的部分——DHT11数据模拟、OLED显示逻辑、告警联动。仿真的搭建步骤很简单在Proteus里搜索添加STM32F103C8T6、OLED屏用“SSD1306”模型、虚拟串口COMPIM、虚拟LED、开关、电位器。DHT11在Proteus里没有现成的传感器模型我是用一个“DHT11”库元件不同版本的Proteus对这个器件的支持情况不一样新版Proteus 8中有DHT11模型从“Sensors”类别下可以找到如果找不到这个模型也可以用一个信号发生器模拟单总线时序配合一个可变电阻模拟温湿度对应的电压信号。元件清单元件Proteus模型关键字数量说明主控STM32F103C8T61LQFP48封装OLEDSSD13061I2C接口电阻RES若干上拉、分压电位器POT2模拟传感器模拟量LEDLED-RED4指示不同状态开关BUTTON2模拟按键输入虚拟串口COMPIM1串口调试4.2 仿真中几个典型的调试手法仿真最爽的地方是你能任意控制传感器输入来验证代码逻辑。我这里用两个电位器一个模拟MQ-135的输出电压一个模拟光敏电阻的分压值。调电位器的旋钮OLED上的空气质量等级和光照等级会实时变化。你还能在代码里临时改报警阈值验证蜂鸣器和继电器控制的逻辑是不是按预期触发。串口调试用的是COMPIM虚拟串口配合PC端“虚拟串口工具”比如VSPD虚拟串口软件把Proteus里的COMPIM映射到COM2然后电脑上用串口助手COM3打开就能看到单片机打印的环境数据了。这里的波特率我设置的1152008位数据位无校验1位停止位。日志格式建议用逗号分隔的字符串方便Excel直接打开并做数据可视化。一个比较实用的调试技巧是用Proteus自带的图表模式分析ADC输入和输出的关系。在系统运行前先给ADC输入一个固定电压比如1.65V看代码里读到的原始值是不是在2048左右1.65V/3.3V*4095约等于2048。如果不是检查一下ADC配置里参考电压设置和分压电阻的计算值是不是匹配。这个步骤能帮你快速定位软件计算错误和原理图分压设计错误。4.3 仿真和真实硬件的差异心里要有数这一点我必须重点强调仿真通过不代表实物就一定能正常工作。最典型的区别在几个方面。第一Proteus里的DHT11模型是理想化的不存在时序竞争问题。而在真实硬件上主频误差、GPIO翻转速率、中断抢占时机都会影响单总线的时序窗口这就是为什么仿真里跑得丝滑实机上偶尔会读不到数据的原因。第二模拟外设的噪声环境在Proteus里不存在它输入给ADC的是绝对精确的电压。真实的电源会有纹波MQ-135的输出在预热阶段甚至会有剧烈的电压漂移这些都是软件上要提前想好对策的地方——滤波不能只靠硬件软件滤波是必需品。第三OLED屏在仿真里显示的刷新速度和效果跟你实际看到的I2C总线上的波形会有偏差。如果你在仿真里做全屏刷新发现没问题但实物上明显闪烁大概率是I2C时钟频率设置太高或者总线上挂的器件的电容型号偏大拖慢了沿的翻转。所以我的建议是仿真用来验证逻辑和看波形实物用来验证时序和看效果。两者配合得好整个开发周期能缩短一半。5. 常见问题与排查技巧实录5.1 DHT11读数失败或全零怎么定位这个大概是项目里被问得最多的问题。新手拿到代码烧进去发现串口打印的温湿度全是0.00第一反应就是传感器坏了其实大概率不是。排查顺序应该这样来先查接线——DHT11三根线也有四根线的多一根NC悬空VCC接3.3V或5VGND接地DATA接MCU控制的引脚别忘了数据线上必须有一根4.7K~10K的上拉电阻到VCC再查电源——用万用表量一下DHT11的VCC和GND之间的电压如果用的是5V且降压芯片电流不够电压可能被拉低到4V以下传感器直接无法工作最后是时序——用示波器抓一下数据线的波形看看有没有起始信号和响应信号如果只是高电平一条直线说明传感器没收到命令或者坏掉了。软件层面有一个非常容易忽视的问题DHT11初始化最容易忽略的是第一次读取时需要加一个“唤醒时间”因为传感器上电后需要1~2秒稳定如果上电后立刻执行读取动作大概率失败。我的代码里在DHT11初始化函数中加了systick_delay_ms(1500)的等待然后再尝试读取这个改动帮我解决了很多奇怪的现象。5.2 OLED不显示或显示乱码的排查方向OLED不亮或者出现满屏雪花这种问题一看多半是硬件连接的坑而不是代码问题。先量I2C地址。SSD1306的I2C地址默认是0x787位地址是0x3C但有些模块的地址跳线被改过了地址变成了0x7A。写代码时如果固定用0x78初始化那模块就永远不会应答。解决办法是写一个I2C扫描程序把总线上的所有设备地址扫出来打印在串口上非常直观。再看上拉电阻。我前面提过I2C总线上必须有两个上拉电阻SDA和SCL各一个如果模块板载已集成就可以忽略但很多裸屏是没有的。没有上拉的话表现很迷——有时候能初始化、有时候黑屏纯粹看运气。最后关于GPIO复用STM32的PB6、PB7在I2C1功能下是开漏模式如果你初始化的时候配置成了推挽输出总线通信就会出问题。注意用HAL库时要调用HAL_I2C_MspInit里配置的引脚不要自己在外面再配置一遍否则容易发生配置覆盖。5.3 ADC数值跳动大或者固定为4095/0怎么办ADC读数固定在4095说明输入引脚电压超过了参考电压多半是传感器模块的输出直接超过了3.3V。这时候不要再调代码了去看硬件查一下模块供电是不是5V输出有没有超过3.3V如果是就一定要用分压电阻。分压计算很简单比如你想把0~5V映射到0~3.3V那分压比就是3.3:1.7用两个电阻比如10K和5.6K分压中间抽头接ADC引脚就行。ADC读数固定在0一个原因是传感器输出本身低于参考地这种情况少见另一个更常见的原因是引脚配置错误——GPIO没有配置成模拟输入。再有就是你忘了使能ADC时钟导致读取寄存器返回0这在初始化的代码里要反复确认。最后是读数跳动大这个问题。排除硬件滤波电容不足的因素后软件滤波一定要加上。在我的工程里ADC读取用的是DMA连续转换滑动平均滤波窗口大小8已经非常稳了。如果你用轮询读取会发现同样的光照条件下读数跳动范围可能在50~100个LSB之间这个跳动会直接影响阈值判定的稳定性比如告警阈值附近就会频繁触发和解除。5.4 串口打印乱码和通信异常的处理思路串口乱码这个问题十个人有八个遇到过。先确认波特率是否一致——如果代码里配置的是115200而串口助手打开的是9600那打出来的必然是乱码。这是最常见的低级错误先检查这个。排除波特率后下一个要查的是时钟频率。STM32F103的USART波特率由APB2总线时钟最高72MHz和波特率寄存器共同决定如果你的时钟配置不是按外部8MHz晶振、PLL倍频9倍到72MHz而是用了内部HSI的8MHz那生成的波特率就会有偏差。误差大了比如超过2%通信就会断断续续甚至完全乱码。调试这种问题建议把代码里的时钟配置先确认一遍把这个变量排除掉。第三个检查点是电平匹配。如果你在用TTL转USB模块确认模块的供电电压跳线是不是3.3V。模块上用5V供电时TX引脚可能输出5V的高电平虽然短期不会烧坏STM32的引脚但会干扰它的电平判断表现就是偶尔收到乱码或丢字符。切换到3.3V供电跳线就正常了。5.5 继电器频繁通断、蜂鸣器乱响的现象及对策这个问题的根源是“阈值抖动”。环境数据在阈值附近波动数据一超过阈值就报警下一瞬间又回到阈值以下就立刻恢复反复触发导致继电器咔哒咔哒频繁通断时间长了触点寿命急剧减少。解决办法有两大类。第一类是滞回控制这是硬件和软件都常用的方案。比如你设定PM2.5浓度超过350触发报警低于250才解除。也就是“触发阈值”和“恢复阈值”设置成不同的值两个阈值之间留一个“死区”数据在这个范围内不会触发状态切换。实现起来就是几个if条件判断if (air_quality TRIGGER_THRESHOLD) { alarm_on 1; } else if (air_quality RESUME_THRESHOLD) { alarm_on 0; } // 中间区域维持原状态不加if判断就不会翻转第二类就是软件延时生效。触发条件满足后保持至少3秒的报警状态3秒后才允许状态翻转。这样即使数据频繁抖动也不会导致继电器快速通断。如果这两种方案都用了还是出现异常那就要回头检查ADC滤波时间常数是否合理——如果滤波窗口太小噪声没滤干净状态抖动就很难消除。6. 开源资源与后续扩展建议6.1 仓库里的文件怎么使用整个项目我按照开源方式打成了压缩包里面包含几个核心文件USERS/Project/目录Keil MDK工程文件用Keil 5版本打开需要提前装好STM32F10x标准外设库和对应的Device PackHARDWARE/目录各个外设的驱动源码包含全面的注释修改参数时建议先读注释DATASHEET/目录DHT11、MQ-135、SSD1306等器件的原始数据手册查时序、查寄存器看这里PCB/目录原理图和PCB工程的源文件基于立创EDA编辑可以直接打开也可以转出Gerber去打样SIMULATION/目录Proteus仿真工程双击打开就能看到完整电路和固件运行效果HOST_TOOL/目录Python上位机源码需要安装pyserial和matplotlib库才能运行建议入手顺序先在Proteus里把仿真工程跑起来熟悉整个系统的功能流程然后再去看源码、研究驱动怎么写最后再去Gerbview或立创EDA看原理图、研究硬件设计。这样形成一个“应用层-驱动层-硬件层”的认知链路学起来效率最高。6.2 想进阶扩展这几个方向值得做本项目只是一个基础平台在你的手里它可以变成非常不一样的东西。方向一是加无线传输模块。代码里串口数据协议部分直接改——把USART_SendData换成ESP8266的AT指令发送就能把数据推到云平台。目前代码里用串口输出的是类似25.30,65.00,888,2,1\r\n的格式你用ESP8266的透传模式直接把这串数据通过TCP发给服务器即可。服务器端配合Node-RED或自建的MQTT Broker就能实现手机端的远程监控。方向二是加SD卡存储。STM32F103C8T6的SPI1可以挂TF卡模块参考FatFs的移植文档把每次采集的数据以CSV格式追加到文件里这就是一个简单的数据记录仪。你可以把它放到蔬菜大棚或实验室里连续记录好几天的数据事后再取回来分析。方向三是内核升级到RTOS。目前main函数里是裸机轮询但每个传感器的读取时间不同DHT11至少间隔1秒MQ-135建议10秒OLED刷新20Hz用FreeRTOS创建三个独立的Task各自管理各自的采样频率代码结构会清晰很多。当你想再加个网络协议栈或者键盘扫描的时候RTOS的优势会彻底显现出来。6.3 开源文档贡献的一点心得最后关于开源这件事多说两句。我这次的开源不只是丢代码还把数据手册整理成了中英对照目录把遇到的坑写进了每个模块文件的文件头注释里。这样做的好处是即使一两周之后我自己回来看代码也能快速回忆起当时为什么这么写。对于看到项目并想学习的其他人这种“带注释的血泪史”比什么教程都有用。你有能力的话也建议把自己做过的小项目整理好发出来。不要求代码多高级、门槛多高哪怕只是一个传感器点灯只要你把原理图、代码、仿真配套齐全并且把设计决策的理由讲清楚对初学者来说就是非常宝贵的资源。做开源这件事本质上不是为了证明自己多厉害而是让后来者少踩一遍你踩过的坑。这次项目里用的代码、原理图和仿真文件都在仓库里下载下来跟着动手做一遍比光看我这里讲感悟有用得多。
返回列表