一个开源经典的MCU菜单框架设计

本文介绍了如何设计和优化单色液晶屏的菜单系统,从早期的树形结构到简化后的结构体列表,强调了菜单系统应易于维护而非追求极致效率。作者提出新的菜单结构体,通过列表方式管理菜单项,简化了菜单的维护工作,并提供了菜单的实现效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来源:嵌入式专栏

不知道有多少人折腾过液晶显示的菜单,我觉得很多人都应该搞过,我还记得以前大学参加电子设计竞赛获奖的作品,我就用到了一个12864,里面有菜单功能。

以前可能觉得菜单高大上,其实并不是想象中的复杂,本文为大家分享一个用单色屏做的菜单框架。

技术让梦想更伟大

1

概述

代码托管在github:

https://github.com/wujique/stm32f407/tree/sw_arch 

本处所说的菜单是用在128*64这种小屏幕的菜单,例如下面这种,不是彩屏上的GUI。

885f93c725e85042ab8e55c13c0f9d88.png

技术让梦想更伟大

2

菜单框架设计

作为一个底层驱动工程师,驱动写完了,是要写硬件测试程序的。这个测试程序,一般给测试部/硬件工程师用来测试硬件, 也会给工厂产线测试准成品。

开始的人偷懒,不想一秒就直接上,所有菜单都这样做,一层套一层

void test_main(void)
{
        while(1)
        {
                get_key(&key);
                switch(key)
                {
                        case 1:
                                test_key();
                                break;
                        case 2:
                                test_lcd();
                                break;
                        ....
                }
        }
}

当菜单越来越多,就开始纠结了,这样写维护不便,看起来也不美,还浪费程序空间。

作为一个天天看《编程之美》的码农,决定改变现状。酷狗百度一番,找到了两个参考:《基于二叉树的多层的液晶菜单界面设计》 《基于节点编号的通用树状菜单设计方法与实现.pdf》 按照他们的设计方法,鼓捣了一个版本,能用,挺好,但是也纠结。因为他们用了树这种数据结构。对于程序运行来说,非常好,效率高。但是对于我来说,菜单代码是一次性的,但是菜单内容,却是会经常改的。让我用人脑去维护一个包含几十个上百个菜单的树,不容易。

想来想去,这些菜单到底有什么不好?对于我来说,为什么不好用?得出下面结论:

  1. 管得太宽 菜单,你就管菜单切换就行了,到了最低一层,也就是实际的测试功能,就不要管了。菜单切换是类似的,实际测试都是不同的。比如在菜单中,按键1,是进入第一个菜单。但是在测试中,按键1,功能都不一样。如果菜单连这个也要管,相同动作功能太多,无法进行统一抽象,就很难模块化。

  2. 出发点不一样 上面说到的菜单,出发点都是如何设计一个好的菜单数据结构,让程序快速,高效运行。我想要的却是一个容易维护的菜单结构,至于菜单的代码有多乱多纠结,没关系, 而且,几百上千个菜单,就算用轮询的方法,也不过几百us吧,没关系。

技术让梦想更伟大

3

改进菜单

根据需求,我重新设计了一个菜单结构体

/**
 * @brief  菜单对象
*/
typedef struct _strMenu
{
    MenuLel l;     ///<菜单等级
    char cha[MENU_LANG_BUF_SIZE];   ///中文
    char eng[MENU_LANG_BUF_SIZE];   ///英文
    MenuType type;  ///菜单类型
    s32 (*fun)(void);  ///测试函数

} MENU;

是的,就这么简单,每一个菜单都是这个结构体 用这个结构体填充一个列表,就是我们的菜单了

const MENU EMenuListTest[]=
{
        MENU_L_0,//菜单等级
        "测试程序",//中文
        "test",        //英文
        MENU_TYPE_LIST,//菜单类型
        NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                MENU_L_1,//菜单等级
                "LCD",//中文
                "LCD",        //英文
                MENU_TYPE_LIST,//菜单类型
                NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
                        MENU_L_2,//菜单等级
                        "VSPI OLED",//中文
                        "VSPI OLED",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_oled,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                        MENU_L_2,//菜单等级
                        "I2C OLED",//中文
                        "I2C OLED",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_i2coled,//菜单函数,功能菜单才会执行,有子菜单的不会执行


                MENU_L_1,//菜单等级
                "声音",//中文
                "sound",        //英文
                MENU_TYPE_LIST,//菜单类型
                NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
                        MENU_L_2,//菜单等级
                        "蜂鸣器",//中文
                        "buzzer",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                        MENU_L_2,//菜单等级
                        "DAC音乐",//中文
                        "DAC music",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                        MENU_L_2,//菜单等级
                        "收音",//中文
                        "FM",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行


                MENU_L_1,//菜单等级
                "触摸屏",//中文
                "tp",        //英文
                MENU_TYPE_LIST,//菜单类型
                NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                        MENU_L_2,//菜单等级
                        "校准",//中文
                        "calibrate",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_cal,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                        MENU_L_2,//菜单等级
                        "测试",//中文
                        "test",        //英文
                        MENU_TYPE_FUN,//菜单类型
                        test_tp,//菜单函数,功能菜单才会执行,有子菜单的不会执行

                MENU_L_1,//菜单等级
                "按键",//中文
                "KEY",        //英文
                MENU_TYPE_FUN,//菜单类型
                test_key,//菜单函数,功能菜单才会执行,有子菜单的不会执行

        /*最后的菜单是结束菜单,无意义*/                        
        MENU_L_0,//菜单等级
        "END",//中文
        "END",        //英文
        MENU_TYPE_NULL,//菜单类型
        NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
};

