feat(bluetooth): 添加多按钮支持和WiFi连接音频反馈

添加MultiButton库支持多按键功能,重构SPI显示屏驱动代码,
迁移MP3音频文件至正确目录并集成WiFi连接状态音频提示音。

- 添加Multi_Button.c源文件和相关头文件包含
- 重构spi_st7735s.c中的数组初始化格式,优化代码可读性
- 将MP3音频文件从Development_Docs/MP3迁移到Core/Bsp/BSP_Device/bsp_mp3/MP3
- 在WiFi连接过程中添加MP3音频反馈(连接成功/失败提示音)
- 优化ST7735显示屏驱动中的DMA传输模式支持
```
This commit is contained in:
2026-02-23 16:59:34 +08:00
parent ce8d6fd2eb
commit 9cadad138e
37 changed files with 980 additions and 201 deletions

View File

@@ -19,10 +19,9 @@
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "cmsis_os.h"
#include "main.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
@@ -30,10 +29,10 @@
#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"
#include "mp3_play_index.h"
/* USER CODE END Includes */
@@ -45,7 +44,11 @@
/* 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 -------------------------------------------------------------*/
@@ -56,45 +59,82 @@
/* 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,
.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,
.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,
.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
*/
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
ST7735_Init(); // 初始化ST7735显示屏
@@ -118,8 +158,7 @@ void MX_FREERTOS_Init(void) {
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle =
osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of wifi_mqtt */
wifi_mqttHandle = osThreadNew(wifi_task_mqtt, NULL, &wifi_mqtt_attributes);
@@ -127,6 +166,9 @@ void MX_FREERTOS_Init(void) {
/* 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 */
@@ -134,6 +176,7 @@ void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
@@ -143,7 +186,8 @@ void MX_FREERTOS_Init(void) {
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument) {
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
// 1. 打开运行灯
Device_Control(DEVICE_LED_RUN, 1);
@@ -159,7 +203,7 @@ void StartDefaultTask(void *argument) {
}
elog_i("MP3", "模块初始化完成");
MP3_Play(SYS_POWER_ON);
MP3_Play(SYS_POWER_ON); // 播放系统启动音
/* Infinite loop */
for (;;) {
@@ -179,36 +223,117 @@ void StartDefaultTask(void *argument) {
* @retval None
*/
/* USER CODE END Header_LCD_Task */
void LCD_Task(void *argument) {
void LCD_Task(void *argument)
{
/* USER CODE BEGIN LCD_Task */
char time_str[16];
char display_str[32];
SNTP_Time_t now;
uint16_t text_color = ST7735_BLACK;
uint16_t bg_color = ST7735_WHITE;
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; // 自动模式
// 显示时间的区域大小假设字体16x24
const uint16_t x = 0;
const uint16_t y = 0;
const uint16_t w = 6 * 16; // 6个字符每个字符16px宽
const uint16_t h = 24; // 高度与字体匹配
/* Infinite loop */
for (;;) {
if (WIFI_Is_Time_Valid()) {
WIFI_Get_SNTP_Time();
now = WIFI_Get_Current_Time();
if (now.valid) {
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", now.hour,
now.minute, now.second);
// 根据当前页面显示不同内容
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:%02d", now.hour,
now.minute, now.second);
} else {
snprintf(display_str, sizeof(display_str), "--:--:--");
}
ST7735_WriteString(10, 20, display_str, &Font_16x26, text_color, bg_color);
} else {
snprintf(time_str, sizeof(time_str), "--:--:--");
ST7735_WriteString(10, 20, "--:--:--", &Font_16x26, text_color, 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);
}
// 先清空显示区域
ST7735_FillRectangleFast(x, y, w, h, bg_color);
// 写入时间
ST7735_WriteString(x, y, time_str, &Font_16x26, text_color, 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;
}
osDelay(1000);
@@ -216,7 +341,173 @@ void LCD_Task(void *argument) {
/* 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 */

View File

@@ -60,10 +60,10 @@ void MX_GPIO_Init(void)
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, LED2_Pin|LED1_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : KEY1_Pin KEY2_Pin KEY3_Pin KEY3C3_Pin */
GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin|KEY3C3_Pin;
/*Configure GPIO pins : KEY1_Pin KEY2_Pin KEY3_Pin KEY4_Pin */
GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin|KEY4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pin : HX711_DOUT_Pin */

View File

@@ -45,7 +45,7 @@ void MX_SPI1_Init(void)
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;