菜单支持,所有功能都测试成功

This commit is contained in:
2026-07-15 15:39:49 +08:00
parent d4112399c2
commit 92facadeb0
16 changed files with 908 additions and 447 deletions

356
STM32F103C8T6/User/menu.c Normal file
View File

@@ -0,0 +1,356 @@
/*
* 模块名称:菜单系统
* 模块功能通过LCD显示参数按键导航/调整AT24C02存储
* 适用平台STM32F103C8T6
* 作者:王建锋
* 创建日期2026-07-14
* 修改记录:
* 2026-07-14 王建锋 创建初始版本
*/
#include "menu.h"
#include "button_driver.h"
#include "at24c02.h"
#include "lcd.h"
#include "tim.h"
#include <stdio.h>
#define COLOR_GRAY 0x8410 /* RGB565 灰 */
/* ======================== EEPROM 存储布局 ======================== */
#define EEPROM_DATA_ADDR 0x10 /* 参数存储起始地址 */
#define EEPROM_MAGIC 0xA55A /* 有效数据标识 */
#define EEPROM_MAGIC_ADDR 0x0E /* Magic 存储地址2字节 */
/* ======================== 参数定义 ======================== */
typedef struct {
const char *name; /* 显示名称 */
const char *unit; /* 单位 */
int32_t default_val; /* 默认值 */
int32_t min; /* 最小值 */
int32_t max; /* 最大值 */
int32_t step; /* 步进 */
} ParamDef_t;
static const ParamDef_t s_param_def[PARAM_COUNT] = {
{"PWM1", "", 500, 0, 999, 50 },
{"PWM2", "", 500, 0, 999, 50 },
{"PWM3", "", 500, 0, 999, 50 },
{"PWM4", "", 500, 0, 999, 50 },
};
/* ======================== 运行时状态 ======================== */
static int32_t s_param_val[PARAM_COUNT]; /* 当前参数值 */
static uint8_t s_sel = 0; /* 选中的参数索引 */
static uint8_t s_editing = 0; /* 0=导航, 1=编辑 */
static uint8_t s_redraw = 1; /* 需要重绘标志 */
/* ======================== 静态函数声明 ======================== */
static void draw_menu(void);
static void load_params(void);
static int save_params(void);
static void apply_param(uint8_t idx);
/* ======================== 公开函数 ======================== */
/*
* 函数功能:菜单系统初始化
* 入口参数:无
* 返回值:无
* 函数说明从EEPROM加载参数清屏绘制初始界面
*/
void menu_init(void)
{
lcd_draw_string(0, 0, "Menu Loading...", COLOR_WHITE, COLOR_BLACK);
load_params();
s_sel = 0;
s_editing = 0;
s_redraw = 1;
apply_param(PARAM_PWM1);
apply_param(PARAM_PWM2);
apply_param(PARAM_PWM3);
apply_param(PARAM_PWM4);
st7735s_fill_screen(COLOR_BLACK);
draw_menu();
}
/*
* 函数功能:菜单系统循环处理
* 入口参数:无
* 返回值:无
* 函数说明:轮询按键事件,更新参数/界面。需在主循环中频繁调用
*/
void menu_process(void)
{
ButtonEvent evt;
uint8_t i;
int32_t *p_val;
/* 依次检查5个按键的事件 */
for (i = 0; i < 5; i++) {
evt = button_driver_get_event(i);
if (evt == BTN_NONE_PRESS) {
continue;
}
if (evt == BTN_SINGLE_CLICK) {
switch (i) {
case 0: /* KEY1: 上移 */
if (!s_editing) {
if (s_sel > 0) {
s_sel--;
} else {
s_sel = PARAM_COUNT - 1;
}
s_redraw = 1;
}
break;
case 1: /* KEY2: 减小 */
p_val = &s_param_val[s_sel];
*p_val -= s_param_def[s_sel].step;
if (*p_val < s_param_def[s_sel].min) {
*p_val = s_param_def[s_sel].min;
}
s_editing = 1;
s_redraw = 1;
apply_param(s_sel);
break;
case 2: /* KEY3: 下移 */
if (!s_editing) {
s_sel++;
if (s_sel >= PARAM_COUNT) {
s_sel = 0;
}
s_redraw = 1;
}
break;
case 3: /* KEY4: 增大 */
p_val = &s_param_val[s_sel];
*p_val += s_param_def[s_sel].step;
if (*p_val > s_param_def[s_sel].max) {
*p_val = s_param_def[s_sel].max;
}
s_editing = 1;
s_redraw = 1;
apply_param(s_sel);
break;
case 4: /* KEY5: 确认保存 */
if (save_params() == AT24C02_OK) {
lcd_draw_string(0, 80, "SAVED! ", COLOR_GREEN, COLOR_BLACK);
printf("Menu: params saved\r\n");
} else {
lcd_draw_string(0, 80, "SAVE FAIL! ", COLOR_RED, COLOR_BLACK);
printf("Menu: params save FAIL\r\n");
}
s_editing = 0;
s_redraw = 1;
break;
default:
break;
}
}
/* 只处理一个按键事件每帧 */
break;
}
if (s_redraw) {
draw_menu();
s_redraw = 0;
}
}
/*
* 函数功能:获取指定参数的当前值
* 入口参数id - 参数ID
* 返回值:参数值
*/
int32_t menu_get_param(ParamId_t id)
{
if (id >= PARAM_COUNT) {
return 0;
}
return s_param_val[id];
}
/*
* 函数功能强制保存参数到EEPROM
* 入口参数:无
* 返回值0=成功,负值=失败
*/
int menu_save_params(void)
{
return save_params();
}
/* ======================== 内部函数 ======================== */
/*
* 函数功能从EEPROM加载参数
* 入口参数:无
* 返回值:无
* 函数说明读取EEPROM中存储的参数值若Magic不对则使用默认值
*/
static void load_params(void)
{
uint8_t magic_buf[2];
uint8_t data_buf[PARAM_COUNT * 4];
int ret;
uint8_t i;
/* 先读取Magic */
ret = at24c02_read_buf(EEPROM_MAGIC_ADDR, magic_buf, 2);
if ((ret != AT24C02_OK) ||
(magic_buf[0] != ((EEPROM_MAGIC >> 8) & 0xFF)) ||
(magic_buf[1] != (EEPROM_MAGIC & 0xFF))) {
/* Magic不匹配使用默认值 */
for (i = 0; i < PARAM_COUNT; i++) {
s_param_val[i] = s_param_def[i].default_val;
}
printf("Menu: using defaults (no saved data)\r\n");
return;
}
/* 读取参数数据 */
ret = at24c02_read_buf(EEPROM_DATA_ADDR, data_buf, PARAM_COUNT * 4);
if (ret != AT24C02_OK) {
for (i = 0; i < PARAM_COUNT; i++) {
s_param_val[i] = s_param_def[i].default_val;
}
printf("Menu: EEPROM read fail, using defaults\r\n");
return;
}
/* 解析数据(小端:低字节在前) */
for (i = 0; i < PARAM_COUNT; i++) {
s_param_val[i] = (int32_t)(
(uint32_t)data_buf[i * 4 + 0] |
((uint32_t)data_buf[i * 4 + 1] << 8) |
((uint32_t)data_buf[i * 4 + 2] << 16) |
((uint32_t)data_buf[i * 4 + 3] << 24));
/* 钳位到有效范围 */
if (s_param_val[i] < s_param_def[i].min) {
s_param_val[i] = s_param_def[i].min;
}
if (s_param_val[i] > s_param_def[i].max) {
s_param_val[i] = s_param_def[i].max;
}
}
printf("Menu: params loaded from EEPROM\r\n");
}
/*
* 函数功能保存参数到EEPROM
* 入口参数:无
* 返回值0=成功,负值=失败
* 函数说明先写Magic标识再写参数数据
*/
static int save_params(void)
{
uint8_t magic_buf[2];
uint8_t data_buf[PARAM_COUNT * 4];
uint8_t i;
int ret;
/* 打包Magic */
magic_buf[0] = (EEPROM_MAGIC >> 8) & 0xFF;
magic_buf[1] = EEPROM_MAGIC & 0xFF;
/* 打包参数(小端) */
for (i = 0; i < PARAM_COUNT; i++) {
data_buf[i * 4 + 0] = (uint8_t)(s_param_val[i] & 0xFF);
data_buf[i * 4 + 1] = (uint8_t)((s_param_val[i] >> 8) & 0xFF);
data_buf[i * 4 + 2] = (uint8_t)((s_param_val[i] >> 16) & 0xFF);
data_buf[i * 4 + 3] = (uint8_t)((s_param_val[i] >> 24) & 0xFF);
}
/* 写入EEPROM */
ret = at24c02_write_buf(EEPROM_MAGIC_ADDR, magic_buf, 2);
if (ret != AT24C02_OK) {
return ret;
}
ret = at24c02_write_buf(EEPROM_DATA_ADDR, data_buf, PARAM_COUNT * 4);
if (ret != AT24C02_OK) {
return ret;
}
return AT24C02_OK;
}
/*
* 函数功能将指定参数立即应用到对应PWM通道
* 入口参数idx - 参数索引 (0=PWM1, 1=PWM2, 2=PWM3, 3=PWM4)
* 返回值:无
*/
static void apply_param(uint8_t idx)
{
uint32_t duty = (uint32_t)s_param_val[idx];
static const uint16_t ch_map[4] = {
TIM_CHANNEL_1, TIM_CHANNEL_2, TIM_CHANNEL_3, TIM_CHANNEL_4
};
if (idx < PARAM_COUNT) {
__HAL_TIM_SET_COMPARE(&htim3, ch_map[idx], duty);
printf("PWM%d duty=%lu\r\n", idx + 1, (unsigned long)duty);
}
}
/*
* 函数功能:绘制菜单界面
* 入口参数:无
* 返回值:无
* 函数说明:清空参数区域,重新绘制所有参数行
*/
static void draw_menu(void)
{
char buf[24];
uint8_t i;
uint16_t color;
/* 绘制标题行 */
lcd_draw_string(0, 0, "-- PARAM MENU --", COLOR_CYAN, COLOR_BLACK);
/* 绘制各参数每行16像素从y=16开始 */
for (i = 0; i < PARAM_COUNT; i++) {
if (i == s_sel) {
if (s_editing) {
/* 编辑态:高亮 + 左右尖括号 */
snprintf(buf, sizeof(buf), ">%s:%ld %s< ",
s_param_def[i].name,
(long)s_param_val[i],
s_param_def[i].unit);
color = COLOR_YELLOW;
} else {
/* 选中态:箭头 + 高亮 */
snprintf(buf, sizeof(buf), ">%s:%ld %s ",
s_param_def[i].name,
(long)s_param_val[i],
s_param_def[i].unit);
color = COLOR_WHITE;
}
} else {
/* 未选中:普通显示 */
snprintf(buf, sizeof(buf), " %s:%ld %s ",
s_param_def[i].name,
(long)s_param_val[i],
s_param_def[i].unit);
color = COLOR_GRAY;
}
/* 填充整行背景以覆盖之前内容 */
lcd_draw_string(0, 16 + i * 16, buf, color, COLOR_BLACK);
}
/* 底部提示 */
if (s_editing) {
lcd_draw_string_6x8(0, 140, "KEY5:SAVE ", COLOR_GREEN, COLOR_BLACK);
} else {
lcd_draw_string_6x8(0, 140, "K1/K3:Sel K2/K4:Adj", COLOR_GRAY, COLOR_BLACK);
}
}