You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C语言优雅实现带键盘与LCD的嵌入式应用多场景交互?

嵌入式C应用的可扩展按键与LCD场景处理方案

针对你提到的按键、场景数量增长后条件判断过于繁琐的问题,以下是两种嵌入式场景下实用且可扩展的编码方案:

1. 有限状态机(FSM)+ 表驱动架构

这是嵌入式系统中最常用的可扩展模式,核心是把每个LCD场景抽象为状态,按键操作作为事件,通过状态-事件映射表处理逻辑,彻底避免嵌套判断。

核心类型定义

先统一枚举状态、按键类型,定义处理函数的原型:

// 每个LCD场景对应一个状态枚举
typedef enum {
    STATE_MAIN_SCREEN,
    STATE_MENU_MAIN,
    STATE_MENU_SUB_OPTION1,
    STATE_MENU_SUB_OPTION2,
    // 新增场景直接追加枚举值即可
    STATE_MAX
} AppState;

// 按键枚举
typedef enum {
    KEY_MENU,
    KEY_UP,
    KEY_DOWN,
    KEY_LEFT,
    KEY_RIGHT,
    KEY_SELECT,
    KEY_EXIT,
    // 新增按键直接追加枚举值
    KEY_MAX
} KeyCode;

// 按键处理函数原型:输入当前状态和按键,返回切换后的状态
typedef AppState (*KeyHandler)(AppState current_state, KeyCode key);

状态-事件映射表

将所有状态下的按键逻辑整理成表格,主循环只需查表执行:

// 状态-按键映射表结构体
typedef struct {
    AppState current_state;
    KeyCode key;
    KeyHandler handler;
} KeyActionTable;

// 具体按键处理函数实现
static AppState handle_main_screen_menu(AppState s, KeyCode k) {
    lcd_clear();
    lcd_draw_main_menu();
    return STATE_MENU_MAIN;
}

static AppState handle_menu_main_up(AppState s, KeyCode k) {
    menu_navigate_up();
    lcd_update_menu_highlight();
    return STATE_MENU_MAIN;
}

static AppState handle_menu_main_select(AppState s, KeyCode k) {
    lcd_clear();
    return (menu_get_selected_idx() == 0) 
        ? (lcd_draw_option1_content(), STATE_MENU_SUB_OPTION1)
        : (lcd_draw_option2_content(), STATE_MENU_SUB_OPTION2);
}

// 填充映射表:新增逻辑只需追加条目
static const KeyActionTable key_action_table[] = {
    {STATE_MAIN_SCREEN, KEY_MENU, handle_main_screen_menu},
    {STATE_MENU_MAIN, KEY_UP, handle_menu_main_up},
    {STATE_MENU_MAIN, KEY_DOWN, handle_menu_main_down},
    {STATE_MENU_MAIN, KEY_SELECT, handle_menu_main_select},
    {STATE_MENU_MAIN, KEY_EXIT, handle_menu_main_exit},
    {STATE_MENU_SUB_OPTION1, KEY_EXIT, handle_sub_option_exit},
};

#define ACTION_TABLE_SIZE (sizeof(key_action_table)/sizeof(KeyActionTable))

主循环简化逻辑

int main(void) {
    AppState current_state = STATE_MAIN_SCREEN;
    lcd_init();
    keyboard_init();
    lcd_draw_main_screen();

    while(1) {
        KeyCode key = keyboard_get_key(); // 已完成消抖的按键读取
        if (key != KEY_MAX) {
            // 查表执行对应逻辑
            for (int i=0; i<ACTION_TABLE_SIZE; i++) {
                if (key_action_table[i].current_state == current_state &&
                    key_action_table[i].key == key) {
                    current_state = key_action_table[i].handler(current_state, key);
                    break;
                }
            }
        }
        // 后台任务(如传感器采集、通信等)
        run_background_tasks();
    }
}

2. 场景对象化封装(高灵活扩展)

如果每个场景的逻辑复杂(包含独立初始化、显示更新、按键处理),可以将场景封装为结构体,实现类似"对象"的模块化管理:

// 场景结构体:包含该场景的所有操作接口
typedef struct {
    void (*init)(void);          // 进入场景时的初始化
    void (*handle_key)(KeyCode); // 按键处理逻辑
    void (*update_display)(void);// 可选:实时显示更新
} Scene;

// 主界面场景实现
static void main_screen_init(void) {
    lcd_clear();
    lcd_draw_main_screen();
}

static void main_screen_handle_key(KeyCode key) {
    if (key == KEY_MENU) {
        current_scene = &menu_main_scene;
        current_scene->init();
    }
}

static const Scene main_screen_scene = {
    .init = main_screen_init,
    .handle_key = main_screen_handle_key,
    .update_display = NULL
};

// 主菜单场景实现
static void menu_main_init(void) {
    lcd_clear();
    lcd_draw_main_menu();
    menu_reset_nav_idx();
}

static void menu_main_handle_key(KeyCode key) {
    switch(key) {
        case KEY_UP: menu_navigate_up(); break;
        case KEY_DOWN: menu_navigate_down(); break;
        case KEY_SELECT: 
            current_scene = (menu_get_selected_idx() == 0) 
                ? &sub_option1_scene 
                : &sub_option2_scene;
            current_scene->init();
            break;
        case KEY_EXIT:
            current_scene = &main_screen_scene;
            current_scene->init();
            break;
        default: break;
    }
    lcd_update_menu_highlight();
}

static const Scene menu_main_scene = {
    .init = menu_main_init,
    .handle_key = menu_main_handle_key,
    .update_display = NULL
};

// 全局当前场景指针
static const Scene* current_scene = &main_screen_scene;

// 主循环逻辑
int main(void) {
    lcd_init();
    keyboard_init();
    current_scene->init();

    while(1) {
        KeyCode key = keyboard_get_key();
        if (key != KEY_MAX) {
            current_scene->handle_key(key);
        }
        // 若场景需要实时更新显示
        if (current_scene->update_display) {
            current_scene->update_display();
        }
        run_background_tasks();
    }
}

额外优化建议

  • 按键队列优化:将按键输入改为队列模式,后台线程处理硬件消抖并压入队列,主循环从队列取按键,避免丢键或重复触发。
  • 查表性能优化:如果状态和按键数量较多,可将映射表改为二维数组(状态为行、按键为列),直接通过索引访问,将查表复杂度降为O(1):
    static const KeyHandler state_key_handlers[STATE_MAX][KEY_MAX] = {
        [STATE_MAIN_SCREEN][KEY_MENU] = handle_main_screen_menu,
        [STATE_MENU_MAIN][KEY_UP] = handle_menu_main_up,
        // 其他映射关系
    };
    // 主循环中直接调用:
    if (state_key_handlers[current_state][key]) {
        current_state = state_key_handlers[current_state][key](current_state, key);
    }
    
  • 场景复用:对于逻辑相似的子场景,可复用同一个Scene结构体,通过全局变量或参数传递区分具体选项。

内容的提问来源于stack exchange,提问作者Aravind D. Chakravarti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 12:26:32