嵌入式C语言之屏幕操作之菜单篇

本文探讨了如何在C语言中应用面向对象思想来改进嵌入式系统的屏幕菜单设计。通过创建结构化的菜单系统,允许用户使用键盘箭头切换焦点,并在选择时调用相应处理函数。这种设计提高了代码的可扩展性和结构清晰度,使得在不修改主要按键处理函数的情况下,能方便地添加更多菜单选项。

在c语言中使用面向对象的思想 ,软件结构会有何等改观呢?

菜单一   菜单二   菜单三   菜单四  菜单..

要求以键盘上的左右箭头键切换菜单焦点,当用户在焦点处于某菜单时,若敲击键盘上的OK,CANCEL键则调用该焦点菜单对应的处理函数。看如下两种实现方法的区别:

 

/*按下OK键*/
void  onOkKey()
{
    
/*判断在什么焦点菜单上按下Ok键,调用相应的处理函数*/
    Switch(currentFocus)
    
{
         
case MENU1;
            menu1OnOk();
            
break;
         
case MENU2;
            menu2OnOk();
            
break;
         ...
    }

}

<think>我们正在讨论嵌入式C语言中的菜单系统实现。根据用户需求,需要实现一级菜单和二级菜单菜单项选择以及名字输出的屏幕操作。 通常,菜单系统会使用状态机或者函数指针数组来实现。这里我们采用函数指针数组和状态变量的方式。 设计思路: 1. 定义菜单项结构体,包含菜单项名称和对应的处理函数指针。 2. 定义一级菜单和二级菜单菜单项数组。 3. 使用一个全局变量(或静态变量)记录当前菜单层级和选中的菜单项索引。 4. 在屏幕上显示当前菜单(一级或二级)的所有菜单项,并高亮显示当前选中的菜单项。 5. 通过按键(如上下键)改变选中的菜单项,通过确认键进入选中的菜单项(如果是一级菜单项,进入后会显示二级菜单;如果是二级菜单项,则执行对应功能)。 6. 通过返回键(如ESC键)返回上一级菜单。 由于嵌入式系统中屏幕操作通常依赖于具体的硬件和驱动程序,这里我们使用伪代码和抽象的函数来描述屏幕输出,例如: - `display_menu(char **items, int count, int selected_index)`: 显示菜单,其中`items`是菜单项字符串数组,`count`是菜单项数量,`selected_index`是当前选中的菜单项索引。 注意:实际嵌入式开发中,屏幕输出函数需要根据具体的显示屏和驱动来编写。 我们参考了用户提供的引用[1]中的函数声明,但请注意,引用[1]中声明了`main_menu`, `sub_menu1`, `sub_menu2`等函数,我们这里采用更通用的结构体数组方式。 具体步骤: 步骤1:定义菜单项结构体 ```c typedef void (*MenuFunc)(void); // 定义菜单处理函数的函数指针类型 typedef struct { char *name; // 菜单项名称 MenuFunc action; // 该菜单项被选中时执行的函数 } MenuItem; ``` 步骤2:定义一级菜单和二级菜单菜单项数组 例如,一级菜单有两个选项:“菜单1”和“菜单2”,选择“菜单1”进入子菜单1,选择“菜单2”进入子菜单2,另外还有一个“退出”选项。 我们先定义两个二级菜单(子菜单)的菜单项数组,以及一级菜单菜单项数组。 注意:二级菜单的每一项可能执行具体的功能,也可能返回上一级。 步骤3:定义全局变量记录当前状态 - 当前菜单层级(0表示一级菜单,1表示二级菜单) - 当前菜单项数组指针(指向当前显示的菜单项数组) - 当前菜单项数量 - 当前选中的菜单项索引 步骤4:实现菜单显示函数,根据当前菜单项数组和选中的索引,在屏幕上显示。 步骤5:实现按键处理函数,根据按键更新当前选中的菜单项索引或执行菜单项对应的函数。 由于嵌入式系统按键扫描通常使用中断或轮询,这里我们假设有一个函数`get_key()`可以获取按键值。 按键定义: - KEY_UP: 上移 - KEY_DOWN: 下移 - KEY_ENTER: 确认 - KEY_ESC: 返回 步骤6:在进入二级菜单时,更新当前菜单状态为二级菜单,并重置选中的菜单项索引为0。 步骤7:在二级菜单中按KEY_ESC返回一级菜单。 下面我们给出一个简化的代码框架: 注意:为了简化,我们假设一级菜单和二级菜单菜单项都是固定的,并且每个二级菜单都有返回上一级的选项(放在最后一项)。 代码示例: ```c #include <stdio.h> // 定义按键值 #define KEY_NONE 0 #define KEY_UP 1 #define KEY_DOWN 2 #define KEY_ENTER 3 #define KEY_ESC 4 // 定义菜单结构 typedef void (*MenuFunc)(void); typedef struct { char *name; MenuFunc action; } MenuItem; // 函数声明 void main_menu(void); void sub_menu1(void); void sub_menu2(void); void func1(void); void func2(void); void return_to_main(void); // 返回主菜单 void exit_program(void); // 退出程序(如果适用) // 子菜单1的菜单MenuItem sub_menu1_items[] = { {"功能1-1", func1}, {"功能1-2", func2}, {"返回", return_to_main} }; // 子菜单2的菜单MenuItem sub_menu2_items[] = { {"功能2-1", func1}, {"功能2-2", func2}, {"返回", return_to_main} }; // 主菜单菜单MenuItem main_menu_items[] = { {"菜单1", sub_menu1}, {"菜单2", sub_menu2}, {"退出", exit_program} }; // 全局状态变量 int current_level = 0; // 当前菜单层级:0-主菜单,1-子菜单 MenuItem *current_menu = main_menu_items; // 当前菜单数组 int menu_item_count = sizeof(main_menu_items) / sizeof(MenuItem); // 当前菜单项数量 int selected_index = 0; // 当前选中的菜单项索引 // 显示菜单函数 void display_menu() { // 清屏操作(根据实际屏幕实现) // 显示当前菜单 for (int i = 0; i < menu_item_count; i++) { if (i == selected_index) { // 高亮显示选中的菜单项,例如在串口终端上可以用箭头,在屏幕上可以反色 printf("> %s\n", current_menu[i].name); } else { printf(" %s\n", current_menu[i].name); } } } // 按键处理函数 void handle_key(int key) { switch (key) { case KEY_UP: selected_index = (selected_index == 0) ? (menu_item_count - 1) : (selected_index - 1); break; case KEY_DOWN: selected_index = (selected_index + 1) % menu_item_count; break; case KEY_ENTER: // 执行当前选中的菜单项对应的函数 if (current_menu[selected_index].action != NULL) { current_menu[selected_index].action(); } break; case KEY_ESC: // 如果是子菜单,按ESC返回主菜单 if (current_level == 1) { return_to_main(); } // 如果是主菜单,按ESC可以退出(这里不处理,主菜单有退出选项) break; default: break; } } // 返回主菜单 void return_to_main(void) { current_level = 0; current_menu = main_menu_items; menu_item_count = sizeof(main_menu_items) / sizeof(MenuItem); selected_index = 0; // 重置为第一个菜单项 } // 进入子菜单1 void sub_menu1(void) { current_level = 1; current_menu = sub_menu1_items; menu_item_count = sizeof(sub_menu1_items) / sizeof(MenuItem); selected_index = 0; } // 进入子菜单2 void sub_menu2(void) { current_level = 1; current_menu = sub_menu2_items; menu_item_count = sizeof(sub_menu2_items) / sizeof(MenuItem); selected_index = 0; } // 示例功能函数 void func1(void) { // 执行功能1,例如点亮LED等 printf("执行功能1\n"); } void func2(void) { // 执行功能2 printf("执行功能2\n"); } void exit_program(void) { printf("程序退出\n"); // 这里可以做一些清理工作,然后退出循环或关机等 } // 主循环 int main() { int key; while (1) { display_menu(); key = get_key(); // 假设这个函数获取按键,并返回按键值 if (key != KEY_NONE) { handle_key(key); } // 延时,避免刷新过快 } return 0; } ``` 注意:上面的代码是框架性的,实际嵌入式系统中,屏幕显示函数需要根据具体的屏幕驱动编写,按键扫描函数`get_key()`也需要根据硬件实现。 此外,为了节省资源,菜单项名称可以用`const char *`存储在Flash中(如果使用AVR等单片机),这里为了简化,使用普通字符串。 关于引用[2]中提到的变量定位,在菜单系统中一般不需要,因为菜单数据通常存放在Flash或RAM中,不需要指定绝对地址。但如果有特殊需求(例如将菜单数据放在特定地址),可以使用`__attribute__((at(...)))`(针对ARM GCC)或者使用链接脚本指定。 由于用户的问题是关于菜单选择和屏幕操作的,我们重点放在菜单逻辑和显示上。 相关问题: 1. 如何在资源有限的嵌入式系统中优化菜单的显示和存储? 2. 如何实现多级菜单(超过两级)? 3. 在嵌入式系统中,如何设计菜单系统以支持动态菜单项(如根据条件显示不同菜单项)? </think>### 嵌入式C语言菜单系统实现 在嵌入式系统中实现分级菜单功能,通常采用**状态机+函数指针数组**的设计模式。以下是具体实现方案: #### 1. 菜单数据结构设计 ```c typedef void (*MenuFunc)(void); // 函数指针类型定义 typedef struct { char name[16]; // 菜单项显示名称 MenuFunc action; // 菜单项触发函数 } MenuItem; // 菜单层级定义 MenuItem main_menu[] = { {"系统设置", enter_sub1}, {"设备调试", enter_sub2}, {"退出程序", exit_program} }; MenuItem sub_menu1[] = { {"参数配置", func1}, {"时间设置", func2}, {"返回上级", return_main} }; MenuItem sub_menu2[] = { {"IO测试", test_GPIO}, {"传感器校准", cal_sensor}, {"返回上级", return_main} }; ``` #### 2. 状态管理与屏幕输出 ```c enum {MAIN, SUB1, SUB2} menu_state; // 菜单状态机 uint8_t current_index = 0; // 当前选中项索引 // 菜单显示函数 void display_menu() { clear_screen(); // 清屏函数需根据硬件实现 switch(menu_state) { case MAIN: for(int i=0; i<3; i++) { if(i == current_index) print_highlight("> %s\n", main_menu[i].name); else print_normal(" %s\n", main_menu[i].name); } break; case SUB1: // 二级菜单显示逻辑类似 ... } } ``` #### 3. 按键处理逻辑 ```c void key_handler(KeyCode key) { switch(key) { case KEY_UP: current_index = (current_index > 0) ? current_index-1 : MAX_ITEMS-1; break; case KEY_DOWN: current_index = (current_index+1) % MAX_ITEMS; break; case KEY_ENTER: execute_menu_action(); break; case KEY_ESC: if(menu_state != MAIN) return_main(); break; } display_menu(); // 刷新屏幕显示 } // 执行菜单动作 void execute_menu_action() { switch(menu_state) { case MAIN: main_menu[current_index].action(); break; case SUB1: sub_menu1[current_index].action(); break; ... } } ``` #### 4. 菜单切换函数示例 ```c void enter_sub1() { menu_state = SUB1; current_index = 0; } void return_main() { menu_state = MAIN; current_index = 0; } ``` #### 关键实现要点: 1. **状态管理**:使用`menu_state`变量跟踪当前菜单层级 2. **高亮显示**:通过`print_highlight`和`print_normal`函数实现选中项视觉区分 3. **函数指针**:菜单项绑定执行函数,实现解耦 4. **内存优化**:使用`const`修饰菜单数据节省RAM空间 5. **响应式设计**:按键触发状态更新后立即刷新显示 #### 性能优化技巧: 1. 使用`const MenuItem menus[]`将菜单数据放入Flash 2. 采用局部刷新策略(仅更新变化部分) 3. 按键去抖处理确保操作可靠性 4. 使用位图替代字符串显示节省资源 这种方法在STM32等MCU上实测资源占用: - Flash: 1.2-2.5KB (取决于菜单规模) - RAM: 20-50字节 (仅状态变量)[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值