打印
[应用相关]

基于STM32的OLED多级菜单GUI实现(简化版智能手表)

[复制链接]
630|82
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
   前言:本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器与OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DHT11,RTC,LED,KEY等器件实现高度智能化一体化操作。后期自己打板设计结构,可以衍生为智能手表等小玩意。目前,项目属于裸机状态(CPU占用率100%),后期可能会加上RTOS系统。(本项目源码在本文末尾进行开源!)
————————————————
版权声明:本文为CSDN博主「混分巨兽龙某某」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/black_sneak/article/details/126442559

使用特权

评论回复
沙发
鱿鱼丝|  楼主 | 2023-3-27 10:36 | 只看该作者
硬件实物图:

使用特权

评论回复
板凳
鱿鱼丝|  楼主 | 2023-3-27 10:37 | 只看该作者
效果图:

        温度计:

使用特权

评论回复
地板
鱿鱼丝|  楼主 | 2023-3-27 10:37 | 只看该作者

使用特权

评论回复
5
鱿鱼丝|  楼主 | 2023-3-27 10:37 | 只看该作者
游戏机:

使用特权

评论回复
6
鱿鱼丝|  楼主 | 2023-3-27 10:38 | 只看该作者

使用特权

评论回复
7
鱿鱼丝|  楼主 | 2023-3-27 10:38 | 只看该作者
引脚连接:

    OLED模块:

    VCC --> 3.3V

    GND --> GND

    SCL --> PB10

    SDA --> PB11

使用特权

评论回复
8
鱿鱼丝|  楼主 | 2023-3-27 10:38 | 只看该作者


    DHT11模块:

    DATA --> PB9

    VCC --> 3.3V

    GND --> GND

使用特权

评论回复
9
鱿鱼丝|  楼主 | 2023-3-27 10:38 | 只看该作者


    KEY模块(这部分笔者直接使用了正点原子精英板上的):

    KEY0 --> PE4

    KEY1 --> PE3

    KEY_UP --> PA0

使用特权

评论回复
10
鱿鱼丝|  楼主 | 2023-3-27 10:38 | 只看该作者
一、多级菜单

        随着工业化和自动化的发展,如今基本上所有项目都离不开显示终端。而多级菜单更是终端显示项目中必不可少的组成因素,其实TFT-LCD屏幕上可以借鉴移植很多优秀的开源多级菜单(GUI,比如:LVGL),而0.96寸的OLED屏幕上通常需要自己去适配和编程多级菜单。

使用特权

评论回复
11
鱿鱼丝|  楼主 | 2023-3-27 10:39 | 只看该作者
精美的多级菜单:

使用特权

评论回复
12
鱿鱼丝|  楼主 | 2023-3-27 10:39 | 只看该作者
网上的普遍采用的多级菜单的方案是基于索引或者结构树,其中,索引法居多。索引法的优点:可阅读性好,拓展性也不错,查找的性能差不多是最优,就是有点占用内存空间。

    说明:本项目的多级菜单也是采用了索引法进行实现。

使用特权

评论回复
13
鱿鱼丝|  楼主 | 2023-3-27 10:45 | 只看该作者
二、索引法多级菜单实现

        网上关于索引法实现多级菜单功能有很多基础教程,笔者就按照本项目中的具体实现代码过程给大家讲解一下索引法实现多级菜单。特别说明:本项目直接使用了正点原子的精英板作为核心板,所以读者朋友复现代码还是很简单的。

使用特权

评论回复
14
鱿鱼丝|  楼主 | 2023-3-27 10:45 | 只看该作者
  首先,基于索引法实现多级菜单的首要条件是先确定项目中将使用到几个功能按键(比如:向前,向后,确定,退出等等)本项目中,笔者使用到了3个按键:下一个(next),确定(enter),退出(back)。所以,接下首先定义一个结构体,结构体中一共有5个变量(3+2),分别为:当前索引序号(current),向下一个(next),确定(enter),退出(back),当前执行函数(void)。其中,标红的为需要设计的按键(笔者这里有3个),标绿的则为固定的索引号与该索引下需要执行的函数。

使用特权

