自己使用的 OLED 多级菜单实现方式

已用更好的方式实现,但是原理差不多,最好使用后面更新的实现,公众号第一篇文章链接:https://mp.weixin.qq.com/s/OOFBPmeIjepWr6Fn6f9dtQ,也可在后面的博文中找到。

已用更好的方式实现,但是原理差不多,最好使用后面更新的实现,公众号第一篇文章链接:https://mp.weixin.qq.com/s/OOFBPmeIjepWr6Fn6f9dtQ,也可在后面的博文中找到。

已用更好的方式实现,但是原理差不多,最好使用后面更新的实现,公众号第一篇文章链接:https://mp.weixin.qq.com/s/OOFBPmeIjepWr6Fn6f9dtQ,也可在后面的博文中找到。

菜单选项的移动方式类似于 windows 文件管理器中的文件选择方式(查看方式为图标方式),目前实现了菜单翻页功能,只读页面翻页显示功能,数值修改功能等,思路就是把所有选项看成一整列,但是一整列里面分成多行多列,之前使用二维数组实现过,但是发现这种方式代码量和逻辑都更简单,便采用此种方式,如果需要借鉴,不明白思路的可以先用键盘上下左右操作一下 windows 文件管理器中的文件选项。因为仅个人使用,所以并没有考虑通用和移植,我觉得代码都不重要,重要的是思路,目前已经在项目中使用,仅提供参考,勿评。

#ifndef __MENU_H
#define __MENU_H

#include <string.h>
#include <stdint.h>

/*菜单级数深度*/
#define MENU_MAX_LEVELS 12
/*最大可设置数值位数*/
#define VALID_MAX_SET_NUM 12

#ifndef COUNTOF
#define COUNTOF(a) (sizeof(a) / sizeof(*(a)))
#endif
#ifndef ABS
#define ABS(a) ((a) > 0?(a):-(a))
#endif

typedef enum {
	MENU_KEY_DEF,
	MENU_KEY_X,
	MENU_KEY_Y,
	MENU_KEY_Z,
	MENU_KEY_IPV4,
	MENU_KEY_IPV6,
}menu_keyTypeDef;

typedef void(*tMenuFunc)(void);
typedef struct sValueSet *tValueSet;
typedef struct sTitle *tTitle;
typedef struct sMenuItem *tMenuItem;
typedef struct sMenu *tMenu;

struct sTitle {
  char *str;//标题名
  uint8_t x;//行坐标
  uint8_t y;//列坐标
//  uint8_t f_w;//字体宽
//  uint8_t f_h;//字体高
};

struct sValueSet {
  int64_t value;
  int64_t max;//数值最大值
  int64_t min;//数值最小值
  int32_t nBit;//数值位数(整数部分)
  int32_t nBit_f;//数值位数(小数部分)
  uint8_t bitIdx;
  uint8_t aBit[VALID_MAX_SET_NUM];
  uint16_t ipv6[8];//用于 ipv6 设置
};

struct sMenuHMIFunc {
  tMenuFunc pfMenuKeyUp;
  tMenuFunc pfMenuKeyDown;
  tMenuFunc pfMenuKeyLeft;
  tMenuFunc pfMenuKeyRight;
  tMenuFunc pfMenuKeyEnter;
  tMenuFunc pfMenuKeyExit;
  
  //静态内容显示
  tMenuFunc pfMenuDispStatic;
  //动态内容显示
  tMenuFunc pfMenuDispDynamic;
};

struct sMenuItem {
  tTitle pszTitle;//选项标题
  tMenu psSubMenu;//子菜单
};

struct sMenu {
  tTitle pszTitle;//菜单标题
  tMenuItem psItem;//子选项
  uint8_t nItems;//子选项数量或页数
  uint8_t idx;//当前索引
  uint8_t nRow;//单页要排列的行
  uint8_t nCol;//单页要排列的列
  int id;
  struct sMenuHMIFunc HMIFunc;
  menu_keyTypeDef menu_key;
};

struct sMenuHandle {
  tMenu psPreMenu[MENU_MAX_LEVELS];//存储各级菜单
  uint8_t Level;//当前菜单级数
  tMenu psCurMenu;//当前菜单
  tValueSet psValueSet;
};