这个菜单列表有什么特点和要求呢?1 需要一个根节点和结束节点 2 子节点必须跟父节点,类似下面结构

关注公众号:Java项目精选,回复:666领取资料 。

-----------------------------------------------
根节点
        第1个1级菜单
                       第1个子菜单
                       第2个子菜单
                       第3个子菜单
        第2个1级菜单
                       第1个子菜单
                                     第1个孙菜单
                                     第2个孙菜单
                       第2个子菜单
                       第3个子菜单
        第3个1级菜单
        第4个1级菜单
        第5个1级菜单
结束节点
------------------------------------------------

第2个1级菜单有3个子菜单,子菜单是2级菜单,其中第1个子菜单下面又有2个孙菜单(3级菜单)。

维护菜单,就是维护这个列表,添加删除修改,非常容易。那菜单程序怎么样呢?管他呢。定义好菜单后,通过下面函数运行菜单,

emenu_run(WJQTestLcd, (MENU *)&WJQTestList[0], sizeof(WJQTestList)/sizeof(MENU), FONT_SONGTI_1616, 2);

-第1个参数是在哪个LCD上显示菜单, -第2个是菜单列表, -第3个是菜单长度, -第4个四字体, -第5则是行间距

注意:运行这个菜单需要有rtos,因为菜单代码是while(1)的,陷进去就不出来了。需要有其他线程(TASK)维护系统,例如按键扫描。

技术让梦想更伟大

4

菜单实现效果

相关文件:emenu.c、emenu.h、emenu_test.c

当前代码: 

1实现了双列菜单,用数字键选择进入下一层。每页最多显示8个菜单(4*4键盘用1-8键)

2 实现了单列菜单,通过上下翻查看菜单,确认键进入菜单。3 天顶菜单未实现,谁有兴趣可以加上。

3 基于LCD驱动架构,这个简易菜单自适应于多种LCD。

效果如下,有需要的尽管拿去,不用谢。

显示效果

128*64 OLED

db05bbc9f9e0cc8838c4926fcfccef0f.png

a5e8a3af298f27b245866b8011755ce0.png

128*128 tft lcd

466b8dfda808bd15de478ec0f3bda8c5.png

6c5b1c1ec0aca241435117d4dabe9185.png

320*240 tft lcd

e6cebb64568c818b81386c06f4c4d8aa.png

1f139ea3cfa172a4c175cafa7fd3d16d.png

技术让梦想更伟大

5

最后说明

以上菜单框架来源屋脊雀工作室,适合初学者练习。我看下这个菜单框架,其实还有很多改进地方。

我当初大学电子设计竞赛用到类似结构体方式,但我那菜单框架用到了二级指针,可以做到无限极扩展,而且可以指向(跳转)任意菜单,方便按键进入、返回等操作。

本文就分享到这里,感兴趣的读者可以自己写一个菜单框架。

免责声明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。

欢迎关注我的视频号:

### 如何使用英飞凌ADS创建针对TC377微控制器的工程 #### 工程创建流程 在英飞凌 ADS (Aurix Development Studio) 中创建一个适用于 TC377 微控制器的新工程涉及多个步骤,这些步骤涵盖了硬件抽象层 (HAL) 配置、外设初始化以及软件框架的选择。以下是关于如何完成这一过程的关键点: 1. **安装并启动 Aurix Development Studio** 安装完成后,在 ADS 中打开一个新的工作区,并通过菜单选项 `File -> New -> Infineon Project` 来创建新项目[^5]。 2. **选择目标设备** 在弹出的向导窗口中,选择目标微控制器型号为 TC377。这一步会自动加载与该芯片相关的配置文件和支持包。 3. **设置编译环境** 确保选择了合适的工具链版本(如 GNU GCC for TriCore)。此外还需要指定调试接口类型(如 JTAG 或 SWD),以便后续能够连接实际硬件进行测试[^4]。 4. **导入必要的库和驱动程序** 对于特定功能需求(比如 CAN 总线通信或者 PWM 输出),可以利用官方提供的标准外设库来简化开发难度。例如,在处理复杂电机控制系统时可能需要用到 SimpleFOC library 和逐飞科技提供的开源库支持[^2]。 5. **配置系统时钟与时基单元** 正确设定 MCU 的主振荡频率及其派生出来的各个子模块的工作速率至关重要。通常情况下可以通过修改相关寄存器值实现这一点;而对于更高级别的应用,则建议采用图形化界面来进行参数调整[^1]。 6. **编写应用程序逻辑代码** 当所有前期准备工作都已完成之后就可以着手构建核心业务算法部分了。注意合理分配不同 CPU 核心之间的任务负载以充分发挥 AURIX 多核架构的优势。 ```c // 示例:简单的 LED 控制代码片段 void init_led(void){ // 初始化 GPIO 引脚作为输出模式 } int main(){ init_led(); while(1){ // 实现闪烁效果 } } ``` #### 注意事项 - 如果计划使用中断机制,请明确哪些事件触发源会被映射到哪个具体的处理器上执行相应的 ISR 函数。 - 考虑到资源利用率最大化原则,在设计阶段就应该充分评估每种方法的成本效益比再做决定[^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值