Files
SmartPetFeeder_STM32/Core/Src/freertos.c
wangbeihong 1fc85589c6 ## 主要修改
### 1. MQTT setMode命令处理修复
- 添加Set_System_Mode函数extern声明,解决编译错误
- 将setMode处理逻辑移到control主题,符合小程序规范
- 完善Set_System_Mode函数,添加音频播放功能

### 2. 按键4手动补水功能
- 实现key4_single_click_handler函数,添加手动补水逻辑
- 与按键2喂食逻辑保持一致:相同的模式检查和错误处理
- 提供完整的本地手动控制功能

### 3. 系统优化
- 统一本地和远程控制逻辑
- 完善错误处理和用户反馈
- 优化代码结构和日志记录

## 影响
- 系统现在支持完整的双重控制方式(本地按键+远程MQTT)
- 所有按键功能完善:模式切换、手动喂食、页面切换、手动补水
- 编译无错误,代码结构清晰,便于维护
2026-02-25 22:48:12 +08:00

1174 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 "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; // 喂食份数
// 加水控制命令枚举
typedef enum {
WATER_CMD_NONE = 0, // 无命令
WATER_CMD_MANUAL, // 手动加水
WATER_CMD_AUTO, // 自动加水
WATER_CMD_REMOTE // 远程加水
} Water_Cmd_t;
// 加水控制相关全局变量
static volatile Water_Cmd_t water_command = WATER_CMD_NONE; // 加水命令标志
static volatile uint8_t watering_in_progress = 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,
};
/* 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,
};
/* Definitions for water_control */
osThreadId_t water_controlHandle;
const osThreadAttr_t water_control_attributes = {
.name = "water_control",
.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时间更新回调函数
uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buffer_size);
static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) ;
void Clear_Feed_Command(void);
uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount);
uint8_t Request_Water(Water_Cmd_t cmd);
void Clear_Water_Command(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 sensorTask(void *argument);
void step_motor_task(void *argument);
void water_controlTask(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);
/* creation of water_control */
water_controlHandle = osThreadNew(water_controlTask, NULL, &water_control_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);
// ========== 发布传感器数据到MQTT ==========
char json_buffer[128];
if (Build_Sensor_JSON(&sensor_data, json_buffer, sizeof(json_buffer))) {
if (WIFI_MQTT_Publish_Sensor(json_buffer)) {
elog_i(TAG, "传感器数据发布成功: %s", json_buffer);
} else {
elog_e(TAG, "传感器数据发布失败");
}
} else {
elog_e(TAG, "构建JSON数据失败");
}
// ========== 发布结束 ===============
// 每2秒读取一次
osDelay(2000);
}
/* USER CODE END sensorTask */
}
/* 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 */
}
/* USER CODE BEGIN Header_water_controlTask */
/**
* @brief Function implementing the water_control thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_water_controlTask */
void water_controlTask(void *argument)
{
/* USER CODE BEGIN water_controlTask */
elog_i("WATER", "启动加水控制任务");
/* Infinite loop */
for(;;)
{
// 检查是否有加水命令需要执行
if (water_command != WATER_CMD_NONE && !watering_in_progress) {
Water_Cmd_t current_cmd = water_command;
// 清除命令标志
Clear_Water_Command();
// 执行加水
watering_in_progress = 1;
// 根据命令类型播放不同音频
switch (current_cmd) {
case WATER_CMD_MANUAL:
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "执行手动加水");
break;
case WATER_CMD_AUTO:
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "执行自动加水");
break;
case WATER_CMD_REMOTE:
MP3_Play(REMOTE_CMD_RECEIVED);
elog_i("WATER", "执行远程加水");
break;
default:
break;
}
// 打开水泵继电器假设DEVICE_RELAY控制水泵
Device_Control(DEVICE_RELAY, 1);
elog_i("WATER", "水泵启动,等待水位传感器检测到有水");
// 加水超时保护最大30秒
uint32_t start_time = osKernelGetTickCount();
const uint32_t MAX_WATERING_TIME = 30000; // 30秒超时
// 循环检查水位传感器,直到检测到有水或超时
while (1) {
// 检查是否检测到有水
if (sensor_data.water_level == 1) {
elog_i("WATER", "水位传感器检测到有水,停止加水");
break;
}
// 检查是否超时
if (osKernelGetTickCount() - start_time > MAX_WATERING_TIME) {
elog_w("WATER", "加水超时30秒强制停止");
break;
}
// 短暂延时避免占用过多CPU
osDelay(100);
}
// 关闭水泵继电器
Device_Control(DEVICE_RELAY, 0);
elog_i("WATER", "水泵停止");
// 播放完成音效
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "加水完成");
watering_in_progress = 0;
}
osDelay(100); // 短暂延时,保持任务响应性
}
/* USER CODE END water_controlTask */
}
/* 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();
}
/**
* @brief 按键4处理函数手动补水仅手动模式有效
* @param btn: 按键句柄
*/
void key4_single_click_handler(Button *btn) {
// 检查是否为手动模式
if (!system_mode) {
// 手动模式下才允许手动补水
if (Request_Water(WATER_CMD_MANUAL)) {
elog_i("KEY", "按键4单击 - 手动补水请求已提交");
} else {
elog_w("KEY", "补水进行中,请稍后再试");
MP3_Play(SYS_ERROR_ALARM);
}
} else {
// 自动模式下按键无效
elog_w("KEY", "当前为自动模式按键4无效");
MP3_Play(SYS_ERROR_ALARM);
}
}
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; // 强制刷新显示
// 播放模式切换音效与按键1逻辑保持一致
MP3_Play(mode ? MODE_AUTO : MODE_MANUAL);
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; }
/**
* @brief 构建传感器数据JSON字符串符合小程序格式要求
* @param data 传感器数据结构体指针
* @param buffer 输出缓冲区
* @param buffer_size 缓冲区大小
* @return 1表示成功0表示失败参数错误或缓冲区不足
*/
uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buffer_size) {
// 参数检查
if (!data || !buffer || buffer_size < 128) {
return 0;
}
// 将float类型的湿度和重量转换为整数小程序要求整数
int humidity_int = (int)data->humidity;
int food_weight_int = (int)data->food_weight;
// 构建符合小程序要求的JSON字符串
// 注意字段名foodWeightcamelCase而不是food_weightsnake_case
// waterLevelcamelCase而不是water_levelsnake_case
snprintf(buffer, buffer_size,
"{\"temperature\":%.1f,\"humidity\":%d,\"foodWeight\":%d,\"waterLevel\":%d}",
data->temperature, // 温度保留1位小数
humidity_int, // 湿度(整数)
food_weight_int, // 食物重量(整数)
data->water_level); // 水位状态0或1
return 1;
}
/**
* @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: 加水命令类型
* @retval 1=请求成功, 0=请求失败(正在加水中)
*/
uint8_t Request_Water(Water_Cmd_t cmd) {
if (watering_in_progress) {
elog_w("WATER", "加水进行中,无法接受新命令");
return 0;
}
water_command = cmd;
elog_i("WATER", "加水请求已提交: cmd=%d", cmd);
return 1;
}
/**
* @brief 清除加水命令
*/
void Clear_Water_Command(void) { water_command = WATER_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 END LCD_Page_Functions */
/* USER CODE END Application */