357 lines
10 KiB
C
357 lines
10 KiB
C
/*
|
||
* 模块名称:菜单系统
|
||
* 模块功能:通过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);
|
||
}
|
||
}
|