0.96寸OLED(四)多级菜单实现


前言

本次是基于0.96寸OLED的多级菜单设计,找了一下网上的菜单写法,发现很多是那种利用一个数组记录界面的,我觉得这样不是很直观。我希望写成那种可以不断延伸拓展的界面,界面数量,菜单级数可以任意指定的类型。
在这里插入图片描述
就像XMIND思维导图工具一样,因此我写了一个简单的多级菜单管理界面。

下面直接上代码

一、结构体定义

首先创造一些结构体存放界面所需的变量

#ifndef __GUI_096_H
#define __GUI_096_H
#include "main.h"
#include "oled.h"
#define Menu_level 5 //菜单级数
#define Menu_Length 20 //单个菜单长度
typedef struct oled_fun
{
    uint8_t string[20]; //菜单名
    void (*func) (void); //菜单函数

}GUI_Menu;
typedef enum GUI_Button
{
      Gui_Up,
      Gui_Down,
      Gui_Right,
      Gui_Left,
      Gui_No,
}GUI_Button;
typedef struct Oled_Gui
{
    uint8_t Pause;   //Gui暂停标志位
    uint8_t Menu_Len; //Gui菜单长度
    GUI_Button Button; //Gui按键
    uint8_t Begin_Index;  //Gui菜单起始
    uint8_t Now_Inedx;  //Gui菜单选中
    GUI_Menu Menu[Menu_Length];  //Gui菜单
    void (*Last_Func[Menu_level]) (void);    //Gui上级函数记录
    void (*Now_Func) (void);   //Gui选中函数
}Gui;
extern Gui My_Gui;
void gui_init();
void Menu_Refresh(void);
void Menu_Clear();
void Menu_Update(uint8_t len,GUI_Menu* the_next);
#endif///

二、相关函数

1.页面刷新

void Menu_Refresh()
{
   OLED_Clear(); 
   for(int i=My_Gui.Begin_Index;i<My_Gui.Menu_Len;i++)
   {
        if(i==(My_Gui.Now_Inedx))
        OLED_ShowString(0,16*(i-My_Gui.Begin_Index),My_Gui.Menu[i].string,16,0); 
        else
        OLED_ShowString(0,16*(i-My_Gui.Begin_Index),My_Gui.Menu[i].string,16,1); 
        if(i-My_Gui.Begin_Index==3)
          break;
   }
  OLED_Refresh();
}

2.页面清除

void Menu_Clear()
{
  memset( (uint32_t *)(&(My_Gui.Menu[0])), 0, My_Gui.Menu_Len*sizeof(My_Gui.Menu[0]));
  My_Gui.Menu_Len=0;
  My_Gui.Begin_Index=0; 
  My_Gui.Now_Inedx=0; 
  OLED_Clear(); 
  OLED_Refresh();
}

3.菜单更新

void Menu_Update(uint8_t len,GUI_Menu* the_next)
{
    memset( (uint32_t *)(&(My_Gui.Menu[0])), 0, My_Gui.Menu_Len*sizeof(My_Gui.Menu[0]));
    My_Gui.Begin_Index=0; 
    My_Gui.Now_Inedx=0; 
    My_Gui.Menu_Len=len;
    for(int i=0;i<len;i++)
    {
           My_Gui.Menu[i]=the_next[i];  
    }     
    Menu_Refresh();     
}

4.GUI按键检测

