项目清单:
一.硬件配置清单
二.核心代码补充
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "i2c.h"
#include "icache.h"
#include "spi.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "gai.h"
#include "bsp_sht20.h"
#include "stdio.h"
#include "string.h"
#include "bsp_ili9341_4line.h"
//printf实现重定向
int fputc(int ch,FILE * p)
{
while(!(USART1->ISR & (1<<7))) {}
//判断发送数据寄存器是否为空
//第七位为1的时候为空,应该发送数据,此时跳出阻塞循环
USART1->TDR = ch;
return ch;
}
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void SystemPower_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
float T,H;//存储温湿度值
uint8_t recv_buf[1024]; //不定长接收缓冲区
uint8_t recv_flag; //不定长接收标志位
uint8_t fan[32]; // 显示风机
uint8_t condenser[32]; // 显示制冷器
uint8_t heater[32]; // 显示加热器
uint8_t temperature[32]; // 显示温度
uint8_t humidity[32]; // 显示湿度
uint8_t Hum_threshold[32]; // 显示湿度阈值
uint8_t Threshold_max[32]; // 显示温度上阈值
uint8_t Threshold_min[32]; // 显示温度下阈值
uint8_t ID[32]; //显示设备ID
int id=123;
uint8_t vbat[32]; //屏幕显示电压
uint32_t val; //设备电池电压
float Vbat; //电压
float TempLow = 20.0; //温度下限阀值
float TempHigh = 40.0; //温度上限阀值
float Hum_level = 50.0; //湿度阀值
int flag = 0; // 手动模式下的标志位,1为调温度上阈值,2为调温度下阈值,3为调湿度阈值
uint8_t Flag[32];//屏幕显示调那个阈值
int mode = 0; // 切换自动模式和手动模式 0为自动 1为手动
uint8_t Mode[32]; //屏幕显示自动或者手动模式 0为自动 1为手动
void lcd_display(void);//LCD屏幕显示
void uart_handle(void);// 不定长接收中断操作
void auto_control(void);//自动模式
//开机自检
void SystemSelfTest(void);
void SystemSelfTest_Fail(void);
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* Configure the System Power */
SystemPower_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_ICACHE_Init();
MX_TIM6_Init();
MX_ADC4_Init();
MX_TIM17_Init();
/* USER CODE BEGIN 2 */
ILI9341_Init(); //初始化屏幕
//ILI9341_Clear(0xf81f); //清屏并显示颜色
//参数:X轴;Y轴;字色;背景色,显示数据
//Gui_DrawFont_GBK16(10,30,BLACK,0xbaba,"gaigai666");
//不定长接收中断
HAL_UART_Receive_IT(&huart1,recv_buf,1024); //开启接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_FLAG_IDLE); //开启空闲中断
HAL_PWREx_EnableVddA(); //启用VDDA电压
HAL_PWREx_EnableVddIO2(); //启用VDDIO电压
HAL_ADCEx_Calibration_Start(&hadc4,ADC_CALIB_OFFSET,ADC_SINGLE_ENDED); //校准单端ADC采样
//启动ADC转换
HAL_ADC_Start(&hadc4);
HAL_ADC_PollForConversion(&hadc4,100); //等待转换完成,第二个参数表示超时时间,单位ms
val=HAL_ADC_GetValue(&hadc4); //获取ADC转换结果
Vbat=(val*4*3.3)/4095; //转换成真实电压值
if(Vbat>0)
{
HAL_TIM_Base_Start_IT(&htim17); //开启定时,用于定时两秒上报数据
recv_flag = 0; //不定长接收标志位初始化
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//温湿度采集
BSP_SHT20_GetData();
//启动ADC转换
HAL_ADC_Start(&hadc4);
HAL_ADC_PollForConversion(&hadc4, 100); // 等待转换完成,第二个参数表示超时时间,单位ms
val = HAL_ADC_GetValue(&hadc4); // 获取ADC转换结果
Vbat = (val*4*3.3)/4095; //转换成真实电压值
uart_handle(); // 串口发送操作
auto_control(); // 自动模式的操作
lcd_display(); // LCD显示
}
}
else
{
Gui_DrawFont_GBK16(40, 150, WHITE, BLUE, (uint8_t *)"Sorry Failed");//蓝屏并显示失败
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_0;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLMBOOST = RCC_PLLMBOOST_DIV4;
RCC_OscInitStruct.PLL.PLLM = 3;
RCC_OscInitStruct.PLL.PLLN = 10;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 1;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLLVCIRANGE_1;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief Power Configuration
* @retval None
*/
static void SystemPower_Config(void)
{
/*
* Disable the internal Pull-Up in Dead Battery pins of UCPD peripheral
*/
HAL_PWREx_DisableUCPDDeadBattery();
/* USER CODE BEGIN PWR */
/* USER CODE END PWR */
}
/* USER CODE BEGIN 4 */
// 按键中断回调函数
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
HAL_TIM_Base_Start_IT(&htim6); //定时器消抖
}
// 定时器回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim17) //定时两秒上报数据
{
printf("id:%d,flag:%d,mode:%d\ntemperature:%.2f, humidity:%.2f,Hum_level:%.2f\nTempHigh:%.2f,TempLow:%.2f,Vbat:%.2f",id, flag, mode, T, H, Hum_level, TempHigh, TempLow, Vbat);
}
if(htim == &htim6)//消抖,按键操作
{
if (HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_12)==0)//USER按键 PA12 切换模式
{
mode=1-mode;//0为自动,1为手动
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6 | GPIO_PIN_4 | GPIO_PIN_13, 0);//全都关闭
strcpy(fan, "fan : OFF");
strcpy(condenser, "condenser : OFF");
strcpy(heater, "heater : OFF");
}
if(mode==1) //手动模式
{
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_9)==0)//KEY1 PC9
{
flag++;
if(flag>3)
flag=0; //flag=0表示确认
}
if(flag==1)//温度上阈值
{
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)==0)
TempHigh++;
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5)==0)
TempHigh--;
}
if(flag==2)//温度下阈值
{
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)==0)
TempLow++;
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5)==0)
TempLow--;
}
if(flag==3)//湿度阈值
{
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_8)==0)
Hum_level++;
if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5)==0)
Hum_level--;
}
}
HAL_TIM_Base_Stop_IT(&htim6);//取消消抖
}
}
//LCD屏幕显示内容
void lcd_display(void)
{
sprintf(temperature, "temperature : %.2f",T);
sprintf(humidity, "humidity : %.2f ", H);
sprintf(Hum_threshold, "Hum_level : %.2f", Hum_level);
sprintf(Threshold_max, "Tem_High : %.2f", TempHigh);
sprintf(Threshold_min, "Tem_Low : %.2f", TempLow);
sprintf(ID, "ID : %d", id);
sprintf(vbat, "vbat : %.2f", Vbat);
sprintf(Mode, "Mode : %d ", mode);
sprintf(Flag, "Flag : %d ", flag);
Gui_DrawFont_GBK16(45, 30, BLUE, WHITE, (uint8_t *)"Smart dehumidifier");//大标题,智能除湿器
Gui_DrawFont_GBK16(20, 65, BLACK, WHITE, temperature);// 显示温湿度
Gui_DrawFont_GBK16(20, 90, BLACK, WHITE, humidity);
if(flag==0)
{
Gui_DrawFont_GBK16(20, 125, BLACK, WHITE, Threshold_max);// 显示温度上阀值
Gui_DrawFont_GBK16(20, 145, BLACK, WHITE, Threshold_min);// 显示温度下阀值
Gui_DrawFont_GBK16(20, 165, BLACK, WHITE, Hum_threshold);// 显示湿度阀值
}
if(flag == 1)
Gui_DrawFont_GBK16(20, 125, BLACK, BLUE, Threshold_max);// 显示温度上阀值
else
Gui_DrawFont_GBK16(20, 125, BLACK, WHITE, Threshold_max);// 显示温度上阀值
if(flag == 2)
Gui_DrawFont_GBK16(20, 145, BLACK, BLUE, Threshold_min);// 显示温度下阀值
else
Gui_DrawFont_GBK16(20, 145, BLACK, WHITE, Threshold_min);// 显示温度下阀值
if(flag == 3)
Gui_DrawFont_GBK16(20, 165, BLACK, BLUE, Hum_threshold);// 显示湿度阀值
else
Gui_DrawFont_GBK16(20, 165, BLACK, WHITE, Hum_threshold);// 显示湿度阀值
Gui_DrawFont_GBK16(35, 190, BLACK, WHITE, fan);//显示风扇开关情况
Gui_DrawFont_GBK16(35, 205, BLACK, WHITE, condenser);// 显示制冷机开关情况
Gui_DrawFont_GBK16(35, 220, BLACK, WHITE, heater);// 显示加热片开关情况
Gui_DrawFont_GBK16(20, 240, BLACK, WHITE, ID);// 设备ID
Gui_DrawFont_GBK16(20, 260, BLACK, WHITE, vbat);// 显示电压
Gui_DrawFont_GBK16(20, 280, BLACK, WHITE, Mode);// 显示模式
Gui_DrawFont_GBK16(100, 280, BLACK, WHITE, Flag);// 显示调那个阈值
}
/*
strstr(recv_buf, "Hum_level=");
strstr 从 recv_buf 的开头开始扫描,寻找子字符串 "Hum_level="。
如果找到 "Hum_level=",strstr 返回一个指向该子字符串在 USART1_RxBuff 中起始位置的指针。
如果未找到 "Hum_level=",strstr 返回 NULL。
sscanf(recv_buf, "Hum_level= %f", &Hum_level);
sscanf 从 recv_buf 的开头开始扫描,寻找与 "Hum_level= " 匹配的文本
一旦找到匹配的部分,sscanf 会继续扫描,寻找一个浮点数(由 %f 指定)
如果找到一个有效的浮点数,sscanf 会将其值存储到 Hum_level
*/
// 接收中断处理
void uart_handle(void)
{
if (recv_flag==1)
{
if (strncmp(recv_buf, "123", 3)==0)//识别设备ID
{
if (strstr(recv_buf, "Hum_level="))
{
sscanf(recv_buf, "123 Hum_level=%f", &Hum_level);
}
else if (strstr(recv_buf, "TempHigh="))
{
sscanf(recv_buf, "123 TempHigh=%f", &TempHigh);
}
else if (strstr(recv_buf, "TempLow="))
{
sscanf(recv_buf, "123 TempLow=%f", &TempLow);
}
else if (strstr(recv_buf, "mode="))
{
sscanf(recv_buf, "123 mode=%d", &mode);
}
else if (strstr(recv_buf, "123 fan_on"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, 1);//开启风扇
}
else if (strstr(recv_buf, "123 fan_off"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, 0);//关闭风扇
}
else if (strstr(recv_buf, "123 heater_on"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);//开启LD3加热器
}
else if (strstr(recv_buf, "123 heater_off"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);//关闭LD3加热器
}
else if (strstr(recv_buf, "123 condenser_on"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, 1);//开启LD1制冷器
}
else if (strstr(recv_buf, "123 condenser_on"))
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, 0);//关闭LD1制冷器
}
}
else
{
printf("未识别到该设备!\n");
}
}
recv_flag = 0;//重置接收标志位
memset(recv_buf, 0, 1024);//清空接收数据
}
// 自动模式控制
// PC6 风扇
// PC4 LD1 制冷器
// PC13 LD3 加热器
void auto_control(void)
{
static uint8_t f = 0;//除湿操作默认0为开
if(mode==0)
{
// 先进行除湿
if (H > Hum_level)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6 | GPIO_PIN_4 | GPIO_PIN_13, 1);//全都打开
strcpy(fan, "fan : ON ");
strcpy(condenser, "condenser : ON ");
strcpy(heater, "heater : ON ");
f = 0;//除湿操作0为开
}
else
{
//如果湿度低于或等于 Hum_level,并且之前除湿设备是开启的(f == 0),则关闭除湿设备
//并设置 f = 1,表示除湿设备已关闭。
if (f == 0)
{
f = 1;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6 | GPIO_PIN_4 | GPIO_PIN_13, 0);//全都关闭
strcpy(fan, "fan : OFF");
strcpy(condenser, "condenser : OFF");
strcpy(heater, "heater : OFF");
}
// 然后控温
if (T > TempHigh)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6 | GPIO_PIN_4, 1);//开启风扇和LD1制冷器
strcpy(fan, "fan : ON ");
strcpy(condenser, "condenser : ON ");
}
else if (T < TempHigh - 2)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6 | GPIO_PIN_4, 0);//关闭风扇和LD1制冷器
strcpy(fan, "fan : OFF");
strcpy(condenser, "condenser : OFF");
}
if (T < TempLow)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);//开启LD3加热器
strcpy(heater, "heater : ON ");
}
else if (T > TempLow + 2)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);//关闭LD3加热器
strcpy(heater, "heater : OFF");
}
}
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
三.项目技术要领
一、硬件驱动与外设控制
-
多外设初始化与配置
- 使用 STM32CubeMX 生成基础外设初始化代码(如
MX_GPIO_Init
、MX_I2C1_Init
等),涉及 GPIO、I2C(温湿度传感器 SHT20)、USART(串口通信)、ADC(电池电压采集)、SPI(LCD 屏幕 ILI9341)、TIM(定时器中断)等。 - 关键点:通过
HAL_PWREx_EnableVddA()
和HAL_PWREx_EnableVddIO2()
启用模拟电源和 IO 电源,确保 ADC 等模拟外设正常工作。
- 使用 STM32CubeMX 生成基础外设初始化代码(如
-
温湿度传感器(SHT20)驱动
- 通过 I2C 接口读取数据,调用
BSP_SHT20_GetData()
函数获取温度(T
)和湿度(H
),体现 I2C 通信协议的应用。
- 通过 I2C 接口读取数据,调用
-
LCD 屏幕(ILI9341)驱动
- 使用 SPI 接口驱动屏幕,通过
Gui_DrawFont_GBK16
函数实现中文字符显示,涉及字库调用和坐标定位,需注意显存操作和刷屏效率。
- 使用 SPI 接口驱动屏幕,通过
-
ADC 采集与电压计算
- 校准 ADC(
HAL_ADCEx_Calibration_Start
)后,通过HAL_ADC_GetValue
获取原始值,经公式Vbat=(val*4*3.3)/4095
转换为实际电压(考虑分压电路),体现模拟信号数字化处理流程。
- 校准 ADC(
二、软件架构与功能模块
-
系统初始化与自检
- 在
main
函数中完成外设初始化后,通过Vbat>0
判断电源状态,若异常则显示 “Sorry Failed”,实现基本的开机自检逻辑。
- 在
-
定时任务与中断机制
- 定时器中断:
htim17
每 2 秒触发一次,通过printf
上报设备状态(ID、模式、温湿度、阈值等),体现定时数据上报功能。htim6
用于按键消抖,避免机械按键抖动导致的误触发,提升交互可靠性。
- 串口中断:
- 启用不定长接收中断(
HAL_UART_Receive_IT
)和空闲中断(UART_FLAG_IDLE
),通过recv_flag
标志位触发数据处理,实现串口命令解析(如远程控制阈值、设备开关)。
- 启用不定长接收中断(
- 定时器中断:
-
模式切换与控制逻辑
- 手动模式(
mode=1
):- 通过按键(PA12、PC9、PC8、PC5)切换阈值调整状态(
flag
),直接修改TempHigh
、TempLow
、Hum_level
,并通过 LCD 高亮显示当前调整项(蓝色背景)。
- 通过按键(PA12、PC9、PC8、PC5)切换阈值调整状态(
- 自动模式(
mode=0
):- 基于温湿度阈值自动控制设备:
- 湿度高于阈值时,同时开启风扇、制冷器、加热器(除湿)。
- 湿度正常后,根据温度自动启停制冷器或加热器,体现 PID-like 的滞后控制逻辑(如
T > TempHigh
时启动制冷,T < TempHigh-2
时关闭,避免频繁启停)。
- 基于温湿度阈值自动控制设备:
- 手动模式(
-
串口通信协议
- 采用文本协议,命令格式如
"123 Hum_level=30.5"
(设备 ID + 参数名 + 值),通过strstr
和sscanf
解析指令,实现远程参数配置和设备控制(如fan_on
、heater_off
)。 - 安全机制:通过设备 ID(
id=123
)过滤非法指令,未匹配 ID 时返回 “未识别到该设备”。
- 采用文本协议,命令格式如
三、代码实现细节
-
printf
重定向- 通过重写
fputc
函数,将printf
输出重定向到 USART1,利用串口发送数据,方便调试和状态上报。
- 通过重写
-
字符串操作与缓冲区管理
- 使用
recv_buf[1024]
缓冲区存储串口接收数据,通过memset
清空缓冲区,避免数据残留。 strncmp
、strstr
、sscanf
等函数的组合使用,实现灵活的文本解析逻辑。
- 使用
-
状态显示与用户交互
- LCD 界面布局清晰,分区域显示温湿度、阈值、设备状态、模式等信息,通过颜色区分当前调整项(蓝色背景),提升人机交互体验。
- 按键消抖采用定时器中断处理,确保按键操作的稳定性。
-
静态变量与标志位
auto_control
函数中使用静态变量f
记录除湿设备状态,避免重复开关,优化设备寿命和能耗。flag
和mode
标志位采用全局变量,实现多模块状态同步。
四、技术挑战与优化点
-
实时性与任务调度
- 主循环中顺序执行温湿度采集、ADC 转换、串口处理、LCD 显示等任务,未使用 RTOS,需注意各任务执行时间避免超时(如
HAL_ADC_PollForConversion
的超时参数设为 100ms)。
- 主循环中顺序执行温湿度采集、ADC 转换、串口处理、LCD 显示等任务,未使用 RTOS,需注意各任务执行时间避免超时(如
-
抗干扰与稳定性
- ADC 校准、按键消抖、串口指令过滤(ID 验证)等机制提升系统抗干扰能力,但需注意多中断优先级配置(如 USART 与 TIM 中断优先级),避免中断嵌套导致的异常。
-
代码可维护性
- 函数模块化(如
lcd_display
、auto_control
)提升可读性,但全局变量较多,可考虑封装为结构体或使用静态局部变量限制作用域。
- 函数模块化(如
-
扩展能力
- 现有代码支持通过串口远程控制和阈值配置,可进一步扩展 OTA 升级、无线通信(如 WiFi/BLE)、更多传感器接入等功能。
四.项目实现原理
一、硬件实现原理
1. 传感器数据采集
- 温湿度传感器(SHT20)
- 工作原理:基于电容式湿度传感和带隙温度传感技术,通过 I2C 接口输出数字信号。
- 通信协议:STM32 通过 I2C 总线发送命令(如触发测量),并读取 16 位温湿度数据,经校准后转换为实际值(如温度单位为℃,湿度单位为 % RH)。
- 电池电压检测(ADC)
- 电路设计:采用分压电路(如 4:1)将电池电压降至 ADC 可测量范围(0-3.3V)。
- 转换公式:
Vbat = (ADC值 × 4 × 3.3V) / 4095
,其中 4095 为 12 位 ADC 的满量程值。
2. 执行设备控制
- GPIO 输出:通过 PC4(制冷器)、PC6(风扇)、PC13(加热器)引脚输出高低电平,控制外部继电器或 MOSFET 开关,实现设备启停。
- 安全机制:所有设备默认关闭,仅在满足条件时激活,避免误操作。
3. 人机交互接口
- LCD 显示(ILI9341)
- 通信方式:SPI 总线传输数据,支持 16 位色彩(65K 色)。
- 显示原理:通过写入显存地址和像素数据,控制屏幕各点颜色,实现文本和图形显示。
- 按键输入:PA12(模式切换)、PC8/PC9/PC5(阈值调整)通过外部中断触发,配合定时器消抖确保可靠性。
二、软件架构原理
1. 初始化与自检流程
2. 多任务调度机制
- 主循环(轮询):
主循环(轮询):
while(1) {
温湿度采集;
ADC转换;
串口命令处理;
自动控制逻辑;
LCD刷新;
}
- 中断驱动:
- 定时器中断:htim17(定时上报)和 htim6(按键消抖)通过中断服务函数(ISR)异步执行。
- 串口中断:空闲中断(UART_FLAG_IDLE)检测一帧数据结束,触发命令解析。
3. 状态机控制模型
- 工作模式:
- 自动控制算法:
plaintext
if (湿度 > 阈值) { 启动除湿(风扇+制冷器+加热器); } else { if (温度 > 上限) 启动制冷; else if (温度 < 上限-2) 关闭制冷; if (温度 < 下限) 启动加热; else if (温度 > 下限+2) 关闭加热; }
- 滞后设计:通过
TempHigh-2
和TempLow+2
实现回差控制,避免设备频繁启停。
- 滞后设计:通过
三、通信与数据处理原理
1. 串口协议解析
- 命令格式:
设备ID 参数名=值
(如123 Hum_level=50.0
)。 - 解析流程:
c
if (接收到数据 && ID匹配) { if (strstr(数据, "Hum_level=")) sscanf(数据, "123 Hum_level=%f", &Hum_level); else if (strstr(数据, "fan_on")) 开启风扇; // ...其他命令解析 }
- 安全性:通过 ID 验证过滤非法指令,防止未授权操作。
2. 数据存储与同步
- 全局变量:
TempHigh
、TempLow
、Hum_level
等参数存储在 RAM 中,掉电丢失。 - 显示同步:LCD 显示函数(
lcd_display
)在每次循环中更新界面,确保数据实时可见。
四、核心算法与控制策略
1. 除湿与温控逻辑
- 优先级策略:湿度控制优先于温度控制,即湿度超标时优先除湿,湿度正常后再调节温度。
- 节能设计:通过静态变量
f
记录除湿状态,避免重复启停设备。
2. 按键消抖机制
- 硬件防抖:按键并联电容,减少机械抖动。
- 软件防抖:
c
检测到按键按下 --> 启动定时器 --> 定时器溢出后再次检测电平 --> 确认按键状态
- 有效避免因抖动导致的误触发。
五、系统优化与扩展方向
1. 实时性优化
- 当前问题:主循环顺序执行任务,若某任务耗时过长(如 LCD 刷新),可能影响其他功能响应。
- 解决方案:引入 RTOS(如 FreeRTOS),将任务划分为独立线程(如传感器采集线程、控制线程、显示线程),提高并发处理能力。
2. 数据持久化
- 现状:参数存储在 RAM 中,断电丢失。
- 改进:添加 EEPROM 或 Flash 存储模块,在参数修改时保存到非易失性存储器,开机自动加载。
3. 通信扩展
- 现状:仅支持串口通信,距离受限。
- 升级:集成 WiFi/BLE 模块,实现远程 APP 控制;或添加 MQTT 协议,接入物联网平台。
五.项目实现展示(见哔哩哔哩的视频录制)