feat(BSP): 添加WiFi模块MQTT连接优化和SNTP配置改进 - 添加SNTP配置标志位,确保只在首次连接时配置SNTP,避免重复重启模块 - 实现MQTT断线重连机制,支持持续运行和自动重连 - 增加SNTP时间查询频率控制,避免频繁查询导致性能问题 - 添加连接状态检测和心跳机制,超时自动重连 - 优化LCD时间显示界面,分别显示时分和年月日信息 - 调整LCD页面刷新策略,时间页面30秒刷新一次以节省资源 ```
536 lines
16 KiB
C
536 lines
16 KiB
C
/* USER CODE BEGIN Header */
|
||
/**
|
||
******************************************************************************
|
||
* File Name : freertos.c
|
||
* Description : Code for freertos applications
|
||
******************************************************************************
|
||
* @attention
|
||
*
|
||
* Copyright (c) 2026 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 "FreeRTOS.h"
|
||
#include "task.h"
|
||
#include "main.h"
|
||
#include "cmsis_os.h"
|
||
|
||
/* Private includes ----------------------------------------------------------*/
|
||
/* USER CODE BEGIN Includes */
|
||
#include "device_ctrl.h"
|
||
#include "dx_wf_24.h"
|
||
#include "elog.h"
|
||
#include "mp3_driver.h"
|
||
#include "mp3_play_index.h"
|
||
#include "multi_button.h"
|
||
#include "spi_st7735s.h"
|
||
#include "stdio.h"
|
||
|
||
/* USER CODE END Includes */
|
||
|
||
/* Private typedef -----------------------------------------------------------*/
|
||
/* USER CODE BEGIN PTD */
|
||
|
||
/* USER CODE END PTD */
|
||
|
||
/* Private define ------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PD */
|
||
#define TAG "Main"
|
||
/******************** 按键功能定义 ********************/
|
||
#define KEY1_MODE_SWITCH // 按键1:模式切换(自动/手动)
|
||
#define KEY2_MANUAL_FEED // 按键2:手动喂食(仅手动模式有效)
|
||
#define KEY3_DISPLAY_NEXT // 按键3:切换显示界面
|
||
#define KEY4_TIME_SET // 按键4:长按设置时间
|
||
/* USER CODE END PD */
|
||
|
||
/* Private macro -------------------------------------------------------------*/
|
||
/* USER CODE BEGIN PM */
|
||
|
||
/* USER CODE END PM */
|
||
|
||
/* Private variables ---------------------------------------------------------*/
|
||
/* USER CODE BEGIN Variables */
|
||
|
||
// LCD页面枚举定义
|
||
typedef enum {
|
||
LCD_PAGE_TIME = 0, // 时间显示页面
|
||
LCD_PAGE_TEMP_HUMI, // 温湿度页面
|
||
LCD_PAGE_FOOD_WEIGHT, // 食物重量页面
|
||
LCD_PAGE_WATER_LEVEL, // 水位页面
|
||
LCD_PAGE_SYSTEM_STATUS // 系统状态页面
|
||
} LCD_Page_t;
|
||
|
||
// 传感器数据结构体
|
||
typedef struct {
|
||
float temperature; // 温度值
|
||
float humidity; // 湿度值
|
||
float food_weight; // 食物重量
|
||
uint8_t water_level; // 水位状态 (0=无水, 1=有水)
|
||
uint8_t system_mode; // 系统模式 (0=手动, 1=自动)
|
||
} Sensor_Data_t;
|
||
|
||
// 全局变量
|
||
static LCD_Page_t current_page = LCD_PAGE_TIME; // 当前显示页面
|
||
static Sensor_Data_t sensor_data = {0}; // 传感器数据
|
||
|
||
/* USER CODE END Variables */
|
||
/* Definitions for defaultTask */
|
||
osThreadId_t defaultTaskHandle;
|
||
const osThreadAttr_t defaultTask_attributes = {
|
||
.name = "defaultTask",
|
||
.stack_size = 256 * 4,
|
||
.priority = (osPriority_t) osPriorityHigh1,
|
||
};
|
||
/* Definitions for wifi_mqtt */
|
||
osThreadId_t wifi_mqttHandle;
|
||
const osThreadAttr_t wifi_mqtt_attributes = {
|
||
.name = "wifi_mqtt",
|
||
.stack_size = 3000 * 4,
|
||
.priority = (osPriority_t) osPriorityHigh,
|
||
};
|
||
/* Definitions for LCD_SHOW_Task */
|
||
osThreadId_t LCD_SHOW_TaskHandle;
|
||
const osThreadAttr_t LCD_SHOW_Task_attributes = {
|
||
.name = "LCD_SHOW_Task",
|
||
.stack_size = 1024 * 4,
|
||
.priority = (osPriority_t) osPriorityHigh,
|
||
};
|
||
/* Definitions for button */
|
||
osThreadId_t buttonHandle;
|
||
const osThreadAttr_t button_attributes = {
|
||
.name = "button",
|
||
.stack_size = 512 * 4,
|
||
.priority = (osPriority_t) osPriorityRealtime2,
|
||
};
|
||
|
||
/* Private function prototypes -----------------------------------------------*/
|
||
/* USER CODE BEGIN FunctionPrototypes */
|
||
void LCD_NextPage(void);
|
||
void LCD_PrevPage(void);
|
||
void LCD_SetPage(LCD_Page_t page);
|
||
LCD_Page_t LCD_GetCurrentPage(void);
|
||
void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
|
||
uint8_t mode);
|
||
Sensor_Data_t *LCD_GetSensorData(void);
|
||
void user_button_init(void);
|
||
/* USER CODE END FunctionPrototypes */
|
||
|
||
void StartDefaultTask(void *argument);
|
||
extern void wifi_task_mqtt(void *argument);
|
||
void LCD_Task(void *argument);
|
||
void button_task(void *argument);
|
||
|
||
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
||
|
||
/**
|
||
* @brief FreeRTOS initialization
|
||
* @param None
|
||
* @retval None
|
||
*/
|
||
void MX_FREERTOS_Init(void) {
|
||
/* USER CODE BEGIN Init */
|
||
ST7735_Init(); // 初始化ST7735显示屏
|
||
/* USER CODE END Init */
|
||
|
||
/* USER CODE BEGIN RTOS_MUTEX */
|
||
/* add mutexes, ... */
|
||
/* USER CODE END RTOS_MUTEX */
|
||
|
||
/* USER CODE BEGIN RTOS_SEMAPHORES */
|
||
/* add semaphores, ... */
|
||
/* USER CODE END RTOS_SEMAPHORES */
|
||
|
||
/* USER CODE BEGIN RTOS_TIMERS */
|
||
/* start timers, add new ones, ... */
|
||
/* USER CODE END RTOS_TIMERS */
|
||
|
||
/* USER CODE BEGIN RTOS_QUEUES */
|
||
/* add queues, ... */
|
||
/* USER CODE END RTOS_QUEUES */
|
||
|
||
/* Create the thread(s) */
|
||
/* creation of defaultTask */
|
||
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
|
||
|
||
/* creation of wifi_mqtt */
|
||
wifi_mqttHandle = osThreadNew(wifi_task_mqtt, NULL, &wifi_mqtt_attributes);
|
||
|
||
/* creation of LCD_SHOW_Task */
|
||
LCD_SHOW_TaskHandle = osThreadNew(LCD_Task, NULL, &LCD_SHOW_Task_attributes);
|
||
|
||
/* creation of button */
|
||
buttonHandle = osThreadNew(button_task, NULL, &button_attributes);
|
||
|
||
/* USER CODE BEGIN RTOS_THREADS */
|
||
/* add threads, ... */
|
||
/* USER CODE END RTOS_THREADS */
|
||
|
||
/* USER CODE BEGIN RTOS_EVENTS */
|
||
/* add events, ... */
|
||
/* USER CODE END RTOS_EVENTS */
|
||
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_StartDefaultTask */
|
||
/**
|
||
* @brief Function implementing the defaultTask thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_StartDefaultTask */
|
||
void StartDefaultTask(void *argument)
|
||
{
|
||
/* USER CODE BEGIN StartDefaultTask */
|
||
// 1. 打开运行灯
|
||
Device_Control(DEVICE_LED_RUN, 1);
|
||
|
||
// 2. 初始化日志和屏幕(在 RTOS 任务中)
|
||
easylogger_init();
|
||
|
||
// 初始化MP3模块
|
||
HAL_StatusTypeDef ret = MP3_Init();
|
||
if (ret != HAL_OK) {
|
||
elog_e("MP3", "MP3模块初始化失败");
|
||
return;
|
||
}
|
||
|
||
elog_i("MP3", "模块初始化完成");
|
||
MP3_Play(SYS_POWER_ON); // 播放系统启动音
|
||
|
||
/* Infinite loop */
|
||
for (;;) {
|
||
|
||
Device_Control(DEVICE_LED_RUN, 1); // 打开运行灯
|
||
osDelay(1000);
|
||
Device_Control(DEVICE_LED_RUN, 0); // 关闭运行灯
|
||
osDelay(1000);
|
||
}
|
||
/* USER CODE END StartDefaultTask */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_LCD_Task */
|
||
/**
|
||
* @brief Function implementing the LCD_SHOW_Task thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_LCD_Task */
|
||
void LCD_Task(void *argument)
|
||
{
|
||
/* USER CODE BEGIN LCD_Task */
|
||
|
||
char display_str[32];
|
||
SNTP_Time_t now;
|
||
uint16_t text_color = ST7735_WHITE;
|
||
uint16_t bg_color = ST7735_BLACK;
|
||
|
||
// 显示区域参数 - 适配160x80横屏
|
||
// 宽度160px,高度80px,使用较小字体确保内容完整显示
|
||
|
||
// 初始化传感器数据 - 全部设置为NC状态
|
||
sensor_data.temperature = 0.0f; // NC - 未检测到温度
|
||
sensor_data.humidity = 0.0f; // NC - 未检测到湿度
|
||
sensor_data.food_weight = 0.0f; // NC - 未检测到重量
|
||
sensor_data.water_level = 0; // NC - 未检测到水
|
||
sensor_data.system_mode = 1; // 自动模式
|
||
|
||
/* Infinite loop */
|
||
for (;;) {
|
||
// 根据当前页面显示不同内容
|
||
switch (current_page) {
|
||
|
||
case LCD_PAGE_TIME:
|
||
// 时间显示页面
|
||
ST7735_FillScreen(bg_color);
|
||
ST7735_WriteString(2, 2, "Time", &Font_7x10, text_color, bg_color);
|
||
|
||
if (WIFI_Is_Time_Valid()) {
|
||
WIFI_Get_SNTP_Time();
|
||
now = WIFI_Get_Current_Time();
|
||
if (now.valid) {
|
||
// 显示时间(大字体,只显示时:分,不显示秒)
|
||
snprintf(display_str, sizeof(display_str), "%02d:%02d", now.hour, now.minute);
|
||
ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color);
|
||
|
||
// 显示年月日(小字体,向下移动并居中)
|
||
snprintf(display_str, sizeof(display_str), "%04d-%02d-%02d", now.year,
|
||
now.month, now.day);
|
||
ST7735_WriteString(20, 60, display_str, &Font_7x10, ST7735_CYAN, bg_color);
|
||
} else {
|
||
ST7735_WriteString(25, 20, "--:--", &Font_16x26, text_color, bg_color);
|
||
ST7735_WriteString(20, 60, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color);
|
||
}
|
||
} else {
|
||
ST7735_WriteString(25, 20, "--:--", &Font_16x26, text_color, bg_color);
|
||
ST7735_WriteString(20, 60, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color);
|
||
}
|
||
break;
|
||
|
||
case LCD_PAGE_TEMP_HUMI:
|
||
// 温湿度页面
|
||
ST7735_FillScreen(bg_color);
|
||
ST7735_WriteString(2, 2, "Temp & Humi", &Font_7x10, text_color, bg_color);
|
||
|
||
// 显示温度
|
||
snprintf(display_str, sizeof(display_str), "T: %.1f C", sensor_data.temperature);
|
||
ST7735_WriteString(5, 20, display_str, &Font_11x18, text_color, bg_color);
|
||
|
||
// 显示湿度
|
||
snprintf(display_str, sizeof(display_str), "H: %.1f %%", sensor_data.humidity);
|
||
ST7735_WriteString(5, 45, display_str, &Font_11x18, text_color, bg_color);
|
||
break;
|
||
|
||
case LCD_PAGE_FOOD_WEIGHT:
|
||
// 食物重量页面
|
||
ST7735_FillScreen(bg_color);
|
||
ST7735_WriteString(2, 2, "Food Weight", &Font_7x10, text_color, bg_color);
|
||
|
||
// 显示食物重量
|
||
snprintf(display_str, sizeof(display_str), "%.1f g", sensor_data.food_weight);
|
||
ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color);
|
||
|
||
// 显示状态
|
||
if (sensor_data.food_weight < 50.0f) {
|
||
ST7735_WriteString(20, 55, "Low", &Font_11x18, ST7735_RED, bg_color);
|
||
} else if (sensor_data.food_weight < 100.0f) {
|
||
ST7735_WriteString(15, 55, "Medium", &Font_11x18, ST7735_YELLOW, bg_color);
|
||
} else {
|
||
ST7735_WriteString(20, 55, "Good", &Font_11x18, ST7735_GREEN, bg_color);
|
||
}
|
||
break;
|
||
|
||
case LCD_PAGE_WATER_LEVEL:
|
||
// 水位页面
|
||
ST7735_FillScreen(bg_color);
|
||
ST7735_WriteString(2, 2, "Water Level", &Font_7x10, text_color, bg_color);
|
||
|
||
// 根据水位传感器状态显示
|
||
if (sensor_data.water_level == 0) {
|
||
ST7735_WriteString(20, 20, "NO WATER", &Font_16x26, ST7735_RED, bg_color);
|
||
ST7735_WriteString(35, 55, "Add water", &Font_11x18, ST7735_YELLOW, bg_color);
|
||
} else {
|
||
ST7735_WriteString(50, 25, "OK", &Font_16x26, ST7735_GREEN, bg_color);
|
||
ST7735_WriteString(35, 55, "Water OK", &Font_11x18, text_color, bg_color);
|
||
}
|
||
break;
|
||
|
||
case LCD_PAGE_SYSTEM_STATUS:
|
||
// 系统状态页面
|
||
ST7735_FillScreen(bg_color);
|
||
ST7735_WriteString(2, 2, "System Status", &Font_7x10, text_color, bg_color);
|
||
|
||
// 显示WiFi状态
|
||
if (WIFI_Is_Time_Valid()) {
|
||
ST7735_WriteString(5, 20, "WiFi: OK", &Font_11x18, ST7735_GREEN, bg_color);
|
||
} else {
|
||
ST7735_WriteString(5, 20, "WiFi: OFF", &Font_11x18, ST7735_RED, bg_color);
|
||
}
|
||
|
||
// 显示系统模式
|
||
if (sensor_data.system_mode) {
|
||
ST7735_WriteString(5, 45, "Mode: AUTO", &Font_11x18, text_color, bg_color);
|
||
} else {
|
||
ST7735_WriteString(5, 45, "Mode: MANUAL", &Font_11x18, text_color, bg_color);
|
||
}
|
||
break;
|
||
}
|
||
|
||
// 记录当前页面
|
||
LCD_Page_t displayed_page = current_page;
|
||
|
||
// 根据当前页面设置不同的刷新间隔
|
||
if (current_page == LCD_PAGE_TIME || current_page == LCD_PAGE_SYSTEM_STATUS) {
|
||
// 时间页面和系统状态页面:30秒刷新一次,但每100ms检查一次是否需要切换页面
|
||
for (int i = 0; i < 300; i++) {
|
||
if (current_page != displayed_page) {
|
||
break; // 检测到页面切换,立即跳出
|
||
}
|
||
osDelay(100); // 每100ms检查一次,总共30秒
|
||
}
|
||
} else {
|
||
// 其他页面:1秒刷新一次
|
||
osDelay(1000);
|
||
}
|
||
}
|
||
/* USER CODE END LCD_Task */
|
||
}
|
||
|
||
/* USER CODE BEGIN Header_button_task */
|
||
/**
|
||
* @brief Function implementing the button thread.
|
||
* @param argument: Not used
|
||
* @retval None
|
||
*/
|
||
/* USER CODE END Header_button_task */
|
||
void button_task(void *argument)
|
||
{
|
||
/* USER CODE BEGIN button_task */
|
||
user_button_init();
|
||
|
||
/* Infinite loop */
|
||
for(;;)
|
||
{
|
||
button_ticks();
|
||
osDelay(5);
|
||
}
|
||
/* USER CODE END button_task */
|
||
}
|
||
|
||
/* Private application code --------------------------------------------------*/
|
||
/* USER CODE BEGIN Application */
|
||
|
||
// 按键库实现部分//
|
||
|
||
/* USER CODE BEGIN KEY Prototypes */
|
||
|
||
static Button KEY1; // 按键1
|
||
static Button KEY2; // 按键2
|
||
static Button KEY3; // 按键3
|
||
static Button KEY4; // 按键4
|
||
|
||
uint8_t read_button_gpio(uint8_t button_id) {
|
||
switch (button_id) {
|
||
case 1:
|
||
return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
|
||
break;
|
||
case 2:
|
||
return HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin);
|
||
break;
|
||
case 3:
|
||
return HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin);
|
||
break;
|
||
case 4:
|
||
return HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin);
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
void key1_single_click_handler(Button *btn) { elog_i("KEY", "按键1单击"); }
|
||
|
||
void key2_single_click_handler(Button *btn) { elog_i("KEY", "按键2单击"); }
|
||
|
||
void key3_single_click_handler(Button *btn) {
|
||
elog_i("KEY", "按键3单击");
|
||
LCD_NextPage();
|
||
}
|
||
|
||
void key4_single_click_handler(Button *btn) { elog_i("KEY", "按键4单击"); }
|
||
|
||
void user_button_init(void) {
|
||
// 初始化按键 (active_level: 0=低电平有效, 1=高电平有效)
|
||
button_init(&KEY1, read_button_gpio, 0, 1);
|
||
button_init(&KEY2, read_button_gpio, 0, 2);
|
||
button_init(&KEY3, read_button_gpio, 0, 3);
|
||
button_init(&KEY4, read_button_gpio, 0, 4);
|
||
|
||
elog_i("BUTTON", "按键初始化完成");
|
||
|
||
// 设置按键回调函数
|
||
button_attach(&KEY1, BTN_SINGLE_CLICK, key1_single_click_handler);
|
||
button_attach(&KEY2, BTN_SINGLE_CLICK, key2_single_click_handler);
|
||
button_attach(&KEY3, BTN_SINGLE_CLICK, key3_single_click_handler);
|
||
button_attach(&KEY4, BTN_SINGLE_CLICK, key4_single_click_handler);
|
||
|
||
elog_i("BUTTON", "按键回调函数设置完成");
|
||
|
||
// 启动按键任务
|
||
button_start(&KEY1);
|
||
button_start(&KEY2);
|
||
button_start(&KEY3);
|
||
button_start(&KEY4);
|
||
|
||
elog_i("BUTTON", "按键任务已启动");
|
||
}
|
||
/* USER CODE END KEY Prototypes */
|
||
|
||
/* USER CODE BEGIN LCD_Page_Functions */
|
||
/**
|
||
* @brief 切换到下一个显示页面
|
||
* @retval None
|
||
*/
|
||
void LCD_NextPage(void) {
|
||
current_page = (current_page + 1) % 5; // 循环切换5个页面
|
||
elog_i("LCD", "切换到页面 %d", current_page + 1);
|
||
|
||
// 播放页面切换提示音
|
||
MP3_Play(PARAM_SAVE_OK); // 使用参数保存成功的音效作为页面切换提示
|
||
}
|
||
|
||
/**
|
||
* @brief 切换到上一个显示页面
|
||
* @retval None
|
||
*/
|
||
void LCD_PrevPage(void) {
|
||
if (current_page == 0) {
|
||
current_page = 4;
|
||
} else {
|
||
current_page--;
|
||
}
|
||
elog_i("LCD", "切换到页面 %d", current_page + 1);
|
||
|
||
// 播放页面切换提示音
|
||
MP3_Play(PARAM_SAVE_OK);
|
||
}
|
||
|
||
/**
|
||
* @brief 设置当前显示页面
|
||
* @param page: 目标页面索引 (0-4)
|
||
* @retval None
|
||
*/
|
||
void LCD_SetPage(LCD_Page_t page) {
|
||
if (page < 5) {
|
||
current_page = page;
|
||
elog_i("LCD", "设置页面为 %d", page + 1);
|
||
MP3_Play(PARAM_SAVE_OK);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前页面索引
|
||
* @retval 当前页面索引
|
||
*/
|
||
LCD_Page_t LCD_GetCurrentPage(void) { return current_page; }
|
||
|
||
/**
|
||
* @brief 更新传感器数据
|
||
* @param temp: 温度值
|
||
* @param humi: 湿度值
|
||
* @param weight: 食物重量
|
||
* @param water: 水位状态 (0=无水, 1=有水)
|
||
* @param mode: 系统模式
|
||
* @retval None
|
||
*/
|
||
void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
|
||
uint8_t mode) {
|
||
sensor_data.temperature = temp;
|
||
sensor_data.humidity = humi;
|
||
sensor_data.food_weight = weight;
|
||
sensor_data.water_level = water;
|
||
sensor_data.system_mode = mode;
|
||
|
||
elog_i("LCD", "传感器数据更新: T=%.1fC H=%.1f%% W=%.1fg Water=%s Mode=%s",
|
||
temp, humi, weight, water ? "DETECTED" : "NONE",
|
||
mode ? "AUTO" : "MANUAL");
|
||
}
|
||
|
||
/**
|
||
* @brief 获取传感器数据指针
|
||
* @retval 传感器数据结构体指针
|
||
*/
|
||
Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; }
|
||
|
||
/* USER CODE END LCD_Page_Functions */
|
||
|
||
/* USER CODE END Application */
|
||
|