/*获取当前菜单*/
inline struct sMenu *MenuGetCur(void)
{
	extern struct sMenuHandle hMenu;
	
	return hMenu.psCurMenu;
}

/*获取当前菜单*/
inline void MenuSetCur(struct sMenu *set)
{
	extern struct sMenuHandle hMenu;
	
	hMenu.psCurMenu = set;
}

/*获取数值设置*/
inline struct sValueSet *MenuGetValueSet(void)
{
	extern struct sMenuHandle hMenu;
	
	return hMenu.psValueSet;
}

/*计算 val 的 n 次方*/
uint64_t CalPower(uint64_t val, uint32_t n);
//计算 10 的次方
double CalPowerOfTen(int32_t n);
/*获取数值位数*/
int32_t GetNumBit(uint64_t num);
/*获取浮点数的整数位数和小数位数*/
void GetNumBit_f(double num,int32_t *d,int32_t *f);
/*初始化菜单*/
void MenuInit(struct sMenu *pFirstMenu);
/*反初始化菜单*/
void MenuDeInit(void);
/*显示菜单*/
void MenuDisp(void);
/*更新静态内容*/
void Updatestatic(void);
/*更新动态内容*/
void UpdateDynamic(void);
/*按键扫描*/
void MenuBspKeyScan(void);
/*menu replace(注意 const)*/
void MenuReplace(struct sMenu re[]);
/*进入选中的子菜单*/
void MenuEnter(void);
/*退出到上一级菜单*/
void MenuExit(void);

#endif/*__MENU_H*/
#include "./menu.h"
#include "debug.h"
#include "./key_board/key_board.h"
#include "./oled.h"

#define OPT_DBG_INF 1U

struct sMenuHandle hMenu;

static struct sValueSet tmpSet;

void MenuUp(void);
void MenuDown(void);
void MenuLeft(void);
void MenuRight(void);
void MenuUp_x(void);
void MenuDown_x(void);
void MenuLeft_x(void);
void MenuRight_x(void);
void MenuUp_y(void);
void MenuDown_y(void);
void MenuLeft_y(void);
void MenuRight_y(void);
void MenuUp_z(void);
void MenuDown_z(void);
void MenuLeft_z(void);
void MenuRight_z(void);
void MenuUp_IPv4(void);
void MenuDown_IPv4(void);
void MenuLeft_IPv4(void);
void MenuRight_IPv4(void);
void MenuUp_IPv6(void);
void MenuDown_IPv6(void);
void MenuLeft_IPv6(void);
void MenuRight_IPv6(void);

/*计算 val 的 n 次方*/
uint64_t CalPower(uint64_t val, uint32_t n)
{
  uint64_t tmp = 1;

  if(val == 0){ return 0; }
  
  while(n--)
  {
    tmp *= val;
  }

  return tmp;
}

//计算 10 的次方
double CalPowerOfTen(int32_t n)
{
  double tmp = 1;
  int32_t tmpN = ABS(n);

  if(n > 0)
  {
    while(n--)
    {
      tmp *= 10;
    }
  }
  else if(n < 0)
  {
    while(tmpN--)
    {
      tmp /= 10;
    }
  }

  return tmp;
}

/*获取数值位数*/
int32_t GetNumBit(uint64_t num)
{
  char aTmp[20] = {0};//max 4bytes
  
  return sprintf(aTmp,"%llu",num);
}
/*获取浮点数的整数位数和小数位数(有缺陷,如果小数点后保留位为全 0)*/
void GetNumBit_f(double num,int32_t *d,int32_t *f)
{
  char aTmp[32] = {0};
  int32_t i = 0;
  
  *d = 0;*f = 0;
  if((double)0 == num)
  {
    *d = 1;
    return ;
  }
  *d = sprintf(aTmp,"%d",ABS((int32_t)num));
  
  i = sprintf(aTmp,"%f",ABS(num));
  while(aTmp[--i] == '0')
  {
    //除去默认精度补的零个数
  }   
  i += 1;
  //除去整数位数和小数点
  *f = i - *d - 1;
}

