Add core firmware modules: input, menu, NRF24L01, OLED, protocol, PWM, storage + UI assets
This commit is contained in:
738
Core/Src/menu.c
Normal file
738
Core/Src/menu.c
Normal file
@@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 菜单系统实现
|
||||
* 基于 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_IsKeyHeld(KEY_BACK)) {
|
||||
/* TODO: 进入休眠模式 */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 主菜单 ---- */
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user