基于STM32与DHT11的温湿度闭环控制系统仿真与实现

📅 2026/8/7 4:31:52
基于STM32与DHT11的温湿度闭环控制系统仿真与实现
1. 项目缘起从“能测”到“能控”的跨越做嵌入式开发的朋友尤其是玩STM32的对DHT11这个温湿度传感器肯定不陌生。它便宜、接口简单几乎是所有温湿度相关项目的入门首选。但很多时候我们的项目就止步于“读取数据并显示”了顶多再加个串口打印或者OLED屏显示。这当然没问题但总觉得缺了点什么——数据是死的系统是静态的。我们能不能更进一步让这个系统“活”起来根据读取到的温湿度数据去自动控制一些设备比如风扇、加热器、加湿器实现一个闭环的自动调节系统呢这就是“基于STM32的DHT11温湿度控制系统”的核心价值所在。它不再是一个简单的数据采集器而是一个具备决策和执行能力的微型控制器。而“仿真设计”这个后缀则为我们提供了一个低成本、高效率、零风险的验证途径。在真正焊接电路、购买执行器件之前我们可以在电脑上完整地模拟整个系统的运行逻辑、控制算法甚至观察在极端温湿度条件下的系统响应。这对于学生完成课程设计、工程师验证方案可行性或者爱好者学习闭环控制思想都有着不可替代的意义。我最近就接手了一个类似的需求需要为一个小型恒温恒湿箱设计控制核心。硬件成本要低逻辑要可靠还得能快速验证方案。STM32F103C8T6也就是常说的“蓝桥杯”或“最小系统板”核心和DHT11的组合自然成为了首选。但直接上硬件调试一旦逻辑有bug或者参数没调好反复烧录、测试不仅效率低还可能损坏传感器或执行机构。所以我决定先走仿真这条路把核心的控制逻辑跑通、跑稳。下面我就把这个从零开始的仿真设计过程包括踩过的坑和总结的经验完整地分享出来。2. 系统架构与仿真环境搭建在动手写代码之前我们必须先想清楚整个系统要做什么以及如何在仿真环境中构建它。一个完整的温湿度控制系统通常包含感知、决策、执行三个核心环节。感知层就是我们的DHT11传感器负责采集环境的温度和湿度数据。DHT11采用单总线协议虽然比I2C、SPI简单但时序要求非常严格这也是仿真和实际硬件中都需要重点攻克的部分。决策层是STM32单片机它是系统的大脑。它需要完成几件事1. 按照严格的时序驱动DHT11读取数据并校验2. 将读取到的原始数据转换为实际的温湿度值3. 根据预设的目标温湿度范围比如温度25±2°C湿度50%±5%运行控制算法比如简单的开关量PID或更直观的阈值比较计算出控制指令4. 将控制指令输出给执行层。执行层则是受控的设备例如用LED模拟的加热器温度低时点亮、另一个LED模拟的加湿器湿度低时点亮、一个LED模拟的风扇温度高或湿度高时点亮。在仿真中我们可以用虚拟的LED来直观展示控制效果。那么用什么来仿真呢对于STM32项目Proteus是经典且强大的选择。它能仿真STM32F103的CPU行为、外设GPIO、定时器、USART等并且有丰富的虚拟仪器如虚拟终端、示波器和元器件模型包括LED、电机等。虽然Proteus的元件库没有直接的DHT11模型但我们可以用一个“技巧”来模拟它——使用可编程的“脚本模型”或者用另一个微控制器来模拟DHT11的响应行为。不过对于学习和验证核心控制逻辑来说我们有一个更简单的思路在仿真中我们暂时不纠结于严格模拟DHT11的单总线时序细节而是专注于STM32的控制逻辑。我们可以让STM32程序“认为”自己已经成功读取到了数据这些数据可以由我们手动设定或通过一个简单的模拟程序来产生。这样我们就能把全部精力放在“如何根据这些数据做出控制决策”这个核心问题上。仿真环境搭建步骤如下安装Proteus 8 Professional或更高版本确保安装时包含了STM32F103C8T6的模型库VSM Simulation Model。安装Keil MDK-ARM这是STM32的官方开发环境之一用于编写、编译和调试C代码。我们需要将Keil编译生成的.axf或.hex文件加载到Proteus的STM32芯片中。新建Proteus工程选择“Schematic Capture”创建一个新的原理图。放置核心元器件在元件库中搜索“STM32F103C8”将其放置到图纸中。放置必要的无源元件一个10K的上拉电阻连接在模拟DHT11数据线的IO口上这是单总线协议的典型要求几个220欧姆的限流电阻和LED用于指示控制输出。放置电源POWER和地GROUND符号。绘制原理图将STM32的VDD接电源3.3VVSS接地。将用于模拟DHT11数据线的GPIO口例如PA0通过上拉电阻接到电源。将用于控制加热、加湿、风扇的GPIO口例如PA1、PA2、PA3分别通过限流电阻连接到LED的正极LED的负极接地。配置STM32芯片双击原理图中的STM32芯片在弹出的属性窗口中设置Program File为我们后续由Keil生成的.hex文件路径。在Crystal Frequency中设置晶振频率通常为8MHz外部或使用内部RC振荡器。这一步可以等代码编译完成后再来指定。至此我们的仿真硬件平台就搭建好了。接下来重心转移到软件——STM32的控制程序。3. STM32程序设计与控制逻辑实现我们的程序将采用裸机开发不使用RTOS以保持简洁和易于理解。程序的核心是一个超级循环while(1)在其中周期性地执行“读取传感器、处理数据、执行控制”这一流程。这里我们分步拆解。3.1 模拟DHT11数据读取由于在仿真中我们暂时不实现真实的单总线通信我们需要一个数据来源。一个简单有效的方法是在程序中定义一个数组或变量手动赋予它温湿度值用于模拟传感器读数。为了更贴近真实场景我们可以让这些模拟数据在一定范围内随机波动。// 模拟传感器数据 typedef struct { uint8_t temp_int; // 温度整数部分 uint8_t temp_deci; // 温度小数部分 uint8_t humi_int; // 湿度整数部分 uint8_t humi_deci; // 湿度小数部分 uint8_t checksum; // 校验和 } DHT11_Data_t; // 模拟一次DHT11数据读取过程 void DHT11_Simulate_Read(DHT11_Data_t *data) { // 在实际项目中这里应包含复杂的单总线时序代码 // 仿真中我们生成随机数据作为模拟 // 例如温度在20-30度之间湿度在40-70%之间随机 >// 控制目标与阈值定义 #define TARGET_TEMP_LOW 23.0f #define TARGET_TEMP_HIGH 27.0f #define TARGET_HUMI_LOW 45.0f #define TARGET_HUMI_HIGH 55.0f // 控制状态 typedef enum { CTRL_OFF 0, CTRL_ON 1 } CtrlState_t; CtrlState_t heater_state CTRL_OFF; // 加热器 CtrlState_t cooler_state CTRL_OFF; // 风扇降温 CtrlState_t humidifier_state CTRL_OFF; // 加湿器 CtrlState_t dehumidifier_state CTRL_OFF; // 除湿器本例用风扇兼作通风除湿 void Control_Logic(float current_temp, float current_humi) { // 温度控制低于目标下限加热高于目标上限降温 if (current_temp TARGET_TEMP_LOW) { heater_state CTRL_ON; cooler_state CTRL_OFF; // 加热时关闭降温 } else if (current_temp TARGET_TEMP_HIGH) { heater_state CTRL_OFF; // 降温时关闭加热 cooler_state CTRL_ON; } else { // 在目标区间内保持原状态或关闭根据需求 // 这里选择关闭实现简单的开关控制 heater_state CTRL_OFF; cooler_state CTRL_OFF; } // 湿度控制低于目标下限加湿高于目标上限除湿通风 if (current_humi TARGET_HUMI_LOW) { humidifier_state CTRL_ON; // 加湿时如果温度不高可以不开风扇如果温度也高则风扇可能被温度控制逻辑打开 } else if (current_humi TARGET_HUMI_HIGH) { humidifier_state CTRL_OFF; // 湿度高时强制开启风扇进行通风除湿如果温度控制没开风扇的话 // 这里赋予湿度控制优先权也可以设计更复杂的仲裁逻辑 if (cooler_state CTRL_OFF) { // 仅当温度控制没要求开风扇时湿度控制才独立开启风扇除湿 // 在实际中可能用一个标志位区分风扇是因温度还是湿度开启 cooler_state CTRL_ON; // 此时风扇作为除湿器 } } else { humidifier_state CTRL_OFF; // 湿度在区间内时不因湿度原因开启风扇但风扇可能因温度原因保持开启 } // 更新GPIO输出状态 HAL_GPIO_WritePin(HEATER_GPIO_Port, HEATER_Pin, (heater_state CTRL_ON) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(COOLER_GPIO_Port, COOLER_Pin, (cooler_state CTRL_ON) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(HUMIDIFIER_GPIO_Port, HUMIDIFIER_Pin, (humidifier_state CTRL_ON) ? GPIO_PIN_SET : GPIO_PIN_RESET); }为什么选择双阈值而不是单点比较如果只设定一个目标值如25°C那么当温度在24.9°C时加热器打开升到25.1°C时加热器关闭然后温度下降至24.9°C又打开……如此循环执行机构加热器会在目标值附近频繁启停这被称为“继电器抖动”现象会严重缩短设备寿命。设定一个合理的上下限如23°C-27°C就形成了一个“死区”只有当温度超出这个区间时设备才动作进入区间则保持大大降低了动作频率。3.3 主循环与时间调度在main函数的超级循环中我们需要合理安排任务的执行周期。读取DHT11不宜过于频繁因为其手册建议两次读取间隔至少2秒。控制逻辑计算可以快一些但也没必要快到微秒级。int main(void) { // HAL库初始化、时钟配置、GPIO初始化等 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化随机种子可以用定时器计数器值 srand(HAL_GetTick()); DHT11_Data_t sensor_data; float current_temp, current_humi; uint32_t last_read_time 0; const uint32_t READ_INTERVAL 2000; // 读取间隔2秒 while (1) { uint32_t current_tick HAL_GetTick(); // 任务1定时读取传感器模拟 if (current_tick - last_read_time READ_INTERVAL) { DHT11_Simulate_Read(sensor_data); // 数据转换DHT11数据格式湿度整数.小数温度整数.小数 current_humi sensor_data.humi_int sensor_data.humi_deci * 0.1; current_temp sensor_data.temp_int sensor_data.temp_deci * 0.1; // 这里可以添加校验和检查模拟 // if ((sensor_data.humi_int sensor_data.humi_deci sensor_data.temp_int sensor_data.temp_deci) ! sensor_data.checksum) { /* 处理错误 */ } last_read_time current_tick; // 任务2执行控制逻辑每次读取后立即执行 Control_Logic(current_temp, current_humi); } // 任务3其他后台任务如状态指示、通信等如果有 // 例如可以让一个LED以1Hz闪烁指示系统运行正常 // ... // 任务4空闲时进入低功耗模式如果需要 // __WFI(); } }这个程序框架清晰地将数据采集模拟、数据处理和控制输出解耦。在Proteus仿真中我们可以通过观察连接到PA1、PA2、PA3的LED的亮灭来直观判断系统是否根据我们模拟的温湿度数据做出了正确的控制决策。4. Proteus仿真调试与可视化验证代码编写并编译生成.hex文件后就可以回到Proteus进行联调了。这是仿真设计中最关键也最有成就感的一步。加载程序双击原理图中的STM32芯片在Program File属性栏中浏览并选择Keil生成的.hex文件。添加调试工具为了更直观地观察系统内部状态和温湿度变化曲线我们可以添加Proteus的虚拟仪器。虚拟终端 (Virtual Terminal)将其RX端连接到STM32的某个USART_TX引脚如PA9在代码中配置串口并打印当前的温湿度值及控制状态。这样我们就能在滚动窗口中看到系统的实时运行日志。模拟图表 (Analog Graph)添加两个模拟分析图表比如ANALOGUE。一个用于绘制温度曲线另一个用于绘制湿度曲线。我们需要用电压来模拟物理量。可以在电路中添加两个“电压探针 (Voltage Probe)”分别将其电压值通过一个简单的运算放大器电路或直接在代码中映射与温湿度值关联起来。更简单的方法是在代码中控制两个GPIO输出PWM波其占空比与温湿度值成正比然后在Proteus中用电压探针测量该GPIO的平均电压需使用GRAPH中的DIGITAL分析即可在图表中看到变化曲线。运行与观察点击Proteus左下角的运行按钮。系统开始仿真。你会看到虚拟终端开始输出信息“Temp: 25.0C, Humi: 50%, Heater: OFF, Fan: OFF, Humidifier: OFF”。由于我们用了随机数模拟数据输出的温湿度值会不断变化。当模拟温度低于23°C时HEATERLED应该点亮当模拟温度高于27°C时FANLED应该点亮。湿度控制逻辑同理。如果你添加了图表可以看到温湿度曲线在目标区间上下波动同时控制LED的状态在曲线超出阈值时发生跳变。边界条件测试这是仿真最大的优势。我们可以手动修改DHT11_Simulate_Read函数中的模拟数据制造各种极端情况来测试系统的鲁棒性。测试1将模拟温度固定设为15°C湿度设为30%。观察系统是否持续加热和加湿。测试2将模拟温度固定设为35°C湿度设为80%。观察系统是否持续开启风扇降温兼除湿并关闭加热和加湿。测试3模拟传感器通信失败例如在模拟函数中返回一个错误的校验和。观察程序是否有相应的错误处理机制比如点亮一个错误指示灯或保持上一次的有效控制状态。测试4快速变化数据测试控制逻辑的响应速度观察是否有不必要的频繁开关抖动。通过以上可视化的仿真调试我们无需任何实体硬件就完成了控制逻辑的验证、算法参数的初步整定如目标阈值和异常情况的处理测试。这能节省大量的开发时间和物料成本。5. 从仿真到实物的关键衔接与避坑指南仿真跑通了不代表实物就一定成功。仿真环境是理想的忽略了诸多现实世界的“噪声”。接下来我们要把仿真中验证过的逻辑移植到真实的STM32和DHT11上这个过程有几个必须注意的关键点。5.1 DHT11单总线时序的精确实现这是从仿真到实物遇到的第一个也是最大的挑战。DHT11的通信协议对微秒级延时非常敏感。在仿真中我们可以忽略但在实物上必须用代码精确实现。核心时序要点主机启动信号拉低数据线至少18ms然后拉高20-40us等待DHT11响应。DHT11响应DHT11会拉低80us再拉高80us然后开始输出数据。数据位每一位都以50us的低电平起始随后的高电平长度决定数据是026-28us还是170us。避坑经验禁用中断在读取整个40位数据期间必须关闭全局中断否则任何中断都可能破坏微秒级延时导致读取失败。读取完成后立即恢复中断。使用硬件定时器不要用HAL_Delay或简单的for循环做微秒延时极不准确。推荐使用STM32的硬件定时器如TIM2的计数器来获取精确的微秒级时间戳通过计算时间差来判断电平宽度。超时处理在等待DHT11响应或数据位时一定要加入超时判断例如等待超过200us还没收到预期电平就认为失败否则程序可能死等。上拉电阻数据线必须接一个4.7K~10K的上拉电阻到VCC否则DHT11无法输出稳定的高电平。// 示例使用SysTick或定时器实现微秒延时需根据系统时钟配置 void DHT11_Delay_us(uint16_t us) { uint32_t ticks us * (SystemCoreClock / 1000000); uint32_t start_tick SysTick-VAL; while ((start_tick - SysTick-VAL) ticks); } // 读取一位数据的函数简化示例实际需用定时器捕获 uint8_t DHT11_Read_Bit(void) { while(DHT11_IO_READ() 0); // 等待50us低电平起始位结束 DHT11_Delay_us(40); // 延时40us后采样 if(DHT11_IO_READ() 1) return 1; else return 0; }5.2 执行机构的驱动与隔离仿真中用LED实物中可能是继电器、固态继电器(SSR)、MOS管或电机驱动芯片。关键点电流驱动能力STM32的GPIO引脚最大输出电流通常为20-25mA不足以驱动继电器线圈通常需要50mA以上或大功率风扇。必须使用三极管如S8050或MOS管如SI2302进行电流放大。电气隔离控制交流负载如220V加热器或大功率直流负载时强烈建议使用光耦或继电器进行电气隔离防止负载侧的干扰或高压窜入单片机导致系统损坏。续流二极管如果驱动的是继电器、电磁阀等感性负载必须在负载线圈两端反向并联一个续流二极管如1N4148以吸收线圈断电时产生的反向感应电动势保护驱动三极管或MOS管。5.3 系统稳定性与抗干扰设计实物环境充满电磁干扰电源也可能波动。电源滤波在STM32的VDD和VSS引脚附近务必放置一个0.1uF和一个10uF的电容进行高频和低频滤波。如果使用电机等大功率负载最好为单片机和控制电路使用独立的LDO电源或加强滤波。传感器布线DHT11不宜离单片机过远建议1米内。如果必须延长数据线应采用屏蔽线或双绞线并适当降低上拉电阻的阻值如用4.7K。软件看门狗在main循环中定期喂狗IWDG或WWDG防止程序跑飞。特别是在控制逻辑复杂或环境干扰强时这是保证系统长期稳定运行的必备措施。数据滤波实物传感器读数可能会有偶发的毛刺。可以在软件中对连续几次的读取结果进行中值滤波或滑动平均滤波再将滤波后的值送入控制逻辑能有效避免单次误读导致控制误动作。// 简单的滑动平均滤波示例 #define FILTER_LEN 5 float temp_buffer[FILTER_LEN] {0}; uint8_t buffer_index 0; float Filter_Temperature(float new_temp) { temp_buffer[buffer_index] new_temp; buffer_index (buffer_index 1) % FILTER_LEN; float sum 0; for(int i0; iFILTER_LEN; i) { sum temp_buffer[i]; } return sum / FILTER_LEN; }5.4 调试与监控实物系统需要“眼睛”。除了控制输出建议至少保留一个LED作为系统状态指示灯比如1Hz闪烁表示运行正常并充分利用串口打印调试信息。可以将温湿度数据、控制状态、错误代码等定期发送到电脑使用串口助手如Putty、SecureCRT或自己编写的上位机软件进行监控。这是定位实物问题最直接的手段。从仿真到实物是一个将理想模型适配复杂现实的过程。把上述这些细节处理好你的温湿度控制系统才能真正可靠地工作起来。仿真阶段验证了逻辑的正确性而实物阶段则考验着你对工程细节的把握能力。