评论回复
15
鱿鱼丝|  楼主 | 2023-3-27 10:46 | 只看该作者
typedef struct
{
    u8 current;            //当前状态索引号
    u8 next;                 //向下一个
    u8 enter;             //确定
        u8 back;                 //退出
    void (*current_operation)(void); //当前状态应该执行的操作
} Menu_table;

使用特权

评论回复
16
鱿鱼丝|  楼主 | 2023-3-27 10:46 | 只看该作者
typedef struct
{
    u8 current;            //当前状态索引号
    u8 next;                 //向下一个
    u8 enter;             //确定
        u8 back;                 //退出
    void (*current_operation)(void); //当前状态应该执行的操作
} Menu_table;

使用特权

评论回复
17
鱿鱼丝|  楼主 | 2023-3-27 10:46 | 只看该作者
   接下来就是定义一个数组去决定整个项目菜单的逻辑顺序(利用索引号)
Menu_table  table[30]=
{
    {0,0,1,0,(*home)},        //一级界面(主页面) 索引,向下一个,确定,退出
               
    {1,2,5,0,(*Temperature)},        //二级界面 温湿度
    {2,3,6,0,(*Palygame)},        //二级界面 游戏
    {3,4,7,0,(*Setting)},        //二级界面 设置
    {4,1,8,0,(*Info)},        //二级界面 信息
               
        {5,5,5,1,(*TestTemperature)},                //三级界面:DHT11测量温湿度
        {6,6,6,2,(*ControlGame)},                                //三级界面:谷歌小恐龙Dinogame
        {7,7,9,3,(*Set)},                                                                //三级界面:设置普通外设状态 LED
        {8,8,8,4,(*Information)},                                //三级界面:作者和相关项目信息

        {9,9,7,3,(*LED)},                //LED控制
};

使用特权

评论回复
18
鱿鱼丝|  楼主 | 2023-3-27 10:46 | 只看该作者
     这里解释一下这个数组中各元素的意义,由于我们在前面先定义了Menu_table结构体,结构体成员变量分别与数组中元素对应。比如:{0,0,1,0,(*home)},代表了索引号为0,按向下键(next)转入索引号为0,按确定键(enter)转入索引号为1,按退出键(back)转入索引号为0,索引号为0时执行home函数。

使用特权

评论回复
19
鱿鱼丝|  楼主 | 2023-3-27 10:47 | 只看该作者
在举一个例子帮助大家理解一下,比如,我们当前程序处在索引号为2(游戏界面),就会执行Playgame函数。此时,如果按下next按键,程序当前索引号就会变为3,并且执行索引号为3时候的Setting函数。如果按下enter按键,程序当前索引号就会变为6,并且执行索引号为6时候的ControlGame函数。如果按下back按键,程序当前索引号就会变为0,并且执行索引号为0时候的home函数。

使用特权

评论回复
20
鱿鱼丝|  楼主 | 2023-3-27 10:47 | 只看该作者
   再接下就是按键处理函数:
uint8_t  func_index = 0;        //主程序此时所在程序的索引值

void  Menu_key_set(void)
{
  if((KEY_Scan(1) == 1) && (func_index != 6))        //屏蔽掉索引6下的情况,适配游戏
  {
    func_index=table[func_index].next;        //按键next按下后的索引号
    OLED_Clear();
  }
       
  if((KEY_Scan(1) == 2) && (func_index != 6))
  {
    func_index=table[func_index].enter;        //按键enter按下后的索引号
    OLED_Clear();
  }

        if(KEY_Scan(1) == 3)
  {
    func_index=table[func_index].back;        //按键back按下后的索引号
    OLED_Clear();
  }
       
  current_operation_index=table[func_index].current_operation;        //执行当前索引号所对应的功能函数
  (*current_operation_index)();//执行当前操作函数
}


//按键函数
u8 KEY_Scan(u8 mode)
{
        static u8 key_up=1;
        if(mode)key_up=1;
        if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
        {
                HAL_Delay(100);                //消抖
                key_up=0;
                if(KEY0==0)return 1;
                else if(KEY1==0)return 2;
                else if(WK_UP==1)return 3;
        }else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
        return 0;
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

35

主题

387

帖子

0

粉丝