/*初始化菜单*/
void MenuInit(struct sMenu *pFirstMenu)
{
	pFirstMenu->HMIFunc.pfMenuKeyUp 	= MenuUp;
	pFirstMenu->HMIFunc.pfMenuKeyDown 	= MenuDown;
	pFirstMenu->HMIFunc.pfMenuKeyLeft 	= MenuLeft;
	pFirstMenu->HMIFunc.pfMenuKeyRight 	= MenuRight;
	pFirstMenu->HMIFunc.pfMenuKeyEnter	= MenuEnter;
	pFirstMenu->HMIFunc.pfMenuKeyExit 	= MenuExit;
        hMenu.psCurMenu			        = pFirstMenu;
        hMenu.Level			        = 0;
        hMenu.psPreMenu[hMenu.Level]		= pFirstMenu;
        hMenu.psValueSet			= &tmpSet;
  
        MenuDisp();
}

/*反初始化菜单*/
void MenuDeInit(void)
{
	memset(&hMenu,NULL,sizeof(struct sMenuHandle));
}

/*显示菜单*/
void MenuDisp(void)
{
  uint32_t i = 0,tmpIdx = 0,tmp = 0;
  tTitle pTmp2 = NULL;
  
  OLED_clcScreen();
  
  /*刷新静态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispStatic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispStatic();
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
  
  tmp = hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol;
  /*如果单页行列元素小于最大选项数*/
  if(tmp && (tmp < hMenu.psCurMenu->nItems))
  {
    //计算当前索引对应页的开始选项
    i = hMenu.psCurMenu->idx - (hMenu.psCurMenu->idx % tmp);
    tmpIdx = i;
  }
  
  for( ;(i < hMenu.psCurMenu->nItems) && (i < (tmp + tmpIdx));i++)
  {
    pTmp2 = hMenu.psCurMenu->psItem[i].pszTitle;
    if(pTmp2 && pTmp2->str)
    {
      OLED_INT_CharDisp(pTmp2->x, pTmp2->y, i == hMenu.psCurMenu->idx?1:0, pTmp2->str);
    }
  }
}

/*更新静态内容*/
void Updatestatic(void)
{
  /*刷新静态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispStatic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispStatic();
  }
}

/*更新动态内容*/
void UpdateDynamic(void)
{
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

/*按键扫描*/
void MenuBspKeyScan(void)
{
  if(hMenu.psCurMenu == NULL)
  {
    return ;
  }
  
  if (KeyGetState(KEY_UP) == KEY_RELEASE)//up
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyUp != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyUp();
    }
  }
  if (KeyGetState(KEY_DOWN) == KEY_RELEASE)//down
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyDown != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyDown();
    }
  }
  if (KeyGetState(KEY_LEFT) == KEY_RELEASE)//left
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft();
    }
  }
  if (KeyGetState(KEY_RIGHT) == KEY_RELEASE)//right
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyRight != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyRight();
    }
  }
  if (KeyGetState(KEY_ENTER) == KEY_RELEASE)//enter
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter();
    }
  }
  if (KeyGetState(KEY_EXIT) == KEY_RELEASE)//exit
  {
    if(hMenu.psCurMenu->HMIFunc.pfMenuKeyExit != NULL)
    {
      hMenu.psCurMenu->HMIFunc.pfMenuKeyExit();
    }
  }
}

/*menu replace(注意 const)*/
void MenuReplace(struct sMenu re[])
{
  hMenu.psCurMenu = re;
}

/*enter key*/
void MenuEnter(void)
{
  if((&hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx] != NULL) &&
     (hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].psSubMenu != NULL))
  {
    hMenu.psPreMenu[hMenu.Level] = hMenu.psCurMenu;
    if(!(++hMenu.Level < MENU_MAX_LEVELS))
    {
      hMenu.Level--;
    }
    hMenu.psCurMenu = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].psSubMenu;
    
    MenuDisp();
  }
  
  switch(hMenu.psCurMenu->menu_key)
  {
    case MENU_KEY_DEF:
	hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight;
	break;
    case MENU_KEY_X:
	hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_x;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_x;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_x;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_x;
	break;
    case MENU_KEY_Y:
	hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_y;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_y;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_y;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_y;
	break;
    case MENU_KEY_Z:
	hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_z;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_z;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_z;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_z;
	break;
    case MENU_KEY_IPV4:
        hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_IPv4;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_IPv4;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_IPv4;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_IPv4;
	break;
    case MENU_KEY_IPV6:
	hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_IPv6;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_IPv6;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_IPv6;
	hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_IPv6;
	break;
    default:
	break;
    }
	
  if(hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter == NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter  = MenuEnter;
  }
  if(hMenu.psCurMenu->HMIFunc.pfMenuKeyExit == NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuKeyExit   = MenuExit;
  }
}

