/* 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 "cmsis_os.h" #include "cmsis_os2.h" #include "main.h" #include "task.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "bsp_aht30.h" #include "bsp_hx711.h" #include "bsp_rtc.h" #include "device_ctrl.h" #include "dx_wf_24.h" #include "elog.h" #include "i2c.h" #include "mp3_driver.h" #include "mp3_play_index.h" #include "multi_button.h" #include "spi_st7735s.h" #include "stdio.h" #include "stepper_motor.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:长按设置时间 /***********************人体存在和水位检测作为两个单独的按键(GPIO输入)******************* */ #define KEY_WATER_LEVEL // 水位检测 #define KEY_BODY_EXIST // 人体存在 /* 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; // 喂食控制命令枚举 typedef enum { FEED_CMD_NONE = 0, // 无命令 FEED_CMD_MANUAL, // 手动喂食 FEED_CMD_AUTO, // 自动喂食 FEED_CMD_REMOTE, // 远程喂食 FEED_CMD_TEST // 测试喂食 } Feed_Cmd_t; // 全局变量 static LCD_Page_t current_page = LCD_PAGE_TIME; // 当前显示页面 static Sensor_Data_t sensor_data = {0}; // 传感器数据 static volatile uint8_t lcd_force_refresh = 0; // 强制刷新标志(RTC更新时设置) static volatile uint8_t system_mode = 1; // 系统模式:1=自动模式,0=手动模式(全局变量) static volatile uint8_t feeding_in_progress = 0; // 喂食进行中标志 // 喂食控制相关全局变量 static volatile Feed_Cmd_t feed_command = FEED_CMD_NONE; // 喂食命令标志 static volatile uint16_t feed_angle = 90; // 喂食角度(默认90度) static volatile uint8_t feed_amount = 1; // 喂食份数 /* 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, }; /* Definitions for sensor */ osThreadId_t sensorHandle; const osThreadAttr_t sensor_attributes = { .name = "sensor", .stack_size = 1024 * 4, .priority = (osPriority_t)osPriorityNormal, }; /* Definitions for step_motor */ osThreadId_t step_motorHandle; const osThreadAttr_t step_motor_attributes = { .name = "step_motor", .stack_size = 512 * 4, .priority = (osPriority_t)osPriorityNormal, }; /* 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); void RTC_TimeUpdateCallback(void); // RTC时间更新回调函数 /* 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 sensorTask(void *argument); void step_motor_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); /* creation of sensor */ sensorHandle = osThreadNew(sensorTask, NULL, &sensor_attributes); /* creation of step_motor */ step_motorHandle = osThreadNew(step_motor_task, NULL, &step_motor_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(); // 3. 初始化RTC管理模块(在日志初始化后) BSP_RTC_Init(); // 初始化MP3模块 HAL_StatusTypeDef ret = MP3_Init(); if (ret != HAL_OK) { elog_e("MP3", "MP3模块初始化失败"); return; } // 设置为低音量-测试用 MP3_SetVolume(20); 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]; RTC_Time_t rtc_time; 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; // 自动模式 // 注册RTC时间更新回调(SNTP同步后立即刷新LCD) BSP_RTC_RegisterCallback(RTC_TimeUpdateCallback); /* Infinite loop */ for (;;) { // 根据当前页面显示不同内容 switch (current_page) { case LCD_PAGE_TIME: // 时间显示页面 ST7735_FillScreen(bg_color); // 绘制标题栏 ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_BLUE); ST7735_WriteString(5, 2, "Time", &Font_7x10, ST7735_WHITE, ST7735_BLUE); // 使用RTC时间(独立于SNTP,即使网络断开也能显示) if (BSP_RTC_GetTime(&rtc_time) == 0) { // 显示时间(大字体,只显示时:分,不显示秒) snprintf(display_str, sizeof(display_str), "%02d:%02d", rtc_time.hour, rtc_time.minute); ST7735_WriteString(40, 30, display_str, &Font_16x26, ST7735_WHITE, bg_color); // 显示年月日(小字体,向下移动并居中) snprintf(display_str, sizeof(display_str), "%04d-%02d-%02d", rtc_time.year, rtc_time.month, rtc_time.day); ST7735_WriteString(30, 65, display_str, &Font_7x10, ST7735_CYAN, bg_color); } else { ST7735_WriteString(40, 30, "--:--", &Font_16x26, ST7735_WHITE, bg_color); ST7735_WriteString(30, 65, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color); } break; case LCD_PAGE_TEMP_HUMI: // 温湿度页面 ST7735_FillScreen(bg_color); // 绘制标题栏 ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_GREEN); ST7735_WriteString(5, 2, "Temp & Humi", &Font_7x10, ST7735_WHITE, ST7735_GREEN); // 绘制分隔线 ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示温度(橙色 - 国际标准温度色) snprintf(display_str, sizeof(display_str), "Temp:"); ST7735_WriteString(5, 25, display_str, &Font_7x10, ST7735_WHITE, bg_color); snprintf(display_str, sizeof(display_str), "%.3fC", sensor_data.temperature); ST7735_WriteString(40, 25, display_str, &Font_16x26, ST7735_ORANGE, bg_color); // 显示湿度(青色 - 国际标准湿度色) snprintf(display_str, sizeof(display_str), "Humi:"); ST7735_WriteString(5, 55, display_str, &Font_7x10, ST7735_WHITE, bg_color); snprintf(display_str, sizeof(display_str), "%.3f%%", sensor_data.humidity); ST7735_WriteString(40, 55, display_str, &Font_16x26, ST7735_CYAN, bg_color); break; case LCD_PAGE_FOOD_WEIGHT: // 食物重量页面 ST7735_FillScreen(bg_color); // 绘制标题栏 ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_YELLOW); ST7735_WriteString(5, 2, "Food Weight", &Font_7x10, ST7735_BLACK, ST7735_YELLOW); // 绘制分隔线 ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示食物重量 snprintf(display_str, sizeof(display_str), "%.2f g", sensor_data.food_weight); ST7735_WriteString(15, 25, display_str, &Font_16x26, ST7735_WHITE, bg_color); // 显示状态 if (sensor_data.food_weight < 50.0f) { ST7735_WriteString(20, 65, "LOW", &Font_11x18, ST7735_RED, bg_color); } else if (sensor_data.food_weight < 100.0f) { ST7735_WriteString(15, 65, "MEDIUM", &Font_11x18, ST7735_YELLOW, bg_color); } else { ST7735_WriteString(20, 65, "GOOD", &Font_11x18, ST7735_GREEN, bg_color); } break; case LCD_PAGE_WATER_LEVEL: // 水位页面 ST7735_FillScreen(bg_color); // 绘制标题栏 ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_CYAN); ST7735_WriteString(5, 2, "Water Level", &Font_7x10, ST7735_BLACK, ST7735_CYAN); // 绘制分隔线 ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 根据水位传感器状态显示 if (sensor_data.water_level == 0) { // 绘制警告背景 ST7735_FillRectangle(0, 20, ST7735_WIDTH, 30, ST7735_RED); ST7735_WriteString(20, 20, "NO WATER", &Font_16x26, ST7735_WHITE, ST7735_RED); ST7735_WriteString(10, 60, "Add water", &Font_16x26, ST7735_YELLOW, bg_color); } else { // 绘制正常背景 ST7735_FillRectangle(0, 20, ST7735_WIDTH, 30, ST7735_GREEN); ST7735_WriteString(5, 20, "WATER OK", &Font_16x26, ST7735_WHITE, ST7735_GREEN); ST7735_WriteString(10, 60, "Not add", &Font_16x26, ST7735_WHITE, bg_color); } break; case LCD_PAGE_SYSTEM_STATUS: // 系统状态页面 ST7735_FillScreen(bg_color); // 绘制标题栏 ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_MAGENTA); ST7735_WriteString(5, 2, "System", &Font_7x10, ST7735_WHITE, ST7735_MAGENTA); // 绘制分隔线 ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示MQTT连接状态 snprintf(display_str, sizeof(display_str), "MQTT:"); ST7735_WriteString(5, 25, display_str, &Font_7x10, ST7735_WHITE, bg_color); if (WIFI_Is_MQTT_Connected()) { ST7735_WriteString(40, 25, "OK", &Font_7x10, ST7735_GREEN, bg_color); } else { ST7735_WriteString(40, 25, "OFF", &Font_7x10, ST7735_RED, bg_color); } // 显示系统模式 snprintf(display_str, sizeof(display_str), "Mode:"); ST7735_WriteString(5, 45, display_str, &Font_7x10, ST7735_WHITE, bg_color); if (sensor_data.system_mode) { ST7735_WriteString(40, 45, "AUTO", &Font_7x10, ST7735_GREEN, bg_color); } else { ST7735_WriteString(40, 45, "MANUAL", &Font_7x10, ST7735_YELLOW, bg_color); } // 显示喂食状态 snprintf(display_str, sizeof(display_str), "Feed:"); ST7735_WriteString(5, 65, display_str, &Font_7x10, ST7735_WHITE, bg_color); if (feeding_in_progress) { ST7735_WriteString(40, 65, "Busy", &Font_7x10, ST7735_ORANGE, bg_color); } else { ST7735_WriteString(40, 65, "Idle", &Font_7x10, ST7735_WHITE, 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; // 检测到页面切换,立即跳出 } if (lcd_force_refresh) { // RTC时间更新,立即刷新LCD lcd_force_refresh = 0; break; } osDelay(100); // 每100ms检查一次,总共30秒 } } else { // 其他页面:1秒刷新一次 osDelay(2000); } } /* 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 */ } /* USER CODE BEGIN Header_sensorTask */ /** * @brief Function implementing the sensor thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_sensorTask */ void sensorTask(void *argument) { /* USER CODE BEGIN sensorTask */ elog_i(TAG, "启动传感器任务"); // #################### 校准流程(首次使用必须执行!)#################### HAL_Delay(1000); // 上电稳定 elog_i(TAG, "开始校准零点..."); HX711_CalibrateZero(); // 空载时调用(传感器上不放任何东西) elog_i(TAG, "零点校准完成!零点AD值:%d", hx711_zero_offset); HAL_Delay(1000); // elog_i(TAG, "请在传感器上放置已知重量的物体,3秒后开始标定..."); // HAL_Delay(3000); // HX711_CalibrateScale(350.0f); // 放置100g的物体(根据实际重量修改) elog_i(TAG, "量程标定完成!标定系数:%.6f", hx711_scale); // #################### 循环读取重量 #################### // 扫描I2C设备(调试) elog_i(TAG, "扫描I2C设备"); AHT30_ScanI2C(&hi2c1); // 如果硬件I2C扫描失败,尝试软件I2C uint8_t found = 0; for (uint8_t addr = 1; addr < 128; addr++) { if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 1, 50) == HAL_OK) { found = 1; break; } } if (!found) { elog_w(TAG, "硬件I2C未发现设备,尝试软件I2C扫描..."); AHT30_ScanI2C_Soft(); } // 初始化AHT30温湿度传感器 elog_i(TAG, "初始化AHT30温湿度传感器"); if (AHT30_Init(&hi2c1)) { elog_i(TAG, "AHT30初始化成功"); } else { elog_e(TAG, "AHT30初始化失败"); } /* Infinite loop */ for (;;) { // 读取温湿度数据 AHT30_Data_t aht30_data; if (AHT30_ReadData(&aht30_data)) { // 数据读取成功,更新sensor_data sensor_data.temperature = aht30_data.temperature; sensor_data.humidity = aht30_data.humidity; elog_d(TAG, "温湿度数据 - 温度: %.2f℃, 湿度: %.2f%%", aht30_data.temperature, aht30_data.humidity); } else { elog_w(TAG, "AHT30数据读取失败"); } sensor_data.food_weight= HX711_GetWeight(10); // 采样10次取平均 elog_d(TAG, "当前重量:%.2f g", sensor_data.food_weight); // 每2秒读取一次 osDelay(2000); } /* USER CODE END sensorTask */ } /** * @brief 请求喂食操作 * @param cmd: 喂食命令类型 * @param angle: 转动角度 * @param amount: 喂食份数 * @retval 1=请求成功, 0=请求失败(正在喂食中) */ uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) { if (feeding_in_progress) { elog_w("FEED", "喂食进行中,无法接受新命令"); return 0; } feed_command = cmd; feed_angle = angle; feed_amount = amount; elog_i("FEED", "喂食请求已提交: cmd=%d, angle=%d, amount=%d", cmd, angle, amount); return 1; } /** * @brief 获取当前喂食命令 * @retval 当前喂食命令 */ Feed_Cmd_t Get_Feed_Command(void) { return feed_command; } /** * @brief 清除喂食命令 */ void Clear_Feed_Command(void) { feed_command = FEED_CMD_NONE; } /** * @brief 执行喂食操作 * @param cmd: 喂食命令类型 * @param angle: 转动角度 * @param amount: 喂食份数 */ static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) { if (feeding_in_progress) { return; // 防止重复执行 } feeding_in_progress = 1; // 根据命令类型播放不同音频 switch (cmd) { case FEED_CMD_MANUAL: MP3_Play(FEED_MANUAL_TRIGGER); elog_i("FEED", "执行手动喂食: 角度%d度, %d份", angle, amount); break; case FEED_CMD_AUTO: MP3_Play(FEED_AUTO_START); elog_i("FEED", "执行自动喂食: 角度%d度, %d份", angle, amount); break; case FEED_CMD_REMOTE: MP3_Play(REMOTE_CMD_RECEIVED); elog_i("FEED", "执行远程喂食: 角度%d度, %d份", angle, amount); break; case FEED_CMD_TEST: elog_i("FEED", "执行测试喂食: 角度%d度", angle); break; default: break; } // 执行实际的喂食动作 for (uint8_t i = 0; i < amount; i++) { Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP); if (i < amount - 1) { osDelay(1000); // 多份之间间隔1秒 } } // 等待食物落下 osDelay(3000); // 停止电机 Stepper_Motor_Stop(); // 播放完成音效 MP3_Play(FEED_COMPLETE); elog_i("FEED", "喂食完成"); feeding_in_progress = 0; } /* USER CODE BEGIN Header_step_motor_task */ /** * @brief Function implementing the step_motor thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_step_motor_task */ void step_motor_task(void *argument) { /* USER CODE BEGIN step_motor_task */ // 初始化步进电机 Stepper_Motor_Init(); /* Infinite loop */ for (;;) { // 检查是否有喂食命令需要执行 if (feed_command != FEED_CMD_NONE && !feeding_in_progress) { Feed_Cmd_t current_cmd = feed_command; uint16_t current_angle = feed_angle; uint8_t current_amount = feed_amount; // 清除命令标志 Clear_Feed_Command(); // 执行喂食 Execute_Feed(current_cmd, current_angle, current_amount); } osDelay(100); // 短暂延时,保持任务响应性 } /* USER CODE END step_motor_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 static Button M3_IO; // M3 IO static Button HC_SR505; // HC-SR505 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; case 5: return HAL_GPIO_ReadPin(M3_IO_GPIO_Port, M3_IO_Pin); break; case 6: return HAL_GPIO_ReadPin(HC_SR505_IO_GPIO_Port, HC_SR505_IO_Pin); break; default: return 0; } } /** * @brief 按键1处理函数:模式切换(自动/手动) * @param btn: 按键句柄 */ void key1_single_click_handler(Button *btn) { // 切换系统模式 system_mode = !system_mode; // 0和1之间切换 // 更新传感器数据中的模式状态 sensor_data.system_mode = system_mode; // 记录日志 elog_i("KEY", "按键1单击 - 模式切换: %s", system_mode ? "自动模式" : "手动模式"); // 播放模式切换音效 MP3_Play(system_mode ? MODE_AUTO : MODE_MANUAL); // 强制刷新LCD显示以更新模式状态 lcd_force_refresh = 1; } /** * @brief 按键2处理函数:手动喂食(仅手动模式有效) * @param btn: 按键句柄 */ void key2_single_click_handler(Button *btn) { // 检查是否为手动模式 if (!system_mode) { // 手动模式下才允许手动喂食 if (Request_Feed(FEED_CMD_MANUAL, 90, 1)) { elog_i("KEY", "按键2单击 - 手动喂食请求已提交"); } else { elog_w("KEY", "喂食进行中,请稍后再试"); MP3_Play(SYS_ERROR_ALARM); } } else { // 自动模式下按键无效 elog_w("KEY", "当前为自动模式,按键2无效"); MP3_Play(SYS_ERROR_ALARM); } } 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 M3_long_press_start_handler(Button *btn) { elog_i("KEY", "M3水位传感器检测到有水(低电平)"); sensor_data.water_level = 1; } void M3_press_up_handler(Button *btn) { elog_i("KEY", "M3水位传感器检测到无水(高电平)"); sensor_data.water_level = 0; } void HC_SR055_long_click_handler(Button *btn) { elog_i("KEY", "HC-SR505触发"); } 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); button_init(&M3_IO, read_button_gpio, 0, 5); button_init(&HC_SR505, read_button_gpio, 1, 6); 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); button_attach(&M3_IO, BTN_LONG_PRESS_START, M3_long_press_start_handler); button_attach(&M3_IO, BTN_PRESS_UP, M3_press_up_handler); button_attach(&HC_SR505, BTN_LONG_PRESS_START, HC_SR055_long_click_handler); // 长按触发 elog_i("BUTTON", "按键回调函数设置完成"); // 启动按键任务 button_start(&KEY1); button_start(&KEY2); button_start(&KEY3); button_start(&KEY4); button_start(&M3_IO); button_start(&HC_SR505); elog_i("BUTTON", "按键任务已启动"); } /** * @brief 获取当前系统模式 * @retval 系统模式 (0=手动, 1=自动) */ uint8_t Get_System_Mode(void) { return system_mode; } /** * @brief 设置系统模式 * @param mode: 目标模式 (0=手动, 1=自动) */ void Set_System_Mode(uint8_t mode) { if (mode <= 1) { system_mode = mode; sensor_data.system_mode = mode; lcd_force_refresh = 1; // 强制刷新显示 elog_i("SYSTEM", "系统模式设置为: %s", mode ? "自动" : "手动"); } } /** * @brief 检查是否正在喂食 * @retval 1=正在喂食, 0=空闲 */ uint8_t Is_Feeding_In_Progress(void) { return feeding_in_progress; } /* 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; } /** * @brief RTC时间更新回调函数 * @note 当RTC通过SNTP更新时间时调用,立即刷新LCD显示 */ void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; } /* USER CODE END LCD_Page_Functions */ /* USER CODE END Application */