在实际嵌入式开发项目中,湿度控制是一个经典且实用的应用场景。无论是家庭环境、仓库管理还是精密仪器室,过高的湿度都会导致设备锈蚀、材料霉变甚至电路短路。传统的除湿机往往功能单一,缺乏智能感知和远程控制能力。基于STM32微控制器构建一个智能除湿系统,不仅能精确采集环境数据,还能通过算法实现自动控制,是学习嵌入式系统从传感器到执行器完整链路、理解实时控制逻辑的绝佳实践。本文将以一个已开源的STM32智能除湿系统项目为蓝本,带你从零开始理解其设计思路,复现其硬件连接与软件实现,并深入剖析源码中的关键模块与配置细节。无论你是正在学习STM32的在校学生,还是希望将想法落地的电子爱好者,通过本文的步骤,你都能掌握搭建一个可运行、可扩展的智能除湿原型系统的核心技能。
1. 理解智能除湿系统的核心架构与工作原理
一个完整的智能除湿系统远不止是单片机读取一个湿度值那么简单。它需要形成一个“感知-决策-执行”的闭环,并考虑人机交互与系统可靠性。在开始动手之前,必须清晰地理解整个系统的架构和各部分之间的协作关系。
1.1 系统核心功能模块分解
典型的STM32智能除湿系统包含以下几个核心模块:
- 环境感知模块:负责采集关键的物理量。最核心的是温湿度传感器(如DHT11、DHT22、SHT3x等),用于获取当前环境的温度和相对湿度。此外,可能还包括水位检测传感器,用于监测除湿机水箱是否已满,防止溢出。
- 核心控制模块:以STM32微控制器为核心,负责运行控制算法、处理传感器数据、驱动执行器并管理其他外设。它是整个系统的大脑。
- 执行器驱动模块:根据控制器的决策,执行具体动作。最主要的是继电器模块,用于控制除湿机压缩机或风扇的电源通断。还可能包括蜂鸣器(用于报警)和指示灯。
- 人机交互模块:提供系统状态显示和参数设置界面。通常使用OLED显示屏、LCD屏或简单的数码管来显示实时温湿度、设定阈值、系统状态等。按键或旋转编码器则用于用户设定目标湿度阈值、手动开关机等。
- 通信模块(可选但推荐):用于实现远程监控和控制。可以通过ESP8266/ESP32等Wi-Fi模块连接网络,将数据上报到云平台或手机APP,也可以使用蓝牙模块与手机进行短距离通信。
- 电源模块:为整个系统提供稳定、隔离的电源。特别是继电器控制部分,通常需要将STM32的3.3V/5V逻辑电路与220V交流负载电路进行物理隔离,以确保安全。
1.2 系统工作流程与控制逻辑
系统的智能体现在其自动控制逻辑上。一个基础但有效的控制流程如下:
- 初始化:系统上电后,STM32初始化所有外设(GPIO、定时器、ADC、I2C/SPI、串口等),读取存储在EEPROM或Flash中的用户设定阈值(如目标湿度55%),并在显示屏上显示欢迎界面和当前参数。
- 数据采集:STM32以固定周期(例如每2秒)通过数字接口或模拟接口读取温湿度传感器的数据。
- 数据处理与判断:将读取到的当前湿度值与用户设定的目标湿度阈值进行比较。同时,检查水箱水位传感器状态。
- 如果当前湿度 > 目标湿度 + 迟滞上限(例如设定55%,迟滞为5%,则上限为60%),且水箱未满,则启动除湿(闭合继电器)。
- 如果当前湿度 < 目标湿度 - 迟滞下限(例如50%),则停止除湿(断开继电器)。
- 引入“迟滞”是为了防止系统在阈值附近频繁启停,保护压缩机。这是实际工程中必须考虑的一点。
- 驱动执行:根据判断结果,控制GPIO引脚输出高/低电平,进而驱动继电器线圈吸合或释放,从而控制除湿机大功率负载的通断。
- 状态更新与交互:在显示屏上更新当前的温湿度值、系统运行状态(如“运行中”、“停止”、“水箱满”)、设定阈值等信息。同时检测按键事件,如有用户操作,则进入设置模式更新参数。
- 通信上报(如果具备):将系统状态和数据打包,通过串口发送给Wi-Fi模块,由模块上传至服务器。
理解了这个闭环流程,再看源码和原理图就会清晰很多。接下来,我们将进入实战环节,从环境准备开始,一步步构建这个系统。
2. 环境准备与硬件平台搭建
在开始编写和阅读代码之前,必须准备好正确的开发环境,并理解硬件连接。这是后续一切工作的基础。
2.1 软件开发环境准备
对于STM32开发,我们主要需要以下软件工具:
- 集成开发环境 (IDE):
- Keil MDK-ARM (uVision):经典、稳定,国内用户广泛。需要安装对应的STM32器件支持包。
- STM32CubeIDE:ST官方推出的免费IDE,基于Eclipse,集成了STM32CubeMX图形化配置工具,非常适合初学者和快速原型开发。本文后续示例将主要基于此环境。
- IAR Embedded Workbench:另一款商业IDE,性能优异。
- STM32CubeMX:这是一个独立的图形化软件配置工具,可以直观地配置引脚、时钟、外设、中间件,并生成初始化代码框架。即使使用STM32CubeIDE,其内部也集成了此功能。
- 串口调试助手:用于查看STM32通过串口打印的调试信息,如
printf输出。常用的有XCOM、SSCOM、Putty等。 - 程序下载工具:用于将编译好的固件烧录到STM32芯片中。
- ST-LINK/V2调试器/编程器:最常用的官方工具。
- J-Link:SEGGER公司出品,支持芯片广泛,调试性能强大。
- USB转TTL串口模块:如果使用串口下载方式(ISP),则需要此模块。
环境搭建步骤简述:
- 从ST官网下载并安装STM32CubeIDE。
- 安装时,它会提示安装或更新STM32CubeMX和HAL库,同意即可。
- 安装完成后,打开STM32CubeIDE,它已经包含了编译器、调试器和CubeMX配置功能。
- 准备一个ST-LINK调试器,并将其SWD接口(SWCLK、SWDIO、GND、3.3V)与你的STM32开发板对应连接。
2.2 硬件元器件清单与核心连接
参考开源项目的原理图,我们需要以下核心硬件。这里以常见的STM32F103C8T6(蓝色战舰最小系统板)为例进行说明。
| 模块 | 推荐型号 | 主要功能 | 与STM32连接方式(示例) |
|---|---|---|---|
| 主控 | STM32F103C8T6 | 系统核心,运行控制逻辑 | - |
| 温湿度传感器 | DHT11 | 采集温度、湿度 | 数据线接GPIO引脚(如PA0),需接上拉电阻 |
| 继电器模块 | 5V/10A单路继电器 | 控制除湿机电源 | IN引脚接GPIO(如PA1),控制低电平有效 |
| OLED显示屏 | 0.96寸 SSD1306 I2C | 显示信息 | SCL接PB6,SDA接PB7(I2C1) |
| 按键 | 轻触开关 | 设置参数、切换模式 | 接GPIO(如PA2,PA3),配置为上拉输入 |
| 蜂鸣器 | 有源蜂鸣器 | 报警提示 | 接GPIO(如PA4),低电平触发 |
| 水位传感器 | 干簧管/浮球开关 | 检测水箱是否满 | 接GPIO(如PA5),配置为上拉输入 |
关键硬件连接详解:
- DHT11:这是一个单总线数字传感器。只需要一根数据线。STM32的GPIO引脚需要配置为开漏输出模式,并外部接一个4.7K-10K的上拉电阻到VCC。通信时序要求严格,需要微秒级延时,通常用定时器或
DWT周期计数器实现。 - 继电器:继电器模块的“IN”信号引脚为低电平(0V)时,继电器吸合(常开触点闭合);高电平(5V/3.3V)时断开。STM32的GPIO输出高电平为3.3V,对于5V供电的继电器模块,这个电压可能无法可靠使其断开。因此,建议选用支持3.3V控制的继电器模块,或者在GPIO与继电器IN引脚之间增加一个电平转换电路或三极管驱动电路。在原理图中,这部分隔离和驱动设计至关重要。
- OLED (I2C):连接简单,但需注意I2C总线上需要接上拉电阻(通常模块已集成)。在代码中需要正确实现I2C的读写函数。
- 电源:STM32开发板通常由USB供电(5V)。继电器模块如果驱动大负载,建议单独供电,并与STM32的GND共地。绝对禁止将220V强电部分与STM32弱电部分有任何直接的电气连接,继电器是实现强弱电隔离的关键器件。
3. 基于STM32CubeMX创建工程与配置
理解了硬件连接后,我们可以使用STM32CubeMX来图形化地配置引脚和生成代码框架,这能极大减少底层驱动代码的编写工作量。
3.1 创建新工程与芯片选型
- 打开STM32CubeIDE,选择
File -> New -> STM32 Project。 - 在芯片选择器中,输入
STM32F103C8,选择STM32F103C8Tx,点击Next。 - 为工程命名,例如
Smart_Dehumidifier,选择保存路径,点击Finish。CubeMX配置界面会自动打开。
3.2 图形化引脚与时钟配置
在Pinout & Configuration标签页下进行配置:
系统核心(SYS):
- 在
System Core -> SYS中,将Debug设置为Serial Wire。这是ST-LINK调试所必需的。
- 在
时钟(RCC):
- 在
System Core -> RCC中,将High Speed Clock (HSE)设置为Crystal/Ceramic Resonator。这表示我们使用外部高速晶振(通常开发板上是8MHz)。
- 在
GPIO引脚配置:
- 根据之前的硬件连接表,在芯片图形上点击对应引脚,选择其功能。
- PA0: 设置为
GPIO_Input,用于DHT11数据线。由于DHT11需要切换输入输出模式,这里先设为输入,代码中动态切换。 - PA1: 设置为
GPIO_Output,初始输出电平为High(继电器断开状态),用于控制继电器。 - PA2, PA3: 设置为
GPIO_Input,并激活内部上拉电阻(Pull-up),用于按键。 - PA4: 设置为
GPIO_Output,初始输出High(蜂鸣器不响),用于控制蜂鸣器。 - PA5: 设置为
GPIO_Input,激活内部上拉电阻,用于水位传感器。 - PB6, PB7: 设置为
I2C1_SCL和I2C1_SDA,用于OLED显示屏。
定时器(TIM)配置:
- 我们需要一个定时器来产生精确的微秒延时,用于驱动DHT11。
- 在
Timers -> TIM2(或其他空闲定时器)中,将Clock Source设为Internal Clock。 - 在
Parameter Settings中,设置Prescaler和Counter Period,使定时器计数频率为1MHz(即每计数一次为1微秒)。假设系统时钟为72MHz,则预分频器Prescaler设为72-1=71,计数器周期Counter Period设为最大值65535即可。开启定时器中断不是必须的,我们可以用查询方式。
I2C配置:
- 在
Connectivity -> I2C1中,将I2C Speed Mode设置为Standard Mode(100kHz) 或Fast Mode(400kHz)。SSD1306通常支持400kHz。
- 在
串口(USART)配置(用于调试打印):
- 在
Connectivity -> USART1中,将模式设置为Asynchronous。 - 参数设置:波特率
115200,字长8 Bits,停止位1,无校验,无流控。
- 在
3.3 生成工程代码
- 点击
Project Manager标签页。 - 在
Project子标签中,选择Toolchain / IDE为STM32CubeIDE。 - 在
Code Generator子标签中,勾选Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral,这会使外设代码更模块化。 - 点击右上角的
GENERATE CODE按钮。STM32CubeIDE会自动生成完整的初始化代码工程。
4. 核心功能模块的代码实现与解析
生成了基础工程后,我们需要在Core/Src/main.c和自行创建的模块文件中添加业务逻辑。一个良好的实践是将不同功能封装成独立的.c/.h文件。
4.1 微秒延时函数实现(用于DHT11)
由于DHT11的时序要求精确到微秒级别,标准的HAL_Delay()(毫秒级)不适用。我们可以利用之前配置的定时器TIM2来实现。
在Core/Src下新建dht11.c和dht11.h。
dht11.h头文件定义:
#ifndef __DHT11_H #define __DHT11_H #include "main.h" // 定义DHT11数据线连接的GPIO端口和引脚 #define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_PIN_0 // 函数声明 void DHT11_GPIO_Init(void); uint8_t DHT11_Read_Data(uint8_t *temperature, uint8_t *humidity); void DHT11_Delay_us(uint16_t us); // 微秒延时函数声明 #endifdht11.c源文件实现(关键部分):
#include "dht11.h" #include "tim.h" // 包含TIM2的头文件 // 微秒延时函数,基于TIM2 void DHT11_Delay_us(uint16_t us) { __HAL_TIM_SET_COUNTER(&htim2, 0); // 定时器计数器清零 HAL_TIM_Base_Start(&htim2); // 启动定时器 while (__HAL_TIM_GET_COUNTER(&htim2) < us); // 等待计数值达到us HAL_TIM_Base_Stop(&htim2); // 停止定时器 } // DHT11初始化,将引脚设置为输出模式并输出起始信号 void DHT11_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); // 先拉高 } // 读取DHT11数据(简化版,未包含完整的校验和超时处理) uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t data[5] = {0}; uint8_t i, j; // 主机发送起始信号 HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); DHT11_Delay_us(18000); // 拉低至少18ms HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); DHT11_Delay_us(30); // 拉高20-40us // 切换为输入模式,等待DHT11响应 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); // ... (等待DHT11拉低、拉高响应信号) // ... (读取40位数据,判断高低电平持续时间) // ... (将5字节数据存入data数组) // 校验和检查 if(data[4] == (data[0] + data[1] + data[2] + data[3])) { *humi = data[0]; *temp = data[2]; return 1; // 读取成功 } return 0; // 读取失败 }注意:以上是DHT11驱动的核心框架。完整的驱动需要精确判断DHT11的响应时序和每一位数据的电平持续时间(约26-28us为0,70us为1),并加入超时判断,代码较长。在实际项目中,建议参考成熟的开源驱动库。
4.2 OLED显示驱动与界面逻辑
对于SSD1306 OLED,我们可以使用现有的开源驱动库,如OLED_SSD1306。将其oled.c和oled.h、font.h等文件添加到工程中。
在Core/Src/main.c中,我们需要初始化OLED并显示信息。
/* 在main函数开头变量定义部分 */ uint8_t current_temp, current_humi; uint8_t set_humi_threshold = 55; // 默认设定湿度阈值 uint8_t system_state = 0; // 0:停止,1:运行,2:水箱满报警 /* 在main函数初始化部分,while(1)之前 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 初始化微秒延时定时器 MX_I2C1_Init(); MX_USART1_UART_Init(); OLED_Init(); // 初始化OLED OLED_Clear(); DHT11_GPIO_Init(); /* 在while(1)循环中 */ // 1. 读取传感器数据 if(DHT11_Read_Data(¤t_temp, ¤t_humi)) { // 2. 更新显示 OLED_ShowString(0, 0, "Temp:", 16); OLED_ShowNum(40, 0, current_temp, 2, 16); OLED_ShowString(60, 0, "C", 16); OLED_ShowString(0, 2, "Humi:", 16); OLED_ShowNum(40, 2, current_humi, 2, 16); OLED_ShowString(60, 2, "%", 16); OLED_ShowString(0, 4, "SetH:", 16); OLED_ShowNum(40, 4, set_humi_threshold, 2, 16); OLED_ShowString(60, 4, "%", 16); // 3. 控制逻辑判断 uint8_t water_full = HAL_GPIO_ReadPin(WATER_SENSOR_GPIO_Port, WATER_SENSOR_Pin); if(water_full == GPIO_PIN_RESET) { // 假设传感器满水时输出低电平 system_state = 2; // 水箱满 HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET); // 强制停止除湿 HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET); // 报警 OLED_ShowString(0, 6, "State: FULL!", 16); } else { HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET); // 关闭报警 if(current_humi > set_humi_threshold + 5) { // 当前湿度高于设定值+迟滞 system_state = 1; HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET); // 拉低,继电器吸合,开始除湿 OLED_ShowString(0, 6, "State: RUN ", 16); } else if(current_humi < set_humi_threshold - 5) { // 当前湿度低于设定值-迟滞 system_state = 0; HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET); // 拉高,继电器断开,停止除湿 OLED_ShowString(0, 6, "State: STOP ", 16); } } } else { OLED_ShowString(0, 6, "DHT11 Error!", 16); } HAL_Delay(2000); // 每2秒读取一次4.3 按键扫描与参数设置
为了允许用户修改湿度阈值,我们需要实现按键扫描功能。通常采用状态机或简单的延时消抖方式。
// 在main.c中定义一个按键扫描函数 void Key_Scan(void) { static uint8_t key1_last_state = 1; static uint8_t key2_last_state = 1; uint8_t key1_current = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); uint8_t key2_current = HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin); // 按键1按下(假设低电平有效),增加设定湿度 if(key1_current == 0 && key1_last_state == 1) { HAL_Delay(10); // 简单消抖 if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0) { set_humi_threshold++; if(set_humi_threshold > 80) set_humi_threshold = 80; // 上限 // 可以在这里将set_humi_threshold保存到EEPROM } } key1_last_state = key1_current; // 按键2按下,减少设定湿度 if(key2_current == 0 && key2_last_state == 1) { HAL_Delay(10); if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == 0) { set_humi_threshold--; if(set_humi_threshold < 30) set_humi_threshold = 30; // 下限 // 保存到EEPROM } } key2_last_state = key2_current; }在while(1)循环中调用Key_Scan()函数即可。
5. 系统联调、验证与常见问题排查
代码编写完成后,需要编译、下载到开发板,并进行实际测试。这个阶段最容易遇到各种问题。
5.1 编译与下载
- 在STM32CubeIDE中,点击工具栏上的锤子图标进行编译。确保0错误,0警告(一些未使用的变量警告可暂时忽略)。
- 将ST-LINK通过SWD接口与开发板连接好,并给开发板上电。
- 点击虫子图标进入调试模式,程序会自动下载并运行。或者点击绿色播放箭头直接下载并复位运行。
5.2 功能验证步骤
按照以下顺序验证系统功能:
- 电源与显示:上电后,观察OLED屏幕是否点亮并显示初始界面(如温度、湿度、设定值、状态)。
- 传感器读取:用手握住DHT11传感器,观察屏幕上的温湿度数值是否发生变化。如果显示“DHT11 Error”,则说明通信失败。
- 按键功能:按下增加/减少按键,观察“SetH”(设定湿度)数值是否随之变化。
- 控制逻辑:
- 用嘴对DHT11哈气(增加湿度),使其读数超过“设定值+5”。观察状态是否变为“RUN”,同时继电器模块的指示灯是否亮起(或听到“咔哒”吸合声)。
- 停止哈气,等待湿度下降至低于“设定值-5”。观察状态是否变为“STOP”,继电器是否断开。
- 报警功能:模拟水箱满的状态(将水位传感器的输入引脚短接到地),观察状态是否变为“FULL!”,蜂鸣器是否鸣叫,继电器是否强制断开。
5.3 常见问题与排查路径
在调试过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| OLED不显示 | 1. I2C地址错误。 2. I2C引脚接反或接触不良。 3. 初始化时序或命令错误。 4. 电源问题。 | 1. 用逻辑分析仪或示波器抓取I2C波形,确认是否有起始信号、地址和数据。SSD1306的I2C地址通常是0x78或0x7A。 2. 检查PB6、PB7连接,确认上拉电阻(4.7K-10K)已接好。 3. 核对OLED驱动库的初始化序列,特别是复位引脚(如果有)的时序。 4. 测量OLED模块VCC电压是否为3.3V或5V(视模块而定)。 |
| DHT11读取失败 | 1. 时序不精确,微秒延时不准。 2. 引脚模式切换错误。 3. 未接上拉电阻。 4. 传感器损坏或接触不良。 | 1. 使用示波器或逻辑分析仪监测DHT11数据线波形,对照数据手册检查起始信号、响应信号和各数据位时长。 2. 确保在发送起始信号时为输出模式,等待响应和读取数据时为输入上拉模式。 3. 在数据线和VCC之间连接一个4.7K电阻。 4. 更换传感器测试。 |
| 继电器不动作 | 1. STM32 GPIO输出电流不足。 2. 继电器模块控制逻辑弄反(高/低电平有效)。 3. 继电器模块供电不足。 4. 负载过大或短路。 | 1. STM32 GPIO最大输出电流约20mA,检查继电器模块IN端所需电流,过大则需要加三极管驱动。 2. 用万用表测量继电器IN端电压,当代码输出 GPIO_PIN_RESET(0V)时,IN端应为低电平。根据模块规格书确认控制逻辑。3. 确保继电器模块的VCC和GND供电稳定(5V或3.3V)。 4.特别注意:调试强电部分务必断电操作,确保安全。先空载测试继电器动作声音,再接负载。 |
| 按键不灵敏或连击 | 1. 消抖处理不当。 2. 扫描频率太快或太慢。 3. 硬件上拉电阻未启用或失效。 | 1. 采用状态机或更可靠的消抖算法,避免在中断或循环中简单使用HAL_Delay。2. 将按键扫描放在主循环中,周期在10-50ms为宜。 3. 确认GPIO配置为输入上拉模式,或硬件有上拉电阻。 |
| 程序运行不稳定,偶尔死机 | 1. 堆栈溢出。 2. 中断冲突或优先级设置不当。 3. 电源纹波大。 4. 看门狗未处理。 | 1. 在STM32CubeIDE的工程属性中,适当增大Stack Size和Heap Size(如从0x400增加到0x800)。2. 检查是否在中断服务程序(如定时器中断)中执行了耗时操作或调用了 HAL_Delay。3. 用示波器测量开发板电源电压,尤其在继电器动作时是否有大幅跌落。 4. 如果使能了独立看门狗(IWDG),需要在主循环中定期“喂狗”。 |
6. 从原型到产品:最佳实践与扩展方向
一个能在实验室运行的原型,与一个稳定可靠的产品之间,还有不少距离。以下是一些提升系统鲁棒性和功能性的建议。
6.1 系统稳定性与可靠性增强
- 增加看门狗:启用STM32内部的独立看门狗(IWDG)或窗口看门狗(WWDG),在主循环或定时中断中定期喂狗。当程序跑飞或陷入死循环时,看门狗会自动复位系统,提高容错能力。
- 参数掉电保存:将用户设定的湿度阈值、系统配置等参数保存到STM32内部的Flash或外置的EEPROM芯片(如AT24C02)中。避免每次上电都需要重新设置。
// 示例:使用HAL库操作内部Flash保存一个字节数据(需注意擦写寿命) #define SETTINGS_FLASH_ADDR 0x0800FC00 // 选择Flash末尾的某一页 void Save_Settings(void) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef EraseInitStruct; uint32_t PageError = 0; EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; EraseInitStruct.PageAddress = SETTINGS_FLASH_ADDR; EraseInitStruct.NbPages = 1; HAL_FLASHEx_Erase(&EraseInitStruct, &PageError); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, SETTINGS_FLASH_ADDR, set_humi_threshold); HAL_FLASH_Lock(); } - 传感器数据滤波:直接读取的传感器数据可能存在毛刺。可以采用滑动平均滤波、中值滤波或一阶低通滤波算法,使显示和控制更加平滑稳定。
#define FILTER_LEN 5 uint8_t humi_buf[FILTER_LEN] = {0}; uint8_t humi_index = 0; uint8_t Get_Filtered_Humidity(uint8_t new_val) { humi_buf[humi_index] = new_val; humi_index = (humi_index + 1) % FILTER_LEN; uint16_t sum = 0; for(int i=0; i<FILTER_LEN; i++) sum += humi_buf[i]; return (uint8_t)(sum / FILTER_LEN); } - 异常状态处理:除了水箱满,还应考虑传感器长时间无响应、通信持续失败、环境温度超出传感器量程等异常情况,并设置相应的系统状态和提示。
6.2 功能扩展方向
- 增加通信模块:
- Wi-Fi (ESP8266/ESP32):通过AT指令或SDK,让STM32将数据发送给ESP模块,模块再连接MQTT服务器(如阿里云、腾讯云物联网平台)。实现手机APP远程查看和控制。
- 蓝牙 (HC-05/06):与手机APP进行短距离通信,实现本地无线控制和数据查看。
- 升级传感器:将DHT11更换为更精确、更快速的数字传感器,如DHT22、SHT30、AHT20等。这些传感器通常使用I2C接口,通信更可靠,精度更高。
- 实现PID控制:对于要求更高的除湿场景,可以使用PID算法来控制继电器的通断时间(PWM占空比),实现更平滑的湿度控制,而不是简单的开关控制。
- 增加更多人机交互:使用触摸屏、旋转编码器+菜单系统,实现更复杂的参数设置(如多个湿度档位、定时开关机、风速调节等)。
- 低功耗设计:如果系统由电池供电,需要进入低功耗模式。在非采集和控制时段,让STM32进入
Stop或Sleep模式,通过RTC定时唤醒或外部中断(按键)唤醒。
6.3 安全注意事项(重中之重)
- 强弱电隔离:继电器是隔离的关键,确保其线圈侧(控制端)与触点侧(负载端)之间没有电气连接。布线时,强电(220V)走线与弱电(STM32)走线应分开,避免平行走线,必要时使用屏蔽或开槽隔离。
- 电源安全:为继电器模块和STM32系统供电的电源适配器应有足够的功率余量和安全认证。强电部分接线必须牢固,使用绝缘端子,裸露部分用热缩管或绝缘胶带包裹。
- 软件互锁:在代码中,除湿动作(继电器闭合)必须同时满足多个条件:湿度超标、水箱未满、系统处于自动模式、无故障报警等。避免因单一条件误触发导致设备损坏。
- 外壳与防护:产品化时,必须使用符合安全标准的绝缘外壳,并考虑散热、防潮、防尘。
通过以上步骤,你不仅能够复现一个基本的STM32智能除湿系统,更能深入理解嵌入式系统开发中硬件选型、驱动编写、控制逻辑、调试排错和产品化思考的全过程。这个项目是一个优秀的起点,你可以基于它,结合具体的应用场景,不断迭代和扩展,最终打造出符合自己需求的智能硬件产品。