/*exit key*/
void MenuExit(void)
{
  if(hMenu.Level > 0)
  {
    hMenu.Level--;
    hMenu.psCurMenu = hMenu.psPreMenu[hMenu.Level];
    
    MenuDisp();
  }
  
  switch(hMenu.psCurMenu->menu_key)
    {
	case MENU_KEY_DEF:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight;
	    break;
	case MENU_KEY_X:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_x;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_x;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_x;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_x;
	    break;
	case MENU_KEY_Y:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_y;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_y;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_y;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_y;
	    break;
	case MENU_KEY_Z:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_z;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_z;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_z;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_z;
	    break;
	case MENU_KEY_IPV4:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_IPv4;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_IPv4;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_IPv4;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_IPv4;
	    break;
	case MENU_KEY_IPV6:
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyUp 	= MenuUp_IPv6;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyDown	= MenuDown_IPv6;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyLeft	= MenuLeft_IPv6;
	    hMenu.psCurMenu->HMIFunc.pfMenuKeyRight	= MenuRight_IPv6;
	    break;
	default:
	    break;
    }
	
  if(hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter == NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuKeyEnter  = MenuEnter;
  }
  if(hMenu.psCurMenu->HMIFunc.pfMenuKeyExit == NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuKeyExit   = MenuExit;
  }
}