void gui_init_tick()
{
  //GUI没有暂停的情况下处理按键
  if(My_Gui.Pause==0)
  {
      if(My_Gui.Button==Gui_Up)
      {
          My_Gui.Button=Gui_No;
          if(My_Gui.Now_Inedx>0)
          {
            //向上移位选项然后刷新屏幕
             My_Gui.Now_Inedx--;
             if(My_Gui.Now_Inedx<My_Gui.Begin_Index)
             My_Gui.Begin_Index=My_Gui.Now_Inedx;
             Menu_Refresh();
          }   
      }
      else if(My_Gui.Button==Gui_Down)
      {
          My_Gui.Button=Gui_No;
          //向下移位选项然后刷新屏幕
          if(My_Gui.Now_Inedx<(My_Gui.Menu_Len-1))
          {
            My_Gui.Now_Inedx++;
            if(My_Gui.Now_Inedx-My_Gui.Begin_Index>3)
              My_Gui.Begin_Index=My_Gui.Now_Inedx-3;
            Menu_Refresh();
          }   
      }
      else if(My_Gui.Button==Gui_Right)
      {
          My_Gui.Button=Gui_No;
          //选择函数
          if(My_Gui.Menu[My_Gui.Now_Inedx].func!=NULL)
          {
            //现函数进入上级存储
            for(int i=0;i<Menu_level;i++)
            {
              if(My_Gui.Last_Func[i]==NULL)
              {
                  My_Gui.Last_Func[i]=My_Gui.Now_Func;
                  break;
              }
            }
            //修改现函数
            My_Gui.Now_Func=My_Gui.Menu[My_Gui.Now_Inedx].func;
            My_Gui.Now_Inedx=0;
            My_Gui.Begin_Index=0;
            My_Gui.Pause=1;            //暂停GUI
          }
    }
    else if(My_Gui.Button==Gui_Left)
    {
          My_Gui.Button=Gui_No;   
          //取出存储的函数
          for(int i=Menu_level-1;i>=0;i--)
          {
            if(My_Gui.Last_Func[i]!=NULL)
            {
                My_Gui.Now_Func=My_Gui.Last_Func[i];
                My_Gui.Last_Func[i]=NULL;
                break;
            }
          }
          My_Gui.Now_Inedx=0;
          My_Gui.Begin_Index=0;
          My_Gui.Pause=1;
    }
  }
  else
    My_Gui.Button=Gui_No;
}

5.初始化

GUI_Menu Menu1[]={{"A",.func=func_A},
                {"AA",.func=func_B},
                {"AAA",.func=func_C},
                {"AAAA",.func=func_D},
                {"AAAAA",.func=func_D},
                {"AAAAAA",.func=func_D},
                {"AAAAAAA",.func=func_D},
                {"AAAAAAAA",.func=func_D},
};
void gui_init()
{
    for(int i=0;i<Menu_level;i++)
    My_Gui.Last_Func[i]=NULL;
    My_Gui.Now_Func=gui_init;
    My_Gui.Menu_Len=8;
    My_Gui.Pause=0;
    My_Gui.Button=Gui_No;    
    My_Gui.Begin_Index=0;
    My_Gui.Now_Inedx=0; 
    for(int j=0;j<My_Gui.Menu_Len;j++)
    My_Gui.Menu[j]=Menu1[j];  
    Menu_Refresh();    
}

二、函数注意

本次是基于0.96寸OLED的多级菜单设计,可以看到我定义的结构体内部函数指针是返回空类型且没有输入变量。并且菜单调用的函数基本上可以分为三类,一类是仅仅运行一次,比如修改了什么参数的函数,一类是菜单更新,因为下一级可能不是函数而是一个菜单更新的函数,最后一类是那种while循环的类型,这种函数需要一个while()检测让函数停止,不然函数会一直运行跳转不出来,这个函数需要我们自己加上停止条件。

1.仅运行一次的函数

这类函数往往很快运行完之后直接跳出来。比如一个LED的翻转函数,前面需要先清除一下当前菜单。

void func_A(void)
{
  Menu_Clear();
  HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);  
}

2.菜单切换的函数

这类函数往往是更新界面函数,向下一级函数跳转,可以直接参考我下面的写法进行调用。

GUI_Menu Menu2[]={{"B",.func=func_A},
                {"中景园",.func=func_B},
};
void func_C(void)
{  
  Menu_Update(2,Menu2);
  HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}

3.重复循环的函数

这类函数需要用户自己添加结束条件

void func_B(void)
{
   Menu_Clear();
   while(flag==1)
   {
    //用户自己添加内容   
   
   }
}

4.主函数循环和调用方法

开一个定时器内部进行不断调用

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
  if(htim->Instance==TIM4)
  {
   //HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
   //HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
  }
  else  if(htim->Instance==TIM3)
  {
    gui_init_tick();
  }
}

主函数的写法就很简单了

#include "sys.h"
int main(void)
{
  hard_steup();
  while(1)
  {
    if(My_Gui.Pause==1)
    {
      My_Gui.Now_Func();
      My_Gui.Pause=0;
    }
  }
}

  • 40
    点赞
  • 316
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值