简介:这是一份基于STM32与Proteus联合仿真的温湿度控制系统设计资料,适合嵌入式初学者、电子相关专业学生用于课程设计或毕业设计参考。系统以STM32为控制核心,完成温湿度采集、按键阈值设置、LCD1602显示、风扇启停及继电器模拟加热控制,覆盖教学中的典型测控场景。压缩包共91个文件,含37个C源文件与34个头文件,另含Proteus仿真工程、PNG预览图及简要说明文档,整体仅1.1MB,便于快速下载查看。目前已有268人学习。代码目录按CORE、SYSTEM、USER、HARDWARE等模块组织,清晰展示初始化、驱动与主逻辑;读者可结合仿真图直接运行调试验证,按需要修改温湿度门限值或调整风扇、继电器控制策略,从而加深对STM32外设编程与Proteus仿真流程的理解。仿真图与源码相互对应,适合边看边改边验证,也可在此基础上扩展为更完整的智能环境监控项目。
1. 从温湿度控制到stm32+Proteus:为什么这种方案适合快速验证
很多人一拿到“基于stm32单片机Protues仿真的温湿度控制系统”这类资源,第一反应是“又是个课设”。但把它拆开看,这个组合其实踩中了嵌入式开发里最实用的一条路径:stm32提供真实的外设接口与处理能力,Proteus解决硬件不可得时的验证问题,而温湿度控制本身就是一套完整的“采集—计算—执行”闭环。对于正在做课程设计、毕业设计,或者想快速验证一个控制逻辑的工程师来说,这套资源的价值不是“能跑”,而是它逼你把传感器时序、按键消抖、LCD驱动、继电器控制这些真实工程问题全部过一遍。而且Proteus仿真和实物之间最大的差异在电气特性,不在逻辑——你在这里调通的代码,换到真实硬件上只需要改引脚映射和延时参数。适合的人群很明确:刚接触stm32但不想焊板子的学生,以及需要快速验证控温逻辑的开发者。
2. 系统架构与关键电路设计:温湿度控制“采集—比较—执行”链路
2.1 温湿度控制系统的核心组成与工作流程
先理清这套系统的整体脉络。以stm32F103为核心,DHT11负责采集温湿度,LCD1602实时显示,独立按键用于设置门限值,而执行端分为两路:一路直接驱动风扇进行降温除湿,另一路通过继电器控制电机转动来模拟加热。工作流程很直白:stm32周期性读取DHT11的数据,把当前温度和湿度与用户设置的上限/下限比较,然后决定风扇和继电器动作。
控制逻辑不复杂,但有一个容易忽略的点:继电器与电机不能直接挂在GPIO上,因为stm32的GPIO驱动能力有限,而Proteus仿真中如果不加驱动电路,逻辑上能“通”但现实上不可行。所以仿真图里通常会用PNP/NPN三极管或ULN2003来放大电流。这个细节决定了你的仿真是否符合真实硬件行为。
2.2 STM32温湿度控制中的传感器、显示与执行器件选型
器件选型是这套设计的“地基”,我以课设里最常见的组合为例,把每个器件的职责列清楚。下面这张表可以直接用于器件清单或Proteus元件检索:
| 器件 | 型号/Proteus关键词 | 职责 | 关键参数 |
|---|---|---|---|
| 主控 | STM32F103C8T6(或R6) | 数据处理、逻辑控制 | 72MHz主频,GPIO复用 |
| 温湿度传感器 | DHT11(Proteus里用HS1101+湿度比较器模拟,或DHT11库模型) | 采集温度和相对湿度 | 单总线,40位数据,20%~90%RH,0~50℃ |
| 显示 | LM016L(LCD1602模型) | 显示当前值/门限值 | 4位或8位并行,I2C扩展可另加 |
| 风扇 | MOTOR(直流电机模型) | 降温/排湿 | 5V/12V,PWM可调速 |
| 模拟加热 | RELAY + MOTOR | 继电器吸合后电机转动模拟加热 | 继电器线圈5V,触点驱动电机 |
| 按键 | BUTTON x3或x4 | 设置门限、切换模式、加减数值 | 机械消抖,10ms扫描 |
选型时注意:DHT11是单总线协议,数据脚需要外接上拉电阻(4.7kΩ左右),Proteus仿真模型如果自带内部上拉,可以省略,但实物必须加。LCD1602用LM016L模型时,对比度引脚(V0)需要接一个可调电阻,否则可能有显示不全或乱码问题。
2.3 门限控制与硬件接口关系
门限控制的核心在于把用户按键输入的设定值与传感器实际值进行比较。这里设计成“上限+下限”还是“单门限+回差”,决定了代码的复杂度。
如果按“温度高于上限则开风扇,低于下限则关风扇,中间保持”的方式,就需要三态判断。湿度同理。这样做的好处是避免继电器在门限附近频繁吸合——这是初学者最容易忽略的:不加回差,风扇会反复启动,继电器寿命骤减。实际工程里叫“滞回比较”,代码上就是两个阈值。
接口映射建议如下表(以常见例程引脚为参考,实际以工程头文件为准):
| 功能 | 引脚 | 连接目标 |
|---|---|---|
| DHT11数据 | PA0 | 上拉电阻 + 传感器 |
| LCD RS | PB11 | LM016L的RS |
| LCD EN | PB10 | LM016L的E |
| LCD D4~D7 | PB12~PB15 | LM016L的数据线 |
| 风扇控制 | PA1(高电平触发PWM或开关) | 三极管基极 |
| 继电器控制 | PA2(高电平吸合) | 三极管基极 + 继电器线圈 |
| 按键1/2/3 | PA3/PA4/PA5 | 接GND,按下为低电平 |
3. STM32源代码实现:从底层驱动到控制逻辑
3.1 工程结构与USMART调试组件的意义
拿到源代码,先看工程目录。这个资源里的文件夹层级是典型的正点原子风格:CORE存放启动文件与内核接口,SYSTEM包含delay/sys/usart这三个基础模块,HARDWARE放各个外设驱动(比如dht11.c、lcd1602.c、key.c),USER里是main.c。另外还有USMART,这是一个串口调试组件,可以在上运行后,通过串口调用函数、修改参数,对嵌入式开发非常实用。在Proteus仿真时,它同样是观察内部变量的好帮手——你甚至不需要仿真器,只要把串口重定向到Proteus的Virtual Terminal,就能用USMART的help命令直接调用DHT11_Read_Temp()这类函数。
理解工程结构的意义在于:仿真时你只需要把USER目录下的main.c编译成hex,而HARDWARE里的驱动模块可以根据自己的硬件改动。不要试图修改SYSTEM里的系统级代码,那些通常不需要动。
3.2 DHT11温湿度读取代码与时序分析
DHT11的驱动是整个系统里唯一需要严格控制时序的部分。stm32通过单总线与DHT11通信,数据格式是:8位湿度整数+8位湿度小数+8位温度整数+8位温度小数+8位校验和。我直接给出在stm32库函数下的读取代码框架,这是串口调试时可以反复验证的核心。
// dht11.c 的部分核心代码 u8 DHT11_Read_Data(u8 *temp, u8 *humi) { u8 buf[5]; u8 i; DHT11_RST(); // 主机发送起始信号:拉低至少18ms if(DHT11_Check() == 0) // 等待从机响应:拉低80us再拉高80us { for(i = 0; i < 5; i++) // 连续读取5个字节 { buf[i] = DHT11_Read_Byte(); } if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) // 校验和 { *humi = buf[0]; // 湿度整数部分 *temp = buf[2]; // 温度整数部分 return 0; } } return 1; }这段代码的关键在于DHT11_RST()和DHT11_Read_Byte()的位时序。只要有一处延时偏差超过几十微秒,读取就会失败。常见做法是使用delay_us()函数,而delay_us()的精度依赖系统时钟配置。Proteus仿真中如果时钟设置错误,DHT11时序会完全对不上。调试时不要直接看最终值,先用逻辑分析仪或者Proteus的波形观察数据脚的电平翻转频率,确认起始信号、响应信号、每一位的高电平长度是否符合:50us的低电平代表“0”,70us左右的高电平后跟短暂低电平代表“1”。
3.3 按键扫描与门限设置逻辑
门限设置必须处理消抖和连击。Proteus中的按键模型如果不处理抖动,也会出现一次按下却多次触发的问题。代码里通常用状态机:每10ms扫描一次,只有检测到按键从高电平变为低电平,且连续两次扫描均为此状态,才认为按键有效。下面是按键扫描的典型实现:
u8 KEY_Scan(u8 mode) { static u8 key_up = 1; if(mode) key_up = 1; // 支持连按 if(key_up && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0)) { delay_ms(10); // 软件消抖 key_up = 0; if(KEY1 == 0) return KEY1_PRESS; else if(KEY2 == 0) return KEY2_PRESS; else if(KEY3 == 0) return KEY3_PRESS; } else if(KEY1 == 1 && KEY2 == 1 && KEY3 == 1) { key_up = 1; } return KEY_UNPRESS; }实际使用中,KEY1用来进入门限设置模式,KEY2增加数值,KEY3减小数值,长按还能加速。代码里的mode参数控制是否支持连续触发——设置门限时需要连续加减,所以mode传1;控制模式下需要单次触发,mode传0。判断逻辑放在主循环的if语句里,当模式标志位为“设置温度上限”时,按键只调整温度上限变量,并且把设置值实时刷新到LCD上,同时用EEPROM(如果没有,就用延时+掉电保存到flash)临时保存,防止掉电丢失。
3.4 LCD1602显示与继电器、风扇控制
LCD1602的驱动是纯逻辑操作,难点在初始化时序。4位模式下,需要先发送03H三次,再发送02H切换为4位模式,再初始化显示配置。很多仿真失败的原因是把LCD的RS和EN接反了,或者初始化时没有等足够的时间。用keil调试时,可以在LCD_Init()的每一步delay_ms(5),仿真里可以看到屏幕逐行点亮的过程。
至于继电器和风扇的控制,我用GPIO写高低电平,核心逻辑是封装一个函数:
void Fan_Relay_Control(u8 temp, u8 humi, u8 tempHigh, u8 tempLow, u8 humiHigh, u8 humiLow) { if(temp > tempHigh) // 温度超上限 { FAN_ON(); // 打开风扇 RELAY_OFF(); // 关闭加热 } else if(temp < tempLow) // 温度低于下限 { FAN_OFF(); RELAY_ON(); // 吸合继电器,模拟加热 } else // 在定义区间内 { if(humi > humiHigh) FAN_ON(); // 湿度高则只开风扇 else if(humi < humiLow) FAN_OFF(); } }注意:这个逻辑里,温度和湿度之间是联动关系。当温度正常但湿度超标,只开风扇;当温度过低,无论湿度如何,都会启动加热——因为低温本身就是触发条件。实际项目里你可能会把湿度控制和温度控制分成两个独立线程,但在这个仿真级别,用主循环轮询就够了,因为DHT11采集一次需要20ms左右,轮询周期控制在50ms内对继电器没有任何压力。
4. Proteus仿真搭建与联调:从加载固件到排坑
4.1 创建Proteus工程与元件选型
打开Proteus后,新建工程,不要选择默认的Atmega,在原理图编辑界面用“P”键添加元件。需要添加的元件清单如下,注意名称要和Proteus库中的一致,否则搜索不到:
| 元件名称 | 搜索关键词 | 数量 |
|---|---|---|
| STM32F103C8 | STM32F103C8 | 1 |
| LM016L | LM016L | 1 |
| DHT11 | DHT11(如果库里有) | 1 |
| 电位器 | POT-HG | 1 |
| 电阻 | RES | 若干 |
| 三极管 | NPN(如2N2222) | 2 |
| 继电器 | RELAY | 1 |
| 电机 | MOTOR | 2 |
| 按键 | BUTTON | 3 |
| 电源 | POWER | 若干 |
如果你用的Proteus版本里没有DHT11模型,常见替代方案是使用“HS1101 + 比较器 + 可调电阻”来模拟湿度变化,或者用“DS1621 + 电位器”模拟温度。但要注意:替代模型的数据格式不是单总线,所以stm32的DHT11驱动在仿真中不一定能跑通。解决方法是:在Proteus工具栏选择“Debug > Digital Probe”等方式强制干预数据线,或者直接把DHT11读到的数据改成变量观察,在仿真中用虚拟串口发送模拟值来验证逻辑。这是很多初学者卡住的地方:不要执念于仿真里一定要有实物传感器,Proteus的SPI/I2C模型可以验证,单总线模型则容易出问题。我一般会先在代码里写一个宏定义,比如#define DHT11_SIMULATION,在仿真时让函数直接返回内部变量,便于调试控制逻辑。
4.2 stm32固件加载与时钟配置
在Proteus中双击STM32F103C8芯片,进入属性对话框,在Program File那一项选择你用Keil编译生成的hex文件。注意:如果编译后没有生成hex,是因为Keil中没有勾选“Create HEX File”。这是最常见的错误。
另一个关键点是时钟。Proteus的stm32模型不像真实芯片那样有内置RC振荡器,你必须给OSC1和OSC2引脚接8MHz晶振,并接两个22pF电容。同时在stm32的配置界面里,把External Oscillator设为8000000,否则SystemInit()函数会按错误的外部时钟来源校准,delay延时就会偏差3倍以上。这个错误会导致DHT11时序完全错乱,LCD初始化也可能失败。
4.3 仿真调试技巧:变量监视、串口模拟、常见问题
Proteus提供了很好的调试能力,但需要用对技巧。首先,在Debug菜单下可以添加新仪器,比如Voltmeter、Logic Probe、Virtual Terminal。Virtual Terminal连接到stm32的USART1引脚(PA9 TX、PA10 RX),波特率设置与代码一致(比如115200),就能在仿真里看到printf输出。这比看LCD更高效,因为LCD刷新有延迟,而串口是逐字符即时可见的。
如果代码里用了printf,记得在stm32工程中重定向fputc函数到USART1:
int fputc(int ch, FILE *f) { while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); USART_SendData(USART1, (u8)ch); return ch; }这样你就能在代码里直接printf("temp:%d,humi:%d\n", temp, humi)。结合Virtual Terminal,可以观察温度变化时风扇和继电器的动作是否跟预期一致。
常见的Proteus仿真问题还有几个:
- 电机不转:检查电机是否定义了“MOTOR”属性,以及继电器触点是否接在电机的电源回路中。在Proteus里,继电器的COM端接电源正极,NO端接电机正极,电机负极接地。
- 继电器一直吸合:原因是三极管基极电流不够或者引脚配置错误。把引脚直接拉高时,需要在下拉电阻保证静态为低。
- LCD显示乱码:检查LCD的片选/读写使能时序,或者把LCD的RW引脚直接接地,只做写操作。
下面这个表是我在做仿真时总结的排查方向:
| 现象 | 可能原因 | 检查方法 |
|---|---|---|
| 程序未运行 | hex未加载/芯片未供电 | 检查芯片属性加载,确认电源网络 |
| 每次复位都卡死 | 晶振未设置/外部晶振参数不对 | 示波器看OSC引脚波形 |
| DHT11数据始终为0 | 单总线模型不支持/上拉缺失 | 用Debug > Direct Logic等强制拉高 |
| 按键失灵 | 端口配置为复用功能 | 检查GPIO是否设为输入模式 |
| 继电器抖动 | 程序未加滞回 | 查看继电器端电压是否有毛刺 |
5. 进阶玩法:从门限控制到PID调节与串口可视化
前面实现的是双门限滞回控制,这种控制方式在目标温度附近会有波动,而且无法精确维持某个温度。更进一层的做法是把输出从继电器改成PWM,用PID算法调节PWM的占空比。在Proteus仿真中,风扇和加热电机都可以用PWM驱动,stm32的TIM3输出两路PWM刚好覆盖这两个执行器。但PID需要连续的温度反馈,DHT11的采样周期是1秒左右,如果直接用DHT11做PID,控制周期太长,效果并不好。常见替代方案是:在仿真中用DS18B20替代DHT11的温度部分(或直接用一个电位器模拟温度传感器电压),然后用ADC采集电压转换成温度值,这样控制周期可以缩短到10ms,PID效果就容易体现。
下面是基于位置式PID的简化代码,控制输出是PWM占空比:
// pid.c 位置式PID核心 typedef struct { float Kp, Ki, Kd; float e0, e1; // 当前偏差和上一次偏差 float integral; // 积分累计 } PID_TypeDef; float PID_Calc(PID_TypeDef *pid, float target, float actual) { float e = target - actual; float output; pid->integral += e; output = pid->Kp * e + pid->Ki * pid->integral + pid->Kd * (e - pid->e1); pid->e1 = e; if(output > 100) output = 100; if(output < 0) output = 0; return output; }调用时,把PID函数的返回值强制转换为PWM比较值。假如ARR设置为1000,那么TIM_SetCompare3(TIM3, (u16)(output*10.0f))。这个设计和门限控制的最大区别是:门限控制看的是“是否越界”,PID控制看的是“偏离了多少”。在Proteus中,可以通过修改电位器来模拟温度缓慢变化,然后用速度计或逻辑示波器观察PWM脉宽的变化过程。你会发现,当设定温度远离当前温度时,占空比接近100%,随着温度靠近,占空比逐渐减小,最终稳定在一个平衡值。这个平衡值就是热系统的耗散功率。
为了更直观地观察温湿度变化曲线,可以用Virtual Terminal配合串口绘图工具。在代码里每隔100ms输出一帧格式化数据,格式用CSV形式:
printf("temp=%.1f,pwm=%.1f\n", temp, output);然后在PC端打开串口助手,把波特率调到一致,勾选“显示时间戳”,就能用串口助手的画图插件生成时间-温度曲线。如果用的是Proteus的COMPIM组件(虚拟串口),可以配合现成的串口转波形工具,实现一个简易的“温控曲线记录仪”。这种方式虽然不如MATLAB来得美观,但胜在完全基于当前仿真环境,不需要额外的数据采集硬件。对于课程设计答辩,这已经能很好地展示你的分析深度了。
如果你还想继续把系统做完整,可以考虑加入掉电保存功能,把手动设置的门限值写入stm32内部Flash的最后一个扇区,复位后自动加载。也可以把LCD显示改成易用的菜单结构:短按按键进入设置,长按保存退出。这些改动都不影响核心控制逻辑,但能让你的项目从“课设”级别上升到“小型产品”级别——尤其是把PID参数做成串口可调时,USMART组件的价值就出来了。在运行USMART后,你可以在Virtual Terminal里输入help看到所有注册函数,直接调用PID_Set_Kp(2.0)实时修改参数,而不用重新编译。这就是那些年调PID最舒服的方式。
本文还有配套的精品资源,点击获取