/*up key 用于选择菜单*/
void MenuUp(void)
{
  tTitle pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  uint8_t currentPage = 0,updatePage = 0;
  
  /*如果索引为 0 则直接退出*/
  if(!(hMenu.psCurMenu->idx > 0))
  {
    return ;
  }
  
  //计算当前索引处于的页数
  currentPage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  if(pTmp1)
  {
    //取消当前选中
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 0, pTmp1->str);
  }
  
  /*如果索引小于当前页列数则跳到第一列,否则跳到上一行对应列*/
  if(hMenu.psCurMenu->idx < hMenu.psCurMenu->nCol)
  {
    hMenu.psCurMenu->idx = 0;
  }
  else
  {
    hMenu.psCurMenu->idx -= hMenu.psCurMenu->nCol;
  }
  pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  
  if(pTmp1)
  {
    //选中下一个
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 1, pTmp1->str);
  }
  
  //计算更新索引后处于的页数
  updatePage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
  
  if((hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol) < hMenu.psCurMenu->nItems)
  {
    /*更新索引后对应的页数小于了更新前的页数则跳到上一页*/
    if(updatePage < currentPage)
    {
      MenuDisp();
    }
  }
}
/*down key 用于选择菜单*/
void MenuDown(void)
{
  tTitle pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  uint8_t currentPage = 0,updatePage = 0;
  
  /*如果当前索引大于最大选项数则直接退出*/
  if(!(hMenu.psCurMenu->idx < (hMenu.psCurMenu->nItems - 1)))
  {
    return ;
  }
  
  //计算当前索引处于的页数
  currentPage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  if(pTmp1)
  {
    //取消当前选中
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 0, pTmp1->str);
  }
  
  /*如果跳到下一行对应列大于了最大选项数则跳到最后一个元素,否则跳到下一行对应列*/
  if((hMenu.psCurMenu->idx + hMenu.psCurMenu->nCol) > (hMenu.psCurMenu->nItems - 1))
  {
    hMenu.psCurMenu->idx = hMenu.psCurMenu->nItems - 1;
  }
  else
  {
    hMenu.psCurMenu->idx += hMenu.psCurMenu->nCol;
  }
  pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  
  if(pTmp1)
  {
    //选中下一个
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 1, pTmp1->str);
  }
  
  //计算更新索引后处于的页数
  updatePage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
  
  if((hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol) < hMenu.psCurMenu->nItems)
  {
    /*更新索引后对应的页数大于了更新前的页数则跳到下一页*/
    if(updatePage > currentPage)
    {
      MenuDisp();
    }
  }
}
/*left key 用于选择菜单*/
void MenuLeft(void)
{
  tTitle pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  uint8_t currentPage = 0,updatePage = 0;
  
  if(!hMenu.psCurMenu->psItem)
  {
	  return ;
  }
  
  //计算当前索引处于的页数
  currentPage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
    if(pTmp1)
    {
	//取消当前选中
	OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 0, pTmp1->str);
    }
  
  if(hMenu.psCurMenu->idx > 0)
  {
    hMenu.psCurMenu->idx--;
  }
  else
  {
    hMenu.psCurMenu->idx = hMenu.psCurMenu->nItems - 1;
  }
  pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  
  if(pTmp1)
  {
    //选中下一个
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 1, pTmp1->str);
  }
  
  //计算更新索引后处于的页数
  updatePage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
  
  if((hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol) < hMenu.psCurMenu->nItems)
  {
    /*更新索引后对应的页数小于了更新前的页数或更新后的索引循环了一次则跳到上一页*/
    if((updatePage < currentPage) || (hMenu.psCurMenu->idx == (hMenu.psCurMenu->nItems - 1)))
    {
      MenuDisp();
    }
  }
}
/*right key 用于选择菜单*/
void MenuRight(void)
{
  tTitle pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  uint8_t currentPage = 0,updatePage = 0;
  
  if(!hMenu.psCurMenu->psItem)
  {
	  return ;
  }
  
  //计算当前索引处于的页数
  currentPage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  if(pTmp1)
  {
    //取消当前选中
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 0, pTmp1->str);
  }
  
  if(hMenu.psCurMenu->idx < (hMenu.psCurMenu->nItems - 1))
  {
    hMenu.psCurMenu->idx++;
  }
  else
  {
    hMenu.psCurMenu->idx = 0;
  }
  pTmp1 = hMenu.psCurMenu->psItem[hMenu.psCurMenu->idx].pszTitle;
  
  if(pTmp1)
  {
    //选中下一个
    OLED_INT_CharDisp(pTmp1->x, pTmp1->y, 1, pTmp1->str);
  }
  
  //计算更新索引后处于的页数
  updatePage = hMenu.psCurMenu->idx / (hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol);
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
  
  if((hMenu.psCurMenu->nRow * hMenu.psCurMenu->nCol) < hMenu.psCurMenu->nItems)
  {
    /*更新索引后对应的页数大于了更新前的页数或更新后的索引循环了一次则跳到下一页*/
    if((updatePage > currentPage) || (hMenu.psCurMenu->idx == 0))
    {
      MenuDisp();
    }
  }
}

/*up key 用于翻页*/
void MenuUp_x(void)
{
  if(hMenu.psCurMenu->idx > 0)
  {
    hMenu.psCurMenu->idx--;
    
    MenuDisp();
  }
}
/*down key 用于翻页*/
void MenuDown_x(void)
{
  if(hMenu.psCurMenu->idx < (hMenu.psCurMenu->nItems - 1))
  {
    hMenu.psCurMenu->idx++;
    
    MenuDisp();
  }
}
/*left key 用于翻页*/
void MenuLeft_x(void)
{
  
}
/*right key 用于翻页*/
void MenuRight_x(void)
{
  
}

/*up key 用于数值设置(各位之间互不影响)*/
void MenuUp_y(void)
{
    /*数值加范围(0~9)*/
    if(++hMenu.psValueSet->aBit[hMenu.psValueSet->bitIdx] >= 10)
    {
        hMenu.psValueSet->aBit[hMenu.psValueSet->bitIdx] = 0;
    }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*down key 用于数值设置(各位之间互不影响)*/
void MenuDown_y(void)
{
    /*数值减范围(0~9)*/
    if(hMenu.psValueSet->aBit[hMenu.psValueSet->bitIdx]-- <= 0)
    {
	hMenu.psValueSet->aBit[hMenu.psValueSet->bitIdx] = 9;
    }

  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*left key 用于数值设置(各位之间互不影响)*/
void MenuLeft_y(void)
{
  if(hMenu.psValueSet->nBit > VALID_MAX_SET_NUM)
  {
    hMenu.psValueSet->nBit = VALID_MAX_SET_NUM;
  }
  
  /*移动索引*/
  if(hMenu.psValueSet->bitIdx-- <= 0)
  {
    hMenu.psValueSet->bitIdx = hMenu.psValueSet->nBit - 1;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*right key 用于数值设置(各位之间互不影响)*/
void MenuRight_y(void)
{
  if(hMenu.psValueSet->nBit > VALID_MAX_SET_NUM)
  {
    hMenu.psValueSet->nBit = VALID_MAX_SET_NUM;
  }
  
  /*移动索引*/
  if(++hMenu.psValueSet->bitIdx >= hMenu.psValueSet->nBit)
  {
    hMenu.psValueSet->bitIdx = 0;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

/*up key 用于数值设置*/
void MenuUp_z(void)
{
  /*数值修改*/
  hMenu.psValueSet->value += CalPowerOfTen(hMenu.psValueSet->nBit + hMenu.psValueSet->nBit_f - hMenu.psCurMenu->idx - 1);
  if(hMenu.psValueSet->value > hMenu.psValueSet->max)
  {
    hMenu.psValueSet->value = hMenu.psValueSet->max;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*down key 用于数值设置*/
void MenuDown_z(void)
{
  /*数值修改*/
  hMenu.psValueSet->value -= CalPowerOfTen(hMenu.psValueSet->nBit + hMenu.psValueSet->nBit_f - hMenu.psCurMenu->idx - 1);
  if(hMenu.psValueSet->value < hMenu.psValueSet->min)
  {
    hMenu.psValueSet->value = hMenu.psValueSet->min;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*left key 用于数值设置*/
void MenuLeft_z(void)
{
  /*移动索引*/
  if(hMenu.psCurMenu->idx-- <= 0)
  {
    hMenu.psCurMenu->idx = (hMenu.psValueSet->nBit + hMenu.psValueSet->nBit_f) - 1;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*right key 用于数值设置*/
void MenuRight_z(void)
{
  /*移动索引*/
  if(++hMenu.psCurMenu->idx >= (hMenu.psValueSet->nBit + hMenu.psValueSet->nBit_f))
  {
    hMenu.psCurMenu->idx = 0;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

/*up key 用于 IPv4 设置*/
void MenuUp_IPv4(void)
{
  uint8_t tmp = 0x00;
  
  /*记录修改前的值*/
  tmp = hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3];
  
  /*数值修改范围(0~255)*/
  hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] += CalPowerOfTen((3 - 1) - (hMenu.psCurMenu->idx % 3));
  if(hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] < tmp)
  {
    hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] = tmp;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*down key 用于 IPv4 设置*/
void MenuDown_IPv4(void)
{
  uint8_t tmp = 0x00;
  
  /*记录修改前的值*/
  tmp = hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3];
  
  /*数值修改范围(0~255)*/
  hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] -= CalPowerOfTen((3 - 1) - (hMenu.psCurMenu->idx % 3));
  if(hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] > tmp)
  {
    hMenu.psValueSet->aBit[hMenu.psCurMenu->idx / 3] = tmp;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*right key 用于 IPv4 设置*/
void MenuLeft_IPv4(void)
{
  if(hMenu.psValueSet->nBit > 12)
  {
    hMenu.psValueSet->nBit = 12;
  }
  
  /*移动索引*/
  if(hMenu.psCurMenu->idx-- <= 0)
  {
    hMenu.psCurMenu->idx = hMenu.psValueSet->nBit - 1;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*right key 用于 IPv4 设置*/
void MenuRight_IPv4(void)
{
  if(hMenu.psValueSet->nBit > 12)
  {
    hMenu.psValueSet->nBit = 12;
  }
  
  /*移动索引*/
  if(++hMenu.psCurMenu->idx >= hMenu.psValueSet->nBit)
  {
    hMenu.psCurMenu->idx = 0;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

/*up key 用于 IPv6 设置*/
void MenuUp_IPv6(void)
{
  /*数值修改*/
  hMenu.psValueSet->ipv6[hMenu.psCurMenu->idx / 4] += (0x1000UL >> ((hMenu.psCurMenu->idx % 4) * 4));
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*down key 用于 IPv6 设置*/
void MenuDown_IPv6(void)
{
  /*数值修改*/
  hMenu.psValueSet->ipv6[hMenu.psCurMenu->idx / 4] -= (0x1000UL >> ((hMenu.psCurMenu->idx % 4) * 4));
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}
/*right key 用于 IPv6 设置*/
void MenuLeft_IPv6(void)
{
  if(hMenu.psValueSet->nBit > 32)
  {
    hMenu.psValueSet->nBit = 32;
  }
  
  /*移动索引*/
  if(hMenu.psCurMenu->idx-- <= 0)
  {
    hMenu.psCurMenu->idx = hMenu.psValueSet->nBit - 1;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

/*right key 用于 IPv6 设置*/
void MenuRight_IPv6(void)
{
  if(hMenu.psValueSet->nBit > 32)
  {
    hMenu.psValueSet->nBit = 32;
  }
  
  /*移动索引*/
  if(++hMenu.psCurMenu->idx >= hMenu.psValueSet->nBit)
  {
    hMenu.psCurMenu->idx = 0;
  }
  
  /*刷新动态内容*/
  if(hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic != NULL)
  {
    hMenu.psCurMenu->HMIFunc.pfMenuDispDynamic();
  }
}

使用示例:

1、自己封装一个菜单初始化函数,如:

/*菜单初始化*/
void OLED_MenuInit(void)
{
	struct sMenu *pStart = NULL;
	OLED_clcScreen();
  
	pStart = pageStart;
	
        MenuInit(pStart);
}

2、使用结构体方式定义菜单所需要的元素,如:

void page1S_f(void);
void page1D_f(void);
void page1E_f(void);

struct sMenu page1[] = {NULL,NULL,0,0,0,0,
	-1,
{.pfMenuDispStatic  = page1S_f,
 .pfMenuDispDynamic = page1D_f,
 .pfMenuKeyEnter    = page1E_f,
},
MENU_KEY_Z};

void page1S_f(void)
{
  /*根据需求完成此函数,最好用来显示静态的内容*/
}
void page1D_f(void)
{
  /*根据需求完成此函数,最好用来显示动态的内容*/
}
void page1E_f(void)
{
  /*根据需求完成此函数,最好用来执行按下确认键后的操作*/
}

/**********************************************************/

void page2S_f(void);
void page2D_f(void);
void page2E_f(void);

struct sTitle page2_o_t[] = {
{"关闭",29,88},
{"打开",29,136},
};
struct sMenuItem page2_o[] = {
{&page2_o_t[0],NULL},
{&page2_o_t[1],NULL},
};
struct sTitle page2_t[] = {"子菜单2",0,0};
struct sMenu page2[] = {page2_t,page2_o,COUNTOF(page2_o),0,1,2,
	-1,
{.pfMenuDispStatic  = page2S_f,
 .pfMenuDispDynamic = page2D_f,
 .pfMenuKeyEnter    = page2E_f,
}};

void page2S_f(void)
{
  /*根据需求完成此函数,最好用来显示静态的内容*/
}
void page2D_f(void)
{
  /*根据需求完成此函数,最好用来显示动态的内容*/
}
void page2E_f(void)
{
  /*根据需求完成此函数,最好用来执行按下确认键后的操作*/
}

/**********************************************************/

void pageStart_S_f(void);
void pageStart_D_f(void);

struct sTitle pageStart_o_t[] = {
{"子菜单1",29,28},
{"子菜单2",29,100},
};
struct sMenuItem pageStart_o[] = {
{&pageStart_o_t[0],page1},
{&pageStart_o_t[1],page2},
};
struct sTitle pageStart_t[] = {"主界面",0,0};
struct sMenu pageStart[] = {pageStart_t,NULL,0,0,0,0,
	-1,
{.pfMenuDispStatic  = pageStart_S_f,
 .pfMenuDispDynamic = pageStart_D_f,
}};

static void pageStart_S_f(void)
{
  /*根据需求完成此函数,最好用来显示静态的内容*/
}
static void pageStart_D_f(void)
{
  /*根据需求完成此函数,最好用来显示动态的内容*/
}

3、按照类似的方法便可容易的做出多级菜单的效果

  • 11
    点赞
  • 131
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
### 回答1: Arduino OLED菜单是一种基于Arduino控制板和OLED显示屏的用户界面菜单。该菜单可以通过各种控制板(如Arduino Uno, Mega等)与不同尺寸的OLED显示屏(如128x64、0.96英寸等)配对使用。该菜单使用Arduino的C++编程语言,并且通过简单的接线和中等难度的编程即可实现。 该菜单可以根据用户的需求进行定制和改造,如添加、删除和修改功能,修改菜单项名称和显示内容等。这个菜单可以实现一些小型的功能,例如控制LED,驱动电机,检测温度和湿度等。由于该菜单界面简洁、易于控制和使用,所以它被广泛应用于物联网、电子设备和自动化系统等领域。 在制作Arduino OLED菜单时,需要考虑以下几个方面:显示屏、控制板、菜单项的设计和菜单功能的实现等。使用OLED显示屏可以提供更好的显示效果和更广的视野,而控制板需要能够提供足够的计算能力和实际控制外设的能力。在菜单项的设计中应该考虑清楚,包括名称、参数、选项和对应功能。菜单功能的实现可以通过定义函数和逻辑判断语句来实现。 总的来说,Arduino OLED菜单是一种简单而实用的电子设备控制菜单,它可以方便地与各种外设和传感器协作,实现各种特定的功能。随着物联网和智能家居的快速发展,Arduino OLED菜单将发挥越来越重要的作用。 ### 回答2: Arduino OLED菜单是一种在Arduino开发板上使用的图形用户界面,可以用于创建交互式菜单和图形用户界面。它可以帮助开发者快速简单地构建用户界面,在Arduino上显示各种信息和数据,从而使Arduino与用户友好接口进行交互。 OLED菜单由多种元素组成,如文本,滚动文本,图像,进度条和可点击的对象。开发者可以使用这些元素创建自定义的用户界面,并将其显示在OLED上。Arduino OLED菜单的实现可能会因基于所使用的硬件和库的不同而有所不同。 当Arduino开发板启动时,OLED菜单可能会显示一个初始菜单页面。该菜单页面可以包含多个菜单项,例如“设置”、“信息”、“控制”等,用户可以通过滚动或点击来浏览和选择不同菜单项。每个菜单选项可以连接到另一个菜单页面或一个特定的功能。开发者可以根据需要进行自定义,以满足应用程序的要求。 在一个Arduino项目中,OLED菜单可以帮助用户方便地操作和监控Arduino连接的硬件模块,例如传感器、执行器和其他外设。开发者可以将条目添加到菜单中,以直接访问它们的功能和状态。开发者还可以集成Arduino菜单与其他库和组件,例如图表库和通信库,以增强用户体验。 总而言之,Arduino OLED菜单是一种功能强大且易于使用的用户界面,用于在Arduino项目中显示数据和状态,并提供直观的交互方式。借助该工具,开发人员可以轻松构建可定制的用户界面,并与其项目的硬件模块进行交互。 ### 回答3: Arduino OLED菜单是一种能够帮助人们在Arduino平台上快速构建交互式菜单的解决方案。该方案基于OLED屏幕和Arduino控制器开发,允许用户通过几个简单的步骤来创建自定义菜单,以满足各种应用需求。 在构建一个基于Arduino OLED菜单的应用程序时,用户需要首先选定适合自己需求的OLED屏幕类型和Arduino控制器,然后在该控制器上加载并运行相关的代码库和示例代码。这些代码库中包含了多种不同的菜单样式和操作功能,并且可以在用户自定义代码中进行修改和扩展,以满足不同的需求。 使用Arduino OLED菜单,用户可以轻松地创建一个多级菜单结构来完成其应用程序的各项功能。这些菜单包括活动菜单、子菜单、设置菜单、可编辑项、可选择项等,使得用户可以快速且方便地进行各种设置和参数调整等操作。同时,该菜单还提供了一些强大的操作功能,比如滚动列表、多线程处理、定制主题等,以满足用户对菜单界面的各种要求。 总之,Arduino OLED菜单是一种实用、易用、灵活且高效的解决方案,它能够满足人们在控制器应用程序中对于交互式菜单的各种需求,同时可以随时根据各种场合进行定制和扩展。这是一个非常实用的技术,也是未来经常应用的一种技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值