
做课设或者拿大作业练手的时候“STM32温湿度采集控制系统”几乎是必选题。很多人一上来就想着焊真机买传感器、调杜邦线、烧了几块板子才发现精力全花在硬件问题上根本没机会把“采集—显示—控制—报警”这一整条链路彻底打通。这次我换了个思路用Proteus仿真把硬件环境完全复现出来配合Keil写代码和联调最终产出一套完整可交付的项目成果包括代码、设计报告和演示视频。这篇内容我从方案选型开始讲把Proteus电路搭建、Keil代码实现、联调排错的完整过程拆开最后会专门说设计报告和演示视频怎么做才拿得出手。无论你是嵌入式方向的学生还是想快速掌握仿真开发流程的爱好者这篇文章应该都能让你少踩不少坑。1. 方案选型仿真方式做温湿度控制系统到底值不值1.1 从功能需求倒推设计方向拿到这个题目第一步不是打开软件画原理图而是把“要做的系统”拆成清晰的功能模块。一个完善的温湿度采集控制系统至少要包含四件事环境温湿度数据采集、当前数值的实时显示、超阈值时的控制动作、状态异常时的报警提示。在实际的工程训练中很多人会把重点放在代码量上觉得功能越多越复杂越好。但我更建议反过来思考这套系统的核心价值在于数据链路是否完整传感器采集到的信号经过单片机处理最终能不能驱动执行机构产生正确的物理动作。只要这条链路是通的项目的主干就立住了。在这个前提下我决定把系统拆成五个基本单元以STM32为主控核心DHT11作为温湿度采集前端LCD1602负责显示直流电机模拟风扇做散热控制蜂鸣器做超限报警。整体是一个闭环采集—处理—显示—控制—反馈。1.2 为什么选STM32F103C8T6搭配DHT11STM32F103C8T6是教学和课程设计里用到最多的芯片之一64引脚、72MHz主频、丰富的定时器和GPIO资源做温湿度采集绰绰有余。更重要的是它的资料极其庞杂任何关于串口、定时器、ADC、外部中断的配置几乎都能找到现成的参考这对一个需要兼顾代码和文档产出的项目来说是很大的便利。温湿度传感器选DHT11原因也很直接它用单总线协议一根数据线就能完成传输电路连线上比I2C的SHT30、模拟量的LM35都省事而且Proteus元件库自带DHT11仿真模型不需要额外导入第三方库省掉了最麻烦的环境配置环节。这里有个很多人忽略的点DHT11的精度并不高温度±2℃、湿度±5%但做控制演示完全够用。如果追求高精度成本和调试复杂度会大幅上升对课程设计这个层面的项目来说并不划算。1.3 Proteus仿真和真机开发之间的性价比权衡在做方案对比的时候我认真评估过“直接上真机”和“先用仿真跑通”这两种路线。真机开发有它不可替代的价值比如引脚电平的实测、传感器在真实环境下的响应、电源纹波对系统的影响。但它的成本也很高器件采购周期、焊接调试时间、硬件故障的不确定性这些对时间有限的课设场景来说都是不小的负担。Proteus仿真的核心优势是可重复性和可演示性。代码崩了点一下重置就能重来硬件接错了改连线重跑一遍就行演示的时候还能随时更改参数向评委展示系统在不同条件下的反应。对于“理解系统工作原理、打通软件逻辑”这个主要目标仿真环境反而比真机更高效。但我也要说清楚边界Proteus是逻辑仿真的工具它模拟的是数字电路的行为和时序模拟不了真实传感器的物理特性。在仿真里你改变的是模型参数而不是实际环境变化。所以仿真跑通之后心里要有数——真机环境下还会面临电源噪声、信号干扰、传感器响应延迟这些问题这些都需要在真机阶段去验证不能指望仿真帮你扛过去。2. 仿真电路搭建Proteus里从零搭出完整硬件系统2.1 元件选择与库文件准备Proteus 8.9以上版本的元件库已经相当完整STM32F103C8T6、DHT11、LCD1602、直流电机这些常用器件都能直接搜到。新建一个工程之后先把下面这些元件添加到原理图元件搜索关键词数量用途STM32F103C8T6STM32F103C81片主控制器DHT11DHT111个温湿度采集LCD1602LM016L或LCD16021块数据展示直流电机MOTOR-DC1个模拟散热风扇蜂鸣器BUZZER1个超限报警电位器POT2个模拟环境温湿度变化这里有一个我自己踩过的小坑不同版本Proteus里LCD1602的元件名不一样老版本叫“LM016L”新版本直搜“LCD1602”也能找到。建议优先用LM016L这是Proteus里经典的标准LCD模型引脚定义和16×2显示驱动兼容性最好。2.2 硬件连线的核心细节原理图连线是先动手做的第一件事连对了后面一切顺畅连错了调试阶段一定会被折磨。我最终的接线方案是DHT11的数据线接PA0同时加一只4.7kΩ上拉电阻到3.3VLCD1602的数据口接PB0到PB7RS接PB11E接PB10RW直接接地直流电机正极接PA1输出的PWM信号通过一个NPN三极管做驱动蜂鸣器接PA3用低电平驱动。上拉电阻这件事值得单独拿出来说。DHT11的数据线在空闲状态下必须保持高电平通信时靠拉低总线来发起时序如果少了上拉电阻读取出来的数据经常是0xFF或者随机跳变。有过硬件经验的朋友都知道这是单总线器件最常见的故障点在Proteus里同样存在——我之前试过把DHT11的DATA引脚直接挂到PA0上不加上拉结果读出来的温度和湿度全是乱码。电源和地线也要注意。Proteus仿真不会像真机那样因为电源走线不当产生噪声但还是要养成良好的习惯每个芯片的VCC和GND都要明确接好去耦电容不加仿真是可以正常跑的但我建议还是加上这样原理图更完整报告里也能解释清楚。时钟电路方面Proteus里的STM32F103C8T6默认使用内部RC时钟即可运行不需要像真机那样必须外接8MHz晶振。但如果你在Keil工程里配置的是外部时钟源仿真时就会跑不起来两边必须一致。2.3 DHT11仿真模型的行为差异是最大的隐藏坑我在仿真环境里花时间最久的地方不是连线而是搞清楚Proteus里DHT11模型的工作方式。这里有一个和真实传感器非常不同的关键行为真实DHT11会随环境温度变化而自动改变输出值但Proteus里的DHT11模型默认输出一组恒定值它并不“感知”环境变化。怎么模拟温度变化我在实际项目中试过两种办法。第一种是在仿真运行暂停的状态下双击DHT11元件在属性面板里直接修改Temperature和Humidity字段改完再继续运行系统就会读到新的数值。这种方式最稳定但演示效果比较生硬。第二种办法是利用模型预留的参数输入引脚在模型附近放置电位器把它和一个虚拟电压节点关联起来用它来动态调节温湿度值。具体引脚位置在不同版本里略有差异右键DHT11查看引脚定义就能确认。这个方法在录制演示视频时特别好用——你可以当着镜头拧电位器让屏幕上的温度从25℃慢慢升到35℃风扇随之启动整个过程一气呵成非常有说服力。3. Keil工程的搭建与控制逻辑的实现3.1 工程文件结构与基础初始化Keil这边我用的是MDK5新建一个基于标准外设库的工程。可能有人会问现在STM32CubeMXHAL库不是主流吗为什么还选标准库原因是这个项目规模不大裸机程序逻辑简单标准库的直接寄存器操作方式更容易让人理解每一步在干什么报告里写代码说明时也更直观。工程目录我建议这样组织project_root/ ├── Core/ # 主函数、中断服务 │ └── main.c ├── Driver/ # 各外设驱动模块 │ ├── dht11.c │ ├── dht11.h │ ├── lcd1602.c │ ├── lcd1602.h │ ├── delay.c │ └── delay.h └── Output/ # 编译生成的hex文件基础初始化的逻辑并不复杂按照GPIO、延时、LCD、外设的顺序依次完成。GPIO配置上PA0设置为开漏输出这符合单总线协议的电气要求PB0到PB7设为推挽输出PA1用定时器映射为PWM输出通道这样风扇可以实现调速而不只是简单的开关PA3用推挽输出控制蜂鸣器。3.2 DHT11时序解析从时序图到可用代码在编写DHT11驱动时成功与否完全取决于你能不能在代码里精确模拟单总线时序。基本的数据链路由三个环节构成主机发起始信号、DHT11发应答信号、DHT11输出40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。核心的读取函数我写成这样uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] {0}; uint8_t i, j; // 主机发送起始信号拉低总线至少18ms GPIO_ResetBits(GPIOA, GPIO_Pin_0); delay_ms(20); GPIO_SetBits(GPIOA, GPIO_Pin_0); delay_us(30); // 检测DHT11的80us低电平应答信号 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) RESET) { while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) RESET); // 等待低电平结束 delay_us(40); for(j 0; j 5; j) { for(i 0; i 8; i) { while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) SET); // 等待50us低电平过去 delay_us(30); if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) SET) { data[j] | (0x80 i); delay_us(20); } } } } // 校验前四个字节之和是否等于校验字节 if(data[4] (uint8_t)(data[0] data[1] data[2] data[3])) { *humidity data[0]; *temperature data[2]; return 1; } return 0; }这个函数里最关键的逻辑是延时30微秒后再判断电平。数据位“0”的高电平持续26到28微秒数据位“1”的高电平持续70微秒。在30微秒这个时间点采样刚好能区分开两者。延时太短会把“0”误判为“1”太长则会错过“1”的采样窗口。这段代码里的延时时间需要配合芯片主频微调72MHz主频下延时函数的参数都能对上。3.3 控制策略阈值判断不是简单的开关在执行机构控制上我见过很多人的代码是“温度大于某个值就开风扇小于就关”这种做法演示起来效果很差——温度在阈值边缘抖动时风扇会频繁启停既伤负载也影响观感。正确处理方式是引入滞回控制也就是常说的施密特触发器思想。具体做法是设置两个不同的阈值点启动阈值和停止阈值。温度升到28℃时启动风扇降到26℃时才关闭风扇。中间这2℃的差值就是滞回区间。在这个区间内系统保持当前状态不变不会因为数字的微小波动而反复切换。#define TEMP_ON_THRESHOLD 28 #define TEMP_OFF_THRESHOLD 26 #define HUMIDITY_LOW_LIMIT 40 void Control_Temperature(float temp) { if(temp TEMP_ON_THRESHOLD) { SystemState.fan_status FAN_ON; // 打开风扇 } else if(temp TEMP_OFF_THRESHOLD) { SystemState.fan_status FAN_OFF; // 关闭风扇 } }湿度控制的逻辑类似但方向相反。湿度低于40%时启动加湿器模拟负载升到45%以上停止。这样设置的好处是系统状态稳定演示过程中执行机构不会出现抽搐式的频繁启停也让报告里“控制策略设计”这一章有东西可写。3.4 LCD1602显示驱动的移植要点LCD1602的驱动是这里面最“套路化”的部分网上代码一抓一大把但真正自己写的时候有几个细节容易翻车。第一是初始化时序配置为8位数据模式后要依次发送0x38、0x0C、0x06、0x01每条命令之间保留足够的延时后面才能正确显示第二是写数据和写命令的引脚状态切换必须准确RS引脚在写命令时拉低写数据时拉高顺序反了屏幕要么花屏要么显示空白。void LCD_Show_Data(float temp, float hum) { char buf[16]; LCD_WriteCmd(0x80); // 第一行起始地址 sprintf(buf, Temp:%.1f C, temp); LCD_WriteString(buf); LCD_WriteCmd(0xC0); // 第二行起始地址 sprintf(buf, Humi:%.1f %%, hum); LCD_WriteString(buf); }显示内容我做了格式化处理温度取一位小数湿度带百分比符号这样整体观感比较专业。DHT11读取失败时我会在LCD上显示“Sensor Error”而不是跳乱码这个小细节在演示视频里有意外的好处——它证明你的代码处理了异常情况而不是只会输出正常数据。4. 联调、跑通与问题排除的完整复盘4.1 Keil与Proteus的联调方式选择很多第一次做仿真的同学会纠结“Keil怎么和Proteus联调”。其实最稳定的方式根本不是实时联合调试而是分两步走先在Keil里编译生成HEX文件再把HEX文件加载到Proteus的STM32芯片里。具体操作流程是在Keil的Options for Target里勾选Create HEX File编译通过后在Output目录找到.hex文件然后在Proteus工程里双击STM32F103C8T6芯片在Program File一栏填入HEX文件路径点OK即可。点运行之后如果程序有串口输出可以放一个虚拟终端来观察完整的数据流。实时联合调试的配置也就是让Proteus作为Keil的调试器在原理上是可行的但配置过程涉及VSM调试接口、AGDI驱动等一堆麻烦事而且版本兼容性问题很多我建议时间有限的话不要碰这个功能。把HEX烧进去跑配合Proteus的断点和单步功能已经能满足绝大部分调试需求。4.2 仿真中我实打实踩过的三个坑第一个坑是DHT11温度永远显示0。排查了很久原因是GPIO模式配置错了。DHT11的数据线在读取数据时需要输入模式在主机发送起始信号时需要输出模式。如果全程配置为推挽输出读引脚电平永远读到异常状态。解决方法是把PA0配置为开漏模式开漏状态天然支持“输出低电平释放高电平”的双向切换。第二个坑是LCD1602有背光但无任何字符。这个问题的根源不是接线而是初始化时没有等待足够的时间。LCD1602上电后需要等待大约15到20毫秒让内部控制器完成复位如果立即发送配置命令命令会丢失。在每条命令之间加一个10毫秒级别的延时后问题立刻消失。第三个坑是风扇电机一转起来整个系统画面就卡顿。原因是把电机调速直接放在了主循环里用delay_ms来改变PWM占空比导致整个主循环的周期变得极其不稳定。解决办法是把风扇调速交给定时器PWM硬件完成主循环只负责根据阈值改变比较寄存器的值彻底把时序控制和业务逻辑分开。4.3 功能验证清单与实测效果系统跑通之后我按下面这张表逐项做了功能验证确认每一个环节都符合预期验证项目测试方法预期结果实测情况DHT11数据读取修改模型温湿度参数数值正确变化通过误差在1以内LCD显示观察屏幕刷新温湿度实时更新通过无花屏高温报警温度参数调至30℃蜂鸣器鸣叫通过1秒内响应风扇控制温度超过28℃电机启动通过PWM调速正常湿度控制湿度参数调至35%加湿负载启动通过回复过程参数调回正常范围执行机构停止通过滞回区间正常整个系统跑起来之后的状态是LCD第一行稳定显示当前温度第二行显示湿度温度正常时蜂鸣器不响、风扇不转把温度模拟参数调到29℃以上约1秒后风扇启动蜂鸣器以低频短鸣的方式报警把温度降回27℃以下风扇在滞回条件下关闭蜂鸣器停止。这一套完整的反应流程就是演示视频的核心内容。5. 一份能直接拿去交差的报告是怎么写出来的5.1 设计报告的结构设计与图文编排标题里特意提到“有报告”说明这已经是很多课程设计验收的标配要求。我写设计报告的时候发现很多人容易把报告写成“说明书”——贴一堆代码画一堆截图但老师真正想看的逻辑链条反而没有。我最终采用的报告结构是需求分析、系统总体设计、硬件电路设计、软件设计、仿真测试、总结与展望六个章节。需求分析这章不要写空话直接写“系统需要实现温湿度采集、LCD显示、超阈值风扇控制、超限蜂鸣器报警”几个具体功能即可。系统总体设计配一张模块框图把主控、传感器、显示、执行机构之间的关系画清楚。硬件电路设计提出DHT11为什么需要上拉电阻、PWM电机调速的原理软件设计按模块拆解重点讲DHT11时序和滞回控制的思路。报告里最需要注意的是图和代码必须经过裁剪。截图要裁剪到只保留与当前论述相关的区域代码片段只截核心函数注释写清楚代码全量附录放在最后。排版上建议用表格将硬件选型的规格参数整理出来这会让报告的专业度上一整个台阶。5.2 演示视频的节奏控制与录制技巧演示视频我控制在5分钟左右时长太短讲不清细节太长则考验观众的耐心。我总结下来比较理想的节奏是前30秒用一句话介绍系统功能然后依次展示Keil工程结构、Proteus电路图、整体运行效果、阈值触发过程最后用一个表格总结验证结果。录制的时候有个重要的技巧如果演示过程中需要修改DHT11参数来模拟环境变化记得先暂停再修改避免画面里出现鼠标在属性面板里乱翻的杂乱镜头。我用第一种方式修改参数时提前把属性面板位置固定好录制时点击修改后立刻恢复运行视频看起来就是系统自动感知了环境变化。另外在展示蜂鸣器报警时Proteus仿真的蜂鸣器音量比较小录制视频时如果直接用麦克风收音会很弱。我选择的做法是在视频里用屏幕字幕同步标注“蜂鸣器报警”的状态文字让观看者明确知道当前正处于报警状态而不是干巴巴地等那个几乎听不见的响声。写在最后这套仿真实战真正教会我的事做完整个项目之后我有一个很深的体会这类仿真控制系统的难点从来不是某个单独模块的实现而是所有模块在时间尺度上的配合。DHT11的时序以微秒为单位LCD的命令时序以毫秒为单位风扇的响应以秒为单位三层时间尺度同时存在于一套系统里这对编程时的心智模型是个不小的考验。如果后续还想继续扩展我个人觉得有三个方向值得尝试一是把开关量控制升级为PID闭环控制让风扇根据温度偏差连续调节转速二是加入ESP8266模组模拟远程数据传输把数据上报到上位机软件这样就从单机采集系统进化成了物联网终端三是把Proteus仿真出来的代码逻辑移植到真机上实际感受仿真与真实的差异那将是这个项目最完整的一次闭环。另外分享一个制作上的小技巧在这个项目里花十几块钱买一块真实的STM32最小系统板和DHT11模块用相同代码在真机上跑一遍和Proteus截图放一起对照这份报告的说服力会明显高于纯仿真内容。无论你是为了完成课程设计还是在学习嵌入式开发Proteus加Keil这套组合都是理解单片机系统最直接的路径之一。它能让你把注意力全部集中在代码逻辑和系统设计上而不是跟硬件故障纠缠。希望这篇复盘能帮你把属于自己的温湿度控制系统顺利跑通。