不干了!!!收

This commit is contained in:
2026-02-26 14:26:55 +08:00
parent 1fc85589c6
commit d185f63856
7 changed files with 583 additions and 114 deletions

View File

@@ -89,12 +89,21 @@ uint8_t AHT30_Init(I2C_HandleTypeDef *hi2c) {
elog_d(AHT30_TAG, "开始初始化AHT30传感器");
// 等待传感器上电稳定
osDelay(50);
osDelay(100);
// 读取初始状态
// 读取初始状态最多重试3次
uint8_t status;
if (AHT30_ReadBytes(&status, 1)) {
elog_d(AHT30_TAG, "AHT30初始状态: 0x%02X", status);
uint8_t retry = 0;
for (retry = 0; retry < 3; retry++) {
if (AHT30_ReadBytes(&status, 1)) {
elog_d(AHT30_TAG, "AHT30初始状态: 0x%02X", status);
break;
}
osDelay(10); // 重试间隔
}
if (retry >= 3) {
elog_e(AHT30_TAG, "读取AHT30初始状态失败");
return 0;
}
// 检查校准使能位,如果未启用则执行复位校准

View File

@@ -2,7 +2,7 @@
// 全局变量(零点偏移值、标定系数)
int32_t hx711_zero_offset = 0; // 零点偏移空载时的AD值
float hx711_scale = 0.002762; // 标定系数(重量/AD差值
float hx711_scale = 1.0f; // 标定系数(重量/AD差值
// #################### 基础函数 ####################
void delay_us(uint32_t us) {
@@ -13,7 +13,8 @@ void delay_us(uint32_t us) {
}
// 初始化HX711引脚SCK输出DOUT输入
void HX711_Init(void) {
// 初始化SCK引脚为低电平
HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET);
}
// 读取单次AD原始数据gain增益选择
int32_t HX711_ReadData(uint8_t gain) {

View File

@@ -20,6 +20,10 @@ extern uint8_t Request_Water(uint8_t cmd);
// 系统模式设置函数声明定义在freertos.c中
extern void Set_System_Mode(uint8_t mode);
// 定时喂食配置函数声明定义在freertos.c中
extern void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight);
extern uint16_t Get_Single_Feed_Weight(void);
/* ================= 配置 ================= */
#define WIFI_TX_BUF_SIZE 512
@@ -585,8 +589,15 @@ void wifi_task_mqtt(void *argument) {
elog_i(TAG, "处理control主题消息");
if (strstr(msg.payload, "feed")) {
elog_i(TAG, "执行喂食动作");
// 根据配置的单次喂食重量计算份数每份10克
uint16_t single_weight = Get_Single_Feed_Weight();
uint8_t feed_amount = single_weight / 10;
if (feed_amount < 1) {
feed_amount = 1; // 至少喂食1份
}
elog_i(TAG, "远程喂食重量: %dg, 计算份数: %d", single_weight, feed_amount);
// 调用远程喂食函数
if (Request_Feed(FEED_CMD_REMOTE, 90, 1)) {
if (Request_Feed(FEED_CMD_REMOTE, 90, feed_amount)) {
elog_i(TAG, "远程喂食请求已提交");
} else {
elog_w(TAG, "喂食进行中,无法接受新命令");
@@ -644,10 +655,71 @@ void wifi_task_mqtt(void *argument) {
}
/* ===== config主题 ===== */
/**
{
"feedTimes": [
"02:00",
"02:04"
],
"singleFeedWeight": 50
}
*/
else if (strcmp(msg.topic, "petfeeder/config") == 0) {
elog_i(TAG, "处理config主题消息");
elog_i(TAG, "更新配置参数");
// 其他配置参数更新逻辑
// 解析JSON配置
char *feed_times_start = strstr(msg.payload, "\"feedTimes\"");
char *single_weight_start = strstr(msg.payload, "\"singleFeedWeight\"");
if (feed_times_start && single_weight_start) {
// 提取feedTimes数组字符串
char *bracket_start = strstr(feed_times_start, "[");
char *bracket_end = strstr(feed_times_start, "]");
if (bracket_start && bracket_end && bracket_end > bracket_start) {
// 计算数组字符串长度
size_t array_len = bracket_end - bracket_start + 1;
char feed_times_array[256] = {0};
if (array_len < sizeof(feed_times_array)) {
strncpy(feed_times_array, bracket_start, array_len);
feed_times_array[array_len] = '\0';
// 提取singleFeedWeight值
char *colon = strstr(single_weight_start, ":");
if (colon) {
// 跳过冒号和可能的空格
char *value_start = colon + 1;
while (*value_start == ' ' || *value_start == '\t') {
value_start++;
}
// 解析数字
uint16_t single_weight = 0;
if (sscanf(value_start, "%hu", &single_weight) == 1) {
elog_i(TAG, "解析到配置: feedTimes=%s, singleFeedWeight=%d",
feed_times_array, single_weight);
// 调用更新函数
Update_Feed_Config(feed_times_array, single_weight);
elog_i(TAG, "喂食配置更新完成");
} else {
elog_w(TAG, "解析singleFeedWeight失败");
}
} else {
elog_w(TAG, "找不到singleFeedWeight的冒号");
}
} else {
elog_w(TAG, "feedTimes数组太长超出缓冲区");
}
} else {
elog_w(TAG, "找不到feedTimes数组的括号");
}
} else {
elog_w(TAG, "JSON格式错误缺少feedTimes或singleFeedWeight字段");
}
}
} else {
elog_w(TAG, "MQTT消息解析失败");

View File

@@ -64,7 +64,7 @@
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES ( 56 )
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
#define configTOTAL_HEAP_SIZE ((size_t)30000)
#define configTOTAL_HEAP_SIZE ((size_t)31000)
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0

View File

@@ -111,22 +111,41 @@ static volatile uint8_t feed_amount = 1; // 喂食份数
// 加水控制命令枚举
typedef enum {
WATER_CMD_NONE = 0, // 无命令
WATER_CMD_MANUAL, // 手动加水
WATER_CMD_AUTO, // 自动加水
WATER_CMD_REMOTE // 远程加水
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; // 加水进行中标志
// 自动补水控制变量
static volatile uint8_t auto_water_enabled = 1; // 自动补水使能标志(默认开启)
static volatile uint8_t water_level_threshold = 1; // 水位阈值0=无水1=有水)
static volatile uint8_t pir_trigger_water = 0; // PIR传感器触发补水标志
static volatile uint32_t last_auto_water_time = 0; // 上次自动补水时间
static const uint32_t AUTO_WATER_INTERVAL = 30000; // 自动补水最小间隔30秒
// 定时喂食控制变量
#define MAX_SCHEDULED_FEED_TIMES 5 // 最大定时喂食时间数量
static volatile uint8_t scheduled_feed_enabled =
1; // 定时喂食使能标志(默认开启)
static volatile uint16_t single_feed_weight = 50; // 单次喂食重量默认50克
static volatile uint8_t scheduled_feed_count = 0; // 定时喂食时间数量
static volatile char scheduled_feed_times[MAX_SCHEDULED_FEED_TIMES][6] = {
0}; // 定时喂食时间数组(格式:"HH:MM"
static volatile uint32_t last_scheduled_feed_time = 0; // 上次定时喂食时间
static const uint32_t SCHEDULED_FEED_INTERVAL =
60000; // 定时喂食检查间隔60秒
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 256 * 4,
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityHigh1,
};
/* Definitions for wifi_mqtt */
@@ -171,6 +190,13 @@ const osThreadAttr_t water_control_attributes = {
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for hx711 */
osThreadId_t hx711Handle;
const osThreadAttr_t hx711_attributes = {
.name = "hx711",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityAboveNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
@@ -183,16 +209,23 @@ void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
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);
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) ;
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);
// 定时喂食相关函数
void Check_Scheduled_Feed(void);
void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight);
uint16_t Get_Single_Feed_Weight(void);
uint8_t Is_Time_Match(volatile const char *scheduled_time, uint8_t current_hour,
uint8_t current_minute);
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
@@ -202,6 +235,7 @@ void button_task(void *argument);
void sensorTask(void *argument);
void step_motor_task(void *argument);
void water_controlTask(void *argument);
void hx711Task(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
@@ -254,6 +288,9 @@ void MX_FREERTOS_Init(void) {
/* creation of water_control */
water_controlHandle = osThreadNew(water_controlTask, NULL, &water_control_attributes);
/* creation of hx711 */
hx711Handle = osThreadNew(hx711Task, NULL, &hx711_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
@@ -290,8 +327,8 @@ void StartDefaultTask(void *argument)
return;
}
// 设置为低音量-测试用
MP3_SetVolume(20);
// 设置为低音量-测试用
// MP3_SetVolume(20);
elog_i("MP3", "模块初始化完成");
MP3_Play(SYS_POWER_ON); // 播放系统启动音
@@ -303,6 +340,21 @@ void StartDefaultTask(void *argument)
osDelay(1000);
Device_Control(DEVICE_LED_RUN, 0); // 关闭运行灯
osDelay(1000);
// ========== 发布传感器数据到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数据失败");
}
osDelay(1000);
// ========== 发布结束 ===============
}
/* USER CODE END StartDefaultTask */
}
@@ -524,7 +576,7 @@ void LCD_Task(void *argument)
}
} else {
// 其他页面1秒刷新一次
osDelay(2000);
osDelay(800);
}
}
/* USER CODE END LCD_Task */
@@ -562,18 +614,6 @@ 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);
@@ -615,21 +655,6 @@ void sensorTask(void *argument)
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);
}
@@ -638,7 +663,6 @@ void sensorTask(void *argument)
/* USER CODE BEGIN Header_step_motor_task */
/**
* @brief Function implementing the step_motor thread.
* @param argument: Not used
@@ -673,10 +697,10 @@ void step_motor_task(void *argument)
/* USER CODE BEGIN Header_water_controlTask */
/**
* @brief Function implementing the water_control thread.
* @param argument: Not used
* @retval None
*/
* @brief Function implementing the water_control thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_water_controlTask */
void water_controlTask(void *argument)
{
@@ -684,9 +708,11 @@ void water_controlTask(void *argument)
elog_i("WATER", "启动加水控制任务");
/* Infinite loop */
for(;;)
{
// 检查是否有加水命令需要执行
for (;;) {
// 1. 检查定时喂食
Check_Scheduled_Feed();
// 2. 检查是否有加水命令需要执行
if (water_command != WATER_CMD_NONE && !watering_in_progress) {
Water_Cmd_t current_cmd = water_command;
@@ -699,11 +725,11 @@ void water_controlTask(void *argument)
// 根据命令类型播放不同音频
switch (current_cmd) {
case WATER_CMD_MANUAL:
MP3_Play(WATER_REFILL_DONE);
MP3_Play(WATER_REFILL_START);
elog_i("WATER", "执行手动加水");
break;
case WATER_CMD_AUTO:
MP3_Play(WATER_REFILL_DONE);
MP3_Play(WATER_REFILL_START);
elog_i("WATER", "执行自动加水");
break;
case WATER_CMD_REMOTE:
@@ -751,11 +777,96 @@ void water_controlTask(void *argument)
watering_in_progress = 0;
}
// 2. 自动补水逻辑(仅在自动模式下且自动补水功能使能时)
if (system_mode && auto_water_enabled && !watering_in_progress) {
uint32_t current_time = osKernelGetTickCount();
// 检查是否满足自动补水条件
// 条件1水位传感器检测到无水sensor_data.water_level == 0
// 条件2距离上次自动补水时间超过最小间隔
// 条件3PIR传感器触发补水标志可选
// 首先检查水位状态,如果水位充足,不进行任何补水
if (sensor_data.water_level == 0) {
// 水位不足,检查是否需要补水
// 优先级1PIR传感器触发补水宠物靠近
if (pir_trigger_water) {
elog_i("WATER", "PIR传感器触发补水 - 宠物靠近检测到水位不足");
// 清除PIR触发标志
pir_trigger_water = 0;
// 设置自动加水命令
water_command = WATER_CMD_AUTO;
// 记录日志
elog_i("WATER", "宠物靠近触发补水请求已提交");
}
// 优先级2定时自动补水水位检测
else if (current_time - last_auto_water_time > AUTO_WATER_INTERVAL) {
elog_i("WATER", "检测到水位不足,开始自动补水");
// 设置自动加水命令
water_command = WATER_CMD_AUTO;
// 记录日志
elog_i("WATER", "水位检测触发补水请求已提交");
}
} else {
// 水位充足清除PIR触发标志如果有
if (pir_trigger_water) {
elog_i("WATER", "水位充足忽略PIR传感器触发补水");
pir_trigger_water = 0;
}
}
}
// 3. PIR传感器触发补水处理宠物靠近时补水
// 注意HC_SR505人体传感器检测到宠物靠近时会触发HC_SR055_long_click_handler
// 我们可以在该处理函数中设置pir_trigger_water标志
// 在water_controlTask中会检查水位状态避免水位充足时不必要的补水
osDelay(100); // 短暂延时,保持任务响应性
}
/* USER CODE END water_controlTask */
}
/* USER CODE BEGIN Header_hx711Task */
/**
* @brief Function implementing the hx711 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_hx711Task */
void hx711Task(void *argument)
{
/* USER CODE BEGIN hx711Task */
HX711_Init(); // 初始化HX711重量传感器
// #################### 校准流程(首次使用必须执行!)####################
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(5000);
HX711_CalibrateScale(334.0f); // 放置0g的物体根据实际重量修改
elog_i(TAG, "量程标定完成!标定系数:%.6f", hx711_scale);
// #################### 循环读取重量 ####################
/* Infinite loop */
for (;;) {
sensor_data.food_weight = HX711_GetWeight(10); // 采样10次取平均
elog_d(TAG, "当前重量:%.2f g", sensor_data.food_weight);
osDelay(500);
}
/* USER CODE END hx711Task */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
@@ -873,7 +984,22 @@ 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触发"); }
/**
* @brief HC-SR505 PIR传感器处理函数检测到宠物靠近时触发补水
* @param btn: 按键句柄
*/
void HC_SR055_long_click_handler(Button *btn) {
elog_i("KEY", "HC-SR505触发 - 检测到宠物靠近");
// 设置PIR触发补水标志
pir_trigger_water = 1;
// 记录日志
elog_i("WATER", "PIR传感器触发补水标志已设置");
// 播放提示音(可选)- 使用PIR联动补水音效
MP3_Play(WATER_PIR_REFILL);
}
void user_button_init(void) {
// 初始化按键 (active_level: 0=低电平有效, 1=高电平有效)
@@ -1021,9 +1147,6 @@ Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; }
*/
void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; }
/**
* @brief 构建传感器数据JSON字符串符合小程序格式要求
* @param data 传感器数据结构体指针
@@ -1031,31 +1154,31 @@ void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; }
* @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;
}
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;
// 将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
// 构建符合小程序要求的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;
return 1;
}
/**
* @brief 请求喂食操作
* @param cmd: 喂食命令类型
@@ -1145,27 +1268,283 @@ static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
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秒
// 计算目标减少重量
float target_weight_loss;
if (cmd == FEED_CMD_MANUAL) {
// 手动喂食每份按10克计算
target_weight_loss = amount * 10.0f;
} else {
// 自动/远程喂食:使用配置的单次喂食重量
target_weight_loss = (float)single_feed_weight;
}
float initial_weight = sensor_data.food_weight;
float current_weight_loss = 0.0f;
// 检查重量传感器数据是否有效大于0.1克)
if (initial_weight < 0.1f) {
elog_w("FEED", "重量传感器数据无效(%.2fg),跳过重量监测,按设定份数喂食", initial_weight);
// 执行原始的喂食逻辑(无重量监测)
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);
} else {
elog_i("FEED", "喂食开始 - 初始重量: %.2fg, 目标减少重量: %.2fg",
initial_weight, target_weight_loss);
// 设置阈值目标重量的95%,避免传感器噪声)
float weight_threshold = target_weight_loss * 0.95f;
// 执行实际的喂食动作,每份喂食后检查重量变化
for (uint8_t i = 0; i < amount; i++) {
// 旋转角度投放一份食物
Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP);
// 等待食物落下
osDelay(3000);
// 更新当前重量损失
current_weight_loss = initial_weight - sensor_data.food_weight;
elog_i("FEED", "第%d份后 - 当前重量: %.2fg, 已减少重量: %.2fg",
i+1, sensor_data.food_weight, current_weight_loss);
// 检查是否已达到阈值
if (current_weight_loss >= weight_threshold) {
elog_i("FEED", "已达到目标减少重量(阈值%.2fg),停止喂食", weight_threshold);
break;
}
// 多份之间间隔1秒最后一份不需要
if (i < amount - 1) {
osDelay(1000);
}
}
}
// 等待食物落下
osDelay(3000);
// 停止电机
Stepper_Motor_Stop();
// 播放完成音效
MP3_Play(FEED_COMPLETE);
elog_i("FEED", "喂食完成");
elog_i("FEED", "喂食完成,总减少重量: %.2fg", current_weight_loss);
feeding_in_progress = 0;
}
/* USER CODE BEGIN Scheduled_Feed_Functions */
/**
* @brief 检查时间是否匹配定时喂食时间
* @param scheduled_time: 定时时间字符串,格式 "HH:MM"
* @param current_hour: 当前小时 (0-23)
* @param current_minute: 当前分钟 (0-59)
* @retval 1=匹配, 0=不匹配
*/
uint8_t Is_Time_Match(volatile const char *scheduled_time, uint8_t current_hour,
uint8_t current_minute) {
if (!scheduled_time) {
return 0;
}
// 创建临时副本以避免volatile问题
char time_buf[6];
strncpy(time_buf, (const char *)scheduled_time, sizeof(time_buf) - 1);
time_buf[sizeof(time_buf) - 1] = '\0';
if (strlen(time_buf) != 5) {
return 0;
}
// 解析定时时间 "HH:MM"
uint8_t scheduled_hour, scheduled_minute;
unsigned short temp_hour, temp_minute;
if (sscanf(time_buf, "%hu:%hu", &temp_hour, &temp_minute) != 2) {
return 0;
}
scheduled_hour = (uint8_t)temp_hour;
scheduled_minute = (uint8_t)temp_minute;
// 检查时间是否匹配
return (scheduled_hour == current_hour && scheduled_minute == current_minute);
}
/**
* @brief 检查定时喂食
* @note 在water_controlTask中调用每分钟检查一次
*/
void Check_Scheduled_Feed(void) {
static uint32_t last_check_time = 0;
uint32_t current_time = osKernelGetTickCount();
// 每分钟检查一次
if (current_time - last_check_time < SCHEDULED_FEED_INTERVAL) {
return;
}
last_check_time = current_time;
// 添加调试日志
elog_d("FEED", "开始检查定时喂食");
// 检查定时喂食是否使能
if (!scheduled_feed_enabled) {
elog_d("FEED", "定时喂食未使能");
return;
}
// 检查系统是否为自动模式
if (!system_mode) {
elog_d("FEED", "系统为手动模式,不执行定时喂食");
return; // 手动模式下不执行定时喂食
}
// 检查是否有定时喂食时间设置
if (scheduled_feed_count == 0) {
elog_d("FEED", "未设置定时喂食时间");
return;
}
// 获取当前时间
RTC_Time_t rtc_time;
if (BSP_RTC_GetTime(&rtc_time) != 0) {
elog_w("FEED", "获取RTC时间失败");
return; // 获取时间失败
}
elog_d("FEED", "当前时间: %02d:%02d, 定时时间数量: %d", rtc_time.hour,
rtc_time.minute, scheduled_feed_count);
// 打印所有定时时间用于调试
for (uint8_t i = 0; i < scheduled_feed_count; i++) {
elog_d("FEED", "定时时间[%d]: %s", i, scheduled_feed_times[i]);
}
// 检查每个定时喂食时间
for (uint8_t i = 0; i < scheduled_feed_count; i++) {
if (Is_Time_Match(scheduled_feed_times[i], rtc_time.hour,
rtc_time.minute)) {
// 时间匹配,执行定时喂食
elog_i("FEED", "定时喂食时间到达: %s", scheduled_feed_times[i]);
// 计算喂食份数:单次喂食重量 / 每份重量假设每份10克
uint8_t feed_amount = single_feed_weight / 10;
if (feed_amount < 1) {
feed_amount = 1; // 至少喂食1份
}
elog_i("FEED", "单次喂食重量: %dg, 计算份数: %d", single_feed_weight,
feed_amount);
// 提交定时喂食请求
if (Request_Feed(FEED_CMD_AUTO, 90, feed_amount)) {
elog_i("FEED", "定时喂食请求已提交: 重量=%dg, 份数=%d",
single_feed_weight, feed_amount);
} else {
elog_w("FEED", "定时喂食请求失败(可能正在喂食中)");
}
// 只执行一个定时喂食(避免同一分钟内有多个定时时间)
break;
}
}
}
/**
* @brief 更新喂食配置
* @param feed_times_json: 定时喂食时间JSON数组字符串格式 ["HH:MM", "HH:MM",
* ...]
* @param single_weight: 单次喂食重量(克)
*/
void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight) {
if (!feed_times_json) {
return;
}
elog_i("CONFIG", "更新喂食配置: 单次重量=%dg, 时间JSON=%s", single_weight,
feed_times_json);
// 更新单次喂食重量
single_feed_weight = single_weight;
// 解析定时喂食时间数组
const char *ptr = feed_times_json;
uint8_t count = 0;
// 清空现有定时时间
for (uint8_t i = 0; i < MAX_SCHEDULED_FEED_TIMES; i++) {
scheduled_feed_times[i][0] = '\0';
}
// 简单JSON解析查找时间字符串 "HH:MM"
while (*ptr && count < MAX_SCHEDULED_FEED_TIMES) {
// 查找引号
if (*ptr == '"') {
ptr++; // 跳过引号
const char *time_start = ptr;
// 查找结束引号
while (*ptr && *ptr != '"') {
ptr++;
}
if (*ptr == '"') {
// 提取时间字符串
size_t time_len = ptr - time_start;
if (time_len == 5) { // "HH:MM" 格式
// 使用类型转换避免volatile警告
char *dest = (char *)scheduled_feed_times[count];
strncpy(dest, time_start, time_len);
dest[time_len] = '\0';
// 验证时间格式
uint8_t hour, minute;
unsigned short temp_hour, temp_minute;
if (sscanf(dest, "%hu:%hu", &temp_hour, &temp_minute) == 2) {
hour = (uint8_t)temp_hour;
minute = (uint8_t)temp_minute;
if (hour < 24 && minute < 60) {
elog_i("CONFIG", "添加定时喂食时间: %s",
scheduled_feed_times[count]);
count++;
} else {
elog_w("CONFIG", "无效的时间格式: %s",
scheduled_feed_times[count]);
}
} else {
elog_w("CONFIG", "时间格式解析失败: %s",
scheduled_feed_times[count]);
}
} else {
elog_w("CONFIG", "时间字符串长度不正确: %.*s (长度=%d)",
(int)time_len, time_start, (int)time_len);
}
}
}
if (*ptr) {
ptr++; // 移动到下一个字符
}
}
scheduled_feed_count = count;
elog_i("CONFIG", "定时喂食配置更新完成: 共%d个时间", count);
// 播放配置更新成功音效
MP3_Play(PARAM_SAVE_OK);
}
/**
* @brief 获取单次喂食重量
* @return 单次喂食重量(克)
*/
uint16_t Get_Single_Feed_Weight(void) {
return single_feed_weight;
}
/* USER CODE END Scheduled_Feed_Functions */
/* USER CODE END LCD_Page_Functions */

View File

@@ -72,8 +72,15 @@ void MX_GPIO_Init(void)
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(HX711_DOUT_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : HX711_SCK_Pin LCD_DC_Pin LCD_RESET_Pin STEPPER_IN4_Pin */
GPIO_InitStruct.Pin = HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin;
/*Configure GPIO pin : HX711_SCK_Pin */
GPIO_InitStruct.Pin = HX711_SCK_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(HX711_SCK_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : LCD_DC_Pin LCD_RESET_Pin STEPPER_IN4_Pin */
GPIO_InitStruct.Pin = LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

View File

@@ -33,10 +33,10 @@ Dma.USART1_TX.0.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE
Dma.USART1_TX.0.Priority=DMA_PRIORITY_VERY_HIGH
Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority
FREERTOS.FootprintOK=true
FREERTOS.FootprintOK=false
FREERTOS.IPParameters=Tasks01,FootprintOK,configTOTAL_HEAP_SIZE
FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL;step_motor,24,512,step_motor_task,Default,NULL,Dynamic,NULL,NULL;water_control,24,512,water_controlTask,Default,NULL,Dynamic,NULL,NULL
FREERTOS.configTOTAL_HEAP_SIZE=30000
FREERTOS.Tasks01=defaultTask,41,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL;step_motor,24,512,step_motor_task,Default,NULL,Dynamic,NULL,NULL;water_control,24,512,water_controlTask,Default,NULL,Dynamic,NULL,NULL;hx711,32,256,hx711Task,Default,NULL,Dynamic,NULL,NULL
FREERTOS.configTOTAL_HEAP_SIZE=31000
File.Version=6
GPIO.groupedBy=Group By Peripherals
KeepUserPlacement=false
@@ -132,8 +132,9 @@ PA2.GPIOParameters=GPIO_Label
PA2.GPIO_Label=HX711_DOUT
PA2.Locked=true
PA2.Signal=GPIO_Input
PA3.GPIOParameters=GPIO_Label
PA3.GPIOParameters=GPIO_Speed,GPIO_Label
PA3.GPIO_Label=HX711_SCK
PA3.GPIO_Speed=GPIO_SPEED_FREQ_HIGH
PA3.Locked=true
PA3.Signal=GPIO_Output
PA4.GPIOParameters=GPIO_Label