Files
STM32F103/STM32F103C8T6/User/menu.c

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