Files
ZhaTianRC/Core/Src/menu.c
admin f3df7276b2 feat: 长按返回键2s开关机功能
- 开机: 长按返回键2s开机,显示左侧Logo+右侧'Starting...'开机画面2秒
- 关机: 主页未连接接收机时长按返回键2s关机
  - 关闭OLED/LED/PWM/NRF24L01/USB/外设时钟 → STOP休眠
  - 唤醒后自动复位重启
- 已连接接收机时屏蔽关机,防止飞行中误操作
- 添加 Input_IsKeyHeldFor() 指定时长按键检测
- 添加 OLED_Off() 关闭显示
- 更新 README.md 文档
2026-06-25 23:19:31 +08:00

739 lines
23 KiB
C

/**
* 菜单系统实现
* 基于 OLED 128x64 的层级菜单 UI
*/
#include "menu.h"
#include <stdio.h>
#include <string.h>
/* 全局页面状态 */
Page_t g_page;
/* 通道输出值 (混控后) */
int16_t g_channels[CHANNEL_COUNT];
/* 模型类型名称 */
static const char *model_types[] = {"Airplane", "Car", "Boat", "MultiRotor"};
/* 输入名称缓存 */
static char input_name_buf[16];
/* 获取输入名称 */
static const char* GetInputName(uint8_t idx, uint8_t type) {
if (type == INPUT_TYPE_ADC) {
snprintf(input_name_buf, sizeof(input_name_buf), "ADC_IN%d", idx);
} else if (type == INPUT_TYPE_KEY) {
snprintf(input_name_buf, sizeof(input_name_buf), "KEY_IN%d", idx);
} else {
snprintf(input_name_buf, sizeof(input_name_buf), "None");
}
return input_name_buf;
}
/* 获取通道值显示 */
static const char* GetChannelValue(uint8_t ch) {
Model_t *m = Storage_GetActiveModel();
if (m->input_type[ch] == INPUT_TYPE_ADC) {
snprintf(input_name_buf, sizeof(input_name_buf), "%4d",
adc_values[m->channel_map[ch]]);
} else if (m->input_type[ch] == INPUT_TYPE_KEY) {
snprintf(input_name_buf, sizeof(input_name_buf), "%s",
key_values[m->channel_map[ch]] ? "ON " : "OFF");
} else {
snprintf(input_name_buf, sizeof(input_name_buf), "----");
}
return input_name_buf;
}
void Menu_Init(void) {
memset(&g_page, 0, sizeof(Page_t));
g_page.page_id = PAGE_HOME;
g_page.cursor = 0;
}
/* ---- 混控计算 ---- */
void Mixer_Compute(int16_t *channels) {
Model_t *m = Storage_GetActiveModel();
for (uint8_t i = 0; i < CHANNEL_COUNT; i++) {
if (m->input_type[i] == INPUT_TYPE_NONE) {
channels[i] = 0;
continue;
}
int16_t raw = 0;
if (m->input_type[i] == INPUT_TYPE_ADC) {
/* ADC: 0-4095 -> -1000 ~ +1000 */
raw = (int16_t)(adc_values[m->channel_map[i]]) - 2048;
raw = raw * 1000 / 2048;
} else {
/* 按键: 0/1 -> -1000/+1000 */
raw = key_values[m->channel_map[i]] ? 1000 : -1000;
}
/* 应用混控 */
switch (m->mixer_type[i]) {
case MIXER_TYPE_SIMPLE:
channels[i] = (raw * m->mixer_scale[i] / 100) + m->mixer_offset[i];
break;
case MIXER_TYPE_DELTA:
/* 三角翼: CH1=elevon_left, CH2=elevon_right */
if (i == 0) channels[i] = (raw * m->mixer_scale[i] / 100) + m->mixer_offset[i];
if (i == 1) channels[i] = (-raw * m->mixer_scale[i] / 100) + m->mixer_offset[i];
break;
case MIXER_TYPE_VTAIL:
channels[i] = (raw * m->mixer_scale[i] / 100) + m->mixer_offset[i];
break;
case MIXER_TYPE_ELEVON:
channels[i] = (raw * m->mixer_scale[i] / 100) + m->mixer_offset[i];
break;
default:
channels[i] = raw;
break;
}
/* 限幅 */
if (channels[i] > 1000) channels[i] = 1000;
if (channels[i] < -1000) channels[i] = -1000;
}
}
/* ---- 通用菜单导航 ---- */
static void Menu_Navigate(void) {
uint8_t key = Input_GetKey();
if (key == KEY_UP) {
if (g_page.cursor > 0) g_page.cursor--;
} else if (key == KEY_DOWN) {
if (g_page.cursor < g_page.item_count - 1) g_page.cursor++;
} else if (key == KEY_OK) {
/* 执行选中项 */
if (g_page.cursor < g_page.item_count && g_page.items[g_page.cursor].target_page != 0xFF) {
g_page.page_id = g_page.items[g_page.cursor].target_page;
g_page.cursor = 0;
g_page.item_count = 0;
}
if (g_page.cursor < g_page.item_count && g_page.items[g_page.cursor].action) {
g_page.items[g_page.cursor].action();
}
} else if (key == KEY_BACK) {
/* 返回上一级 */
if (g_page.page_id == PAGE_HOME) {
/* 首页按返回: 切换值/回传 */
g_config.home_page = !g_config.home_page;
} else if (g_page.page_id == PAGE_MAIN_MENU) {
g_page.page_id = PAGE_HOME;
} else if (g_page.page_id >= PAGE_MODEL_LIST && g_page.page_id <= PAGE_MIXER_EDIT) {
g_page.page_id = PAGE_MAIN_MENU;
} else {
g_page.page_id = PAGE_MAIN_MENU;
}
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 首页 ---- */
void Menu_DrawHome(void) {
OLED_Clear();
if (g_config.home_page == 0) {
/* 回传信息页 */
OLED_ShowString(0, 0, "Telemetry", 6);
OLED_DrawLine(0, 9, 127, 9);
/* 电压 */
OLED_ShowString(0, 14, "V:", 6);
OLED_ShowNum(18, 14, 0, 4, 6); /* TODO: 实际电压 */
OLED_ShowString(48, 14, "V", 6);
/* RSSI */
OLED_ShowString(0, 24, "RSSI:", 6);
OLED_ShowNum(36, 24, 0, 3, 6);
/* 丢包率 */
OLED_ShowString(0, 34, "Lost:", 6);
OLED_ShowNum(36, 34, 0, 3, 6);
OLED_ShowString(60, 34, "%", 6);
/* 模型名 */
Model_t *m = Storage_GetActiveModel();
OLED_ShowString(0, 48, m->name, 6);
/* 高频头状态 */
OLED_ShowString(80, 48, g_config.rf_type == 0 ? "NRF" : "CRFS", 6);
OLED_ShowString(80, 56, nrf_connected ? "OK" : "--", 6);
} else {
/* INx 值页 */
OLED_ShowString(0, 0, "ADC Values", 6);
OLED_DrawLine(0, 9, 127, 9);
Model_t *m = Storage_GetActiveModel();
for (uint8_t i = 0; i < 8; i++) {
uint8_t row = i / 2;
uint8_t col = (i % 2) * 64;
OLED_ShowString(col, 12 + row * 12, "IN", 6);
OLED_ShowNum(col + 12, 12 + row * 12, i, 2, 6);
OLED_ShowString(col + 24, 12 + row * 12, ":", 6);
if (m->input_type[i] == INPUT_TYPE_ADC) {
OLED_ShowNum(col + 30, 12 + row * 12,
adc_values[m->channel_map[i]], 4, 6);
} else {
OLED_ShowString(col + 30, 12 + row * 12,
key_values[m->channel_map[i]] ? "ON " : "OFF", 6);
}
}
}
OLED_Display();
Menu_Navigate();
/* 首页长按确认 -> 高频头快捷页 */
if (Input_IsKeyHeld(KEY_OK)) {
g_page.page_id = PAGE_RF_QUICK;
g_page.cursor = 0;
g_page.item_count = 0;
}
/* 长按返回 2s -> 关机 (已连接接收机时不关机) */
if (Input_IsKeyHeldFor(KEY_BACK, 2000) && !nrf_connected) {
System_Shutdown();
}
}
/* ---- 主菜单 ---- */
void Menu_DrawMainMenu(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Main Menu", 16);
OLED_DrawLine(0, 17, 127, 17);
static const char *items[] = {
"Model Select",
"RF Config",
"Channel Map",
"Mixer",
"PWM Driver",
"Home Page"
};
g_page.item_count = 6;
for (uint8_t i = 0; i < g_page.item_count; i++) {
uint8_t y = 20 + i * 10;
if (i == g_page.cursor) {
OLED_ShowString(0, y, ">", 6);
}
OLED_ShowString(8, y, items[i], 6);
}
OLED_Display();
Menu_Navigate();
/* 映射到具体页面 */
if (Input_IsKeyPressed(KEY_OK)) {
switch (g_page.cursor) {
case 0: g_page.page_id = PAGE_MODEL_LIST; break;
case 1: g_page.page_id = PAGE_RF_CONFIG; break;
case 2: g_page.page_id = PAGE_CHANNEL_MAP; break;
case 3: g_page.page_id = PAGE_MIXER; break;
case 4: g_page.page_id = PAGE_PWM_CONFIG; break;
case 5: g_page.page_id = PAGE_HOME_SET; break;
}
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 模型列表 ---- */
void Menu_DrawModelList(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Models", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 0;
/* 第一项: 新建 */
OLED_ShowString(0, 20, g_page.cursor == 0 ? ">[New]" : " [New]", 6);
g_page.item_count++;
for (uint8_t i = 0; i < MODEL_MAX_COUNT; i++) {
if (g_config.models[i].name[0] != '\0') {
uint8_t y = 20 + g_page.item_count * 10;
if (y > 54) break; /* 超出屏幕 */
char buf[20];
snprintf(buf, sizeof(buf), "%s[%c] %s",
g_page.cursor == g_page.item_count ? ">" : " ",
i == g_config.active_model ? '*' : ' ',
g_config.models[i].name);
OLED_ShowString(0, y, buf, 6);
g_page.item_count++;
}
}
OLED_Display();
Menu_Navigate();
/* 处理选择 */
if (Input_IsKeyPressed(KEY_OK)) {
if (g_page.cursor == 0) {
/* 新建模型 */
g_page.page_id = PAGE_MODEL_NEW;
g_page.cursor = 0;
g_page.edit_idx = 0;
g_page.edit_str_len = 0;
memset(g_page.edit_str, 0, sizeof(g_page.edit_str));
} else {
/* 选择模型 */
uint8_t model_idx = 0xFF;
uint8_t cnt = 1;
for (uint8_t i = 0; i < MODEL_MAX_COUNT; i++) {
if (g_config.models[i].name[0] != '\0') {
if (cnt == g_page.cursor) { model_idx = i; break; }
cnt++;
}
}
if (model_idx != 0xFF) {
Storage_ModelSelect(model_idx);
g_page.page_id = PAGE_MAIN_MENU;
}
}
g_page.cursor = 0;
g_page.item_count = 0;
}
/* 长按确认 -> 编辑/删除 */
if (Input_IsKeyHeld(KEY_OK) && g_page.cursor > 0) {
/* TODO: 弹出编辑/删除选项 */
}
}
/* ---- 新建模型 ---- */
void Menu_DrawModelNew(void) {
OLED_Clear();
OLED_ShowString(0, 0, "New Model", 16);
OLED_DrawLine(0, 17, 127, 17);
/* 名称编辑 */
OLED_ShowString(0, 22, "Name:", 6);
OLED_ShowString(36, 22, g_page.edit_str, 6);
if (g_page.edit_idx == 0) {
/* 闪烁光标 */
OLED_ShowString(36 + g_page.edit_str_len * 6, 22, "_", 6);
}
/* 类型选择 */
OLED_ShowString(0, 34, "Type:", 6);
OLED_ShowString(36, 34, (char*)model_types[g_page.edit_value], 6);
/* 按钮 */
OLED_ShowString(20, 50, "[OK]", 6);
OLED_ShowString(80, 50, "[Cancel]", 6);
OLED_Display();
/* 处理输入 */
uint8_t key = Input_GetKey();
if (g_page.edit_idx == 0) {
/* 编辑名称 */
if (key == KEY_UP) {
/* 字符递增 */
if (g_page.edit_str_len < 8) {
g_page.edit_str[g_page.edit_str_len] = 'A';
g_page.edit_str_len++;
}
} else if (key == KEY_DOWN) {
if (g_page.edit_str_len > 0) {
g_page.edit_str_len--;
g_page.edit_str[g_page.edit_str_len] = '\0';
}
} else if (key == KEY_OK) {
g_page.edit_idx = 1; /* 切换到类型选择 */
}
} else if (g_page.edit_idx == 1) {
if (key == KEY_UP && g_page.edit_value < 3) g_page.edit_value++;
if (key == KEY_DOWN && g_page.edit_value > 0) g_page.edit_value--;
if (key == KEY_OK) g_page.edit_idx = 2; /* 到确认按钮 */
} else if (g_page.edit_idx == 2) {
/* 确认/取消 */
if (key == KEY_UP || key == KEY_DOWN) g_page.edit_idx = (g_page.edit_idx == 2) ? 3 : 2;
if (key == KEY_OK) {
if (g_page.edit_idx == 2) {
/* 确认创建 */
Storage_ModelAdd(g_page.edit_str, g_page.edit_value);
g_page.page_id = PAGE_MODEL_LIST;
} else {
g_page.page_id = PAGE_MODEL_LIST;
}
g_page.cursor = 0;
g_page.item_count = 0;
}
}
if (key == KEY_BACK) {
g_page.page_id = PAGE_MODEL_LIST;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 通道映射 ---- */
void Menu_DrawChannelMap(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Channel Map", 16);
OLED_DrawLine(0, 17, 127, 17);
Model_t *m = Storage_GetActiveModel();
g_page.item_count = CHANNEL_COUNT + 1; /* +1 for [Next] */
uint8_t start = g_page.scroll_offset;
for (uint8_t i = start; i < start + 4 && i < CHANNEL_COUNT; i++) {
uint8_t y = 20 + (i - start) * 10;
char buf[32];
snprintf(buf, sizeof(buf), "%s[CH%d]->[%s]",
i == g_page.cursor ? ">" : " ",
i + 1,
GetInputName(m->channel_map[i], m->input_type[i]));
OLED_ShowString(0, y, buf, 6);
}
/* [下一步] 按钮 */
if (g_page.cursor >= CHANNEL_COUNT - 4) {
OLED_ShowString(40, 56, "[Next]", 6);
}
OLED_Display();
Menu_Navigate();
/* 编辑通道映射 */
if (Input_IsKeyPressed(KEY_OK) && g_page.cursor < CHANNEL_COUNT) {
/* 循环切换输入源 */
m->input_type[g_page.cursor]++;
if (m->input_type[g_page.cursor] > INPUT_TYPE_KEY)
m->input_type[g_page.cursor] = INPUT_TYPE_ADC;
}
/* 下一步 */
if (Input_IsKeyPressed(KEY_OK) && g_page.cursor == CHANNEL_COUNT) {
g_page.page_id = PAGE_MIXER;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 混控列表 ---- */
void Menu_DrawMixer(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Mixer", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = CHANNEL_COUNT + 1;
uint8_t start = g_page.scroll_offset;
for (uint8_t i = start; i < start + 4 && i < CHANNEL_COUNT; i++) {
uint8_t y = 20 + (i - start) * 10;
char buf[20];
snprintf(buf, sizeof(buf), "%s[CH%d]: %4d",
i == g_page.cursor ? ">" : " ",
i + 1, g_channels[i]);
OLED_ShowString(0, y, buf, 6);
}
if (g_page.cursor >= CHANNEL_COUNT - 4) {
OLED_ShowString(40, 56, "[Next]", 6);
}
OLED_Display();
Menu_Navigate();
if (Input_IsKeyPressed(KEY_OK) && g_page.cursor < CHANNEL_COUNT) {
g_page.page_id = PAGE_MIXER_EDIT;
g_page.edit_idx = g_page.cursor;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 混控编辑 ---- */
void Menu_DrawMixerEdit(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Mixer Edit", 16);
OLED_DrawLine(0, 17, 127, 17);
Model_t *m = Storage_GetActiveModel();
uint8_t ch = g_page.edit_idx;
char buf[32];
snprintf(buf, sizeof(buf), "CH%d Mixer", ch + 1);
OLED_ShowString(0, 20, buf, 6);
/* 混控类型 */
static const char *mixer_names[] = {"None", "Simple", "Delta", "V-Tail", "Elevon"};
snprintf(buf, sizeof(buf), "Type: %s", mixer_names[m->mixer_type[ch]]);
OLED_ShowString(0, 30, buf, 6);
/* 比例 */
snprintf(buf, sizeof(buf), "Scale: %d%%", m->mixer_scale[ch]);
OLED_ShowString(0, 40, buf, 6);
/* 偏移 */
snprintf(buf, sizeof(buf), "Offset: %d", m->mixer_offset[ch]);
OLED_ShowString(0, 50, buf, 6);
OLED_Display();
uint8_t key = Input_GetKey();
if (key == KEY_UP) {
if (g_page.cursor == 0) {
m->mixer_type[ch]++;
if (m->mixer_type[ch] > MIXER_TYPE_ELEVON) m->mixer_type[ch] = MIXER_TYPE_NONE;
} else if (g_page.cursor == 1) {
m->mixer_scale[ch] += 5;
if (m->mixer_scale[ch] > 200) m->mixer_scale[ch] = 200;
} else if (g_page.cursor == 2) {
m->mixer_offset[ch] += 10;
}
} else if (key == KEY_DOWN) {
if (g_page.cursor == 0) {
if (m->mixer_type[ch] > 0) m->mixer_type[ch]--;
} else if (g_page.cursor == 1) {
m->mixer_scale[ch] -= 5;
if (m->mixer_scale[ch] < 0) m->mixer_scale[ch] = 0;
} else if (g_page.cursor == 2) {
m->mixer_offset[ch] -= 10;
}
} else if (key == KEY_OK) {
g_page.cursor++;
if (g_page.cursor > 2) g_page.cursor = 0;
} else if (key == KEY_BACK) {
g_page.page_id = PAGE_MIXER;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 高频头配置 ---- */
void Menu_DrawRFConfig(void) {
OLED_Clear();
OLED_ShowString(0, 0, "RF Config", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 2;
OLED_ShowString(0, 22, g_page.cursor == 0 ? ">[NRF24]" : " [NRF24]", 6);
OLED_ShowString(0, 34, g_page.cursor == 1 ? ">[CRFS]" : " [CRFS]", 6);
OLED_Display();
Menu_Navigate();
if (Input_IsKeyPressed(KEY_OK)) {
if (g_page.cursor == 0) {
g_config.rf_type = 0;
g_page.page_id = PAGE_NRF_CONFIG;
} else {
g_config.rf_type = 1;
g_page.page_id = PAGE_CRFS_CONFIG;
}
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- NRF24 配置 ---- */
void Menu_DrawNRFConfig(void) {
OLED_Clear();
OLED_ShowString(0, 0, "NRF24 Config", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 5;
char buf[32];
snprintf(buf, sizeof(buf), "%sPhrase: %s",
g_page.cursor == 0 ? ">" : " ", g_config.nrf_phrase);
OLED_ShowString(0, 20, buf, 6);
snprintf(buf, sizeof(buf), "%sChannel: %d",
g_page.cursor == 1 ? ">" : " ", g_config.nrf_channel);
OLED_ShowString(0, 30, buf, 6);
snprintf(buf, sizeof(buf), "%sAddr: %02X%02X%02X%02X%02X",
g_page.cursor == 2 ? ">" : " ",
g_config.nrf_addr[0], g_config.nrf_addr[1],
g_config.nrf_addr[2], g_config.nrf_addr[3],
g_config.nrf_addr[4]);
OLED_ShowString(0, 40, buf, 6);
static const char *rates[] = {"250K", "1M", "2M"};
snprintf(buf, sizeof(buf), "%sRate: %s",
g_page.cursor == 3 ? ">" : " ", rates[g_config.nrf_rate]);
OLED_ShowString(0, 50, buf, 6);
snprintf(buf, sizeof(buf), "%sPower: %d",
g_page.cursor == 4 ? ">" : " ", g_config.nrf_power);
OLED_ShowString(0, 60, buf, 6);
OLED_Display();
Menu_Navigate();
}
/* ---- CRFS 配置 ---- */
void Menu_DrawCRFSConfig(void) {
OLED_Clear();
OLED_ShowString(0, 0, "CRFS Config", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 3;
char buf[32];
snprintf(buf, sizeof(buf), "%sPkt Rate: %dHz",
g_page.cursor == 0 ? ">" : " ", g_config.crfs_packet_rate);
OLED_ShowString(0, 22, buf, 6);
snprintf(buf, sizeof(buf), "%sTelem: 1:%d",
g_page.cursor == 1 ? ">" : " ", g_config.crfs_telemetry_ratio);
OLED_ShowString(0, 34, buf, 6);
snprintf(buf, sizeof(buf), "%sPower: %dmW",
g_page.cursor == 2 ? ">" : " ", g_config.crfs_power);
OLED_ShowString(0, 46, buf, 6);
OLED_Display();
Menu_Navigate();
}
/* ---- PWM 配置 ---- */
void Menu_DrawPWMConfig(void) {
OLED_Clear();
OLED_ShowString(0, 0, "PWM Config", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 5;
static const char *pwm_modes[] = {"Disabled", "WS2812", "LED", "Vibrate"};
char buf[32];
snprintf(buf, sizeof(buf), "%sPWM1: %s",
g_page.cursor == 0 ? ">" : " ", pwm_modes[g_config.pwm1_mode]);
OLED_ShowString(0, 20, buf, 6);
snprintf(buf, sizeof(buf), "%sPWM2: %s",
g_page.cursor == 1 ? ">" : " ", pwm_modes[g_config.pwm2_mode]);
OLED_ShowString(0, 30, buf, 6);
snprintf(buf, sizeof(buf), "%sWS2812 Effect: %d",
g_page.cursor == 2 ? ">" : " ", g_config.ws2812_effect);
OLED_ShowString(0, 40, buf, 6);
snprintf(buf, sizeof(buf), "%sLED Bright: %d%%",
g_page.cursor == 3 ? ">" : " ", g_config.led_brightness);
OLED_ShowString(0, 50, buf, 6);
snprintf(buf, sizeof(buf), "%sVibrate: Lv%d",
g_page.cursor == 4 ? ">" : " ", g_config.vibrate_level);
OLED_ShowString(0, 60, buf, 6);
OLED_Display();
Menu_Navigate();
}
/* ---- 首页设置 ---- */
void Menu_DrawHomeSet(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Home Page", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 2;
OLED_ShowString(0, 25, g_page.cursor == 0 ? ">Telemetry" : " Telemetry", 6);
OLED_ShowString(0, 37, g_page.cursor == 1 ? ">INx Values" : " INx Values", 6);
OLED_Display();
Menu_Navigate();
if (Input_IsKeyPressed(KEY_OK)) {
g_config.home_page = g_page.cursor;
g_page.page_id = PAGE_MAIN_MENU;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 对频页 ---- */
void Menu_DrawBind(void) {
OLED_Clear();
OLED_ShowString(0, 0, "Binding...", 16);
OLED_DrawLine(0, 17, 127, 17);
OLED_ShowString(0, 30, "Scanning channels", 6);
OLED_DrawProgressBar(10, 45, 108, 8, 50); /* 示例进度 */
OLED_Display();
Menu_Navigate();
}
/* ---- 高频头快捷页 ---- */
void Menu_DrawRFQuick(void) {
OLED_Clear();
if (g_config.rf_type == 0) {
OLED_ShowString(0, 0, "NRF24 Quick", 16);
OLED_DrawLine(0, 17, 127, 17);
g_page.item_count = 3;
char buf[32];
snprintf(buf, sizeof(buf), "%s[Bind]",
g_page.cursor == 0 ? ">" : " ");
OLED_ShowString(0, 22, buf, 6);
snprintf(buf, sizeof(buf), "%sCh: %d",
g_page.cursor == 1 ? ">" : " ", g_config.nrf_channel);
OLED_ShowString(0, 34, buf, 6);
snprintf(buf, sizeof(buf), "%sAddr: %02X%02X%02X%02X%02X",
g_page.cursor == 2 ? ">" : " ",
g_config.nrf_addr[0], g_config.nrf_addr[1],
g_config.nrf_addr[2], g_config.nrf_addr[3],
g_config.nrf_addr[4]);
OLED_ShowString(0, 46, buf, 6);
} else {
OLED_ShowString(0, 0, "CRFS Quick", 16);
OLED_DrawLine(0, 17, 127, 17);
OLED_ShowString(0, 30, "CRFS mode active", 6);
}
OLED_Display();
Menu_Navigate();
if (Input_IsKeyPressed(KEY_OK) && g_page.cursor == 0) {
/* 进入对频模式 */
g_page.page_id = PAGE_BIND;
g_page.cursor = 0;
g_page.item_count = 0;
}
if (Input_IsKeyPressed(KEY_BACK)) {
g_page.page_id = PAGE_HOME;
g_page.cursor = 0;
g_page.item_count = 0;
}
}
/* ---- 主运行循环 ---- */
void Menu_Run(void) {
switch (g_page.page_id) {
case PAGE_HOME: Menu_DrawHome(); break;
case PAGE_MAIN_MENU: Menu_DrawMainMenu(); break;
case PAGE_MODEL_LIST: Menu_DrawModelList(); break;
case PAGE_MODEL_NEW: Menu_DrawModelNew(); break;
case PAGE_CHANNEL_MAP: Menu_DrawChannelMap(); break;
case PAGE_MIXER: Menu_DrawMixer(); break;
case PAGE_MIXER_EDIT: Menu_DrawMixerEdit(); break;
case PAGE_RF_CONFIG: Menu_DrawRFConfig(); break;
case PAGE_NRF_CONFIG: Menu_DrawNRFConfig(); break;
case PAGE_CRFS_CONFIG: Menu_DrawCRFSConfig(); break;
case PAGE_PWM_CONFIG: Menu_DrawPWMConfig(); break;
case PAGE_HOME_SET: Menu_DrawHomeSet(); break;
case PAGE_BIND: Menu_DrawBind(); break;
case PAGE_RF_QUICK: Menu_DrawRFQuick(); break;
default: g_page.page_id = PAGE_HOME; break;
}
}