源代码源自:
(Arduino提高篇15—摇杆操作OLED多级菜单_TonyIOT的博客-CSDN博客_arduino 多级菜单
根据个人的理解,画图理解,根据结构体中的变量,我将index索引和界面操作menu分为在一个方格内,表示同一个意思,index有九个界面。
typedef struct
{
unsigned char index;
unsigned char up;
unsigned char down;
unsigned char left;
unsigned char right;
void (*operation)(void);
} KEY_TABLE;
根据上图,其中四个按键对应的功能理解为:
上UP————–同级别界面左移动或无法移动则保持本界面
下DOWN————–同级别界面右移动或无法移动则保持本界面
左LEFT————–返回上一级或无法返回则保持本界面
右RIGHT————–往下一级或无法往下则保持本界面
假设在index为0的情况下,也就是menu11情况下,往up同级别左移动的操作界面为本身menu11,那么对应的索引号为0,down同级别右移的界面为menu12,索引号为1,left返回上级为本身menu11索引号为0,right往下一级移动的界面最先跳到索引号为2的界面,即menu12,
所以inex=0时,结构体数组变量赋值情况为:{0,0,1,0,2,(*menu11)}
同理假如换到index=7的情况,LEFT往上一级为3,RIGHT往下一级是无法移动的,所以是本身索引号7,UP同级别左移无法移动过到6这边(特别注意不是同一树支不能乱移动),只能为7,DOWN右移为8。结构体数组变量赋值情况为:{7, 7, 8, 3, 7, (*menu33)}
那么假设index=1,即menu12,
可以同级别左移,不能右移,可以下移,不能上移,则为{1, 0, 1, 1, 4, (*menu12)}。
综合以上规律总结所有的界面, 完整的结构体数组变量赋值情况为:
KEY_TABLE table[9] =
{
{0, 0, 1, 0, 2, (*menu11)},
{1, 0, 1, 1, 4, (*menu12)},
{2, 2, 3, 0, 5, (*menu21)},
{3, 2, 3, 0, 7, (*menu22)},
{4, 4, 4, 1, 4, (*menu23)},
{5, 5, 6, 2, 5, (*menu31)},
{6, 5, 6, 2, 6, (*menu32)},
{7, 7, 8, 3, 7, (*menu33)},
{8, 7, 8, 3, 8, (*menu34)},
};
原完整代码为
/*
OLED_Menu
摇杆操作OLED多级菜单
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
#define pinX A0
#define pinY A1
int valueX = 0;
int valueY = 0;
unsigned char keyValue = 0;
//定义按键结构体
typedef struct
{
unsigned char index;
unsigned char up;
unsigned char down;
unsigned char left;
unsigned char right;
void (*operation)(void);
} KEY_TABLE;
//定义日期时间结构体变量
struct
{
unsigned char month;
unsigned char day;
unsigned char hour;
unsigned char minute;
} date = {0};
unsigned char funIndex = 0;
void (*current)(void);
void menu11(void);
void menu12(void);
void menu21(void);
void menu22(void);
void menu23(void);
void menu31(void);
void menu32(void);
void menu33(void);
void menu34(void);
//定义按键操作数据
KEY_TABLE table[9] =
{
{0, 0, 1, 0, 2, (*menu11)},
{1, 0, 1, 1, 4, (*menu12)},
{2, 2, 3, 0, 5, (*menu21)},
{3, 2, 3, 0, 7, (*menu22)},
{4, 4, 4, 1, 4, (*menu23)},
{5, 5, 6, 2, 5, (*menu31)},
{6, 5, 6, 2, 6, (*menu32)},
{7, 7, 8, 3, 7, (*menu33)},
{8, 7, 8, 3, 8, (*menu34)},
};
//一级菜单1
void menu11(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Menu--");
display.setCursor(2, 25);//设置显示位置
display.println("->1.Tim");
display.setCursor(2, 50);//设置显示位置
display.println(" 2.About");
display.display(); // 开显示
}
//一级菜单2
void menu12(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Menu--");
display.setCursor(2, 25);//设置显示位置
display.println(" 1.Tim");
display.setCursor(2, 50);//设置显示位置
display.println("->2.About");
display.display(); // 开显示
}
//二级菜单1
void menu21(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Tim--");
display.setCursor(2, 25);//设置显示位置
display.println("->1.Date");
display.setCursor(2, 50);//设置显示位置
display.println(" 2.Time");
display.display(); // 开显示
}
//二级菜单2
void menu22(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Tim--");
display.setCursor(2, 25);//设置显示位置
display.println(" 1.Date");
display.setCursor(2, 50);//设置显示位置
display.println("->2.Time");
display.display(); // 开显示
}
//二级菜单3
void menu23(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--About--");
display.setCursor(2, 25);//设置显示位置
display.println("Multi menu");
display.setTextSize(1); //设置字体大小
display.setCursor(70, 50);//设置显示位置
display.println("-TonyCode");
display.display(); // 开显示
}
//三级菜单1
void menu31(void)
{
date.month++;
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Date--");
display.setCursor(2, 25);//设置显示位置
display.print("->M: ");
display.println(date.month);
display.setCursor(2, 50);//设置显示位置
display.print(" D: ");
display.println(date.day);
display.display(); // 开显示
if (date.month >= 12)
{
date.month = 0;
}
}
//三级菜单2
void menu32(void)
{
date.day++;
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Date--");
display.setCursor(2, 25);//设置显示位置
display.print(" M: ");
display.println(date.month);
display.setCursor(2, 50);//设置显示位置
display.print("->D: ");
display.println(date.day);
display.display(); // 开显示
if (date.day >= 31)
{
date.day = 0;
}
}
//三级菜单3
void menu33(void)
{
date.hour++;
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Time--");
display.setCursor(2, 25);//设置显示位置
display.print("->H: ");
display.println(date.hour);
display.setCursor(2, 50);//设置显示位置
display.print(" M: ");
display.println(date.minute);
display.display(); // 开显示
if (date.hour >= 24)
{
date.hour = 0;
}
}
//三级菜单4
void menu34(void)
{
date.minute++;
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Time--");
display.setCursor(2, 25);//设置显示位置
display.print(" H: ");
display.println(date.hour);
display.setCursor(2, 50);//设置显示位置
display.print("->M: ");
display.println(date.minute);
display.display(); // 开显示
if (date.minute >= 30)
{
date.minute = 0;
}
}
//按键扫描函数
unsigned char keyScan(void)
{
static unsigned char keyUp = 1;
valueX = analogRead(pinX);
valueY = analogRead(pinY);
if (keyUp && ((valueX <= 10) || (valueX >= 1010) || (valueY <= 10) || (valueY >= 1010)))
{
delay(10);
keyUp = 0;
if (valueX <= 10)return 1;
else if (valueX >= 1010)return 2;
else if (valueY <= 10)return 3;
else if (valueY >= 1010)return 4;
} else if ((valueX > 10) && (valueX < 1010) && (valueY > 10) && (valueY < 1010))keyUp = 1;
return 0;
}
void setup()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(WHITE);//开像素点发光
display.clearDisplay();//清屏 vc
menu11();
}
void loop()
{
keyValue = keyScan();
if (keyValue != 0) //每发生一次有效按键就根据按键功能获取对应函数并执行
{
switch (keyValue)//获取按键对应序号
{
case 1: funIndex = table[funIndex].right; break;
case 2: funIndex = table[funIndex].left; break;
case 3: funIndex = table[funIndex].down; break;
case 4: funIndex = table[funIndex].up; break;
}
current = table[funIndex].operation;//根据需要获取对应需要执行的函数
(*current)();//执行获取到的函数
}
}
假如你在二级菜单没有写死数据的的话没加入我们需要修改为两层的界面,我们需要修改的地方的就是索引号部分,往下一级全部改为本身界面的索引号即可。
KEY_TABLE table[5] =
{
{0, 0, 1, 0, 2, (*menu11)},
{1, 0, 1, 1, 4, (*menu12)},
{2, 2, 3, 0, 2, (*menu21)},
{3, 2, 3, 0, 3, (*menu22)},
{4, 4, 4, 1, 4, (*menu23)},
// {5, 5, 6, 2, 5, (*menu31)},
// {6, 5, 6, 2, 6, (*menu32)},
// {7, 7, 8, 3, 7, (*menu33)},
// {8, 7, 8, 3, 8, (*menu34)},
};
将 menu31,menu32,menu33,menu34对应的函数注释掉,完整代码如下:
/*
OLED_Menu
摇杆操作OLED多级菜单
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
#define pinX A0
#define pinY A1
int valueX = 0;
int valueY = 0;
unsigned char keyValue = 0;
//定义按键结构体
typedef struct
{
unsigned char index;//索引号
unsigned char up;//
unsigned char down;
unsigned char left;
unsigned char right;
void (*operation)(void);//操作
} KEY_TABLE;
unsigned char funIndex = 0;
void (*current)(void);
void menu11(void);
void menu12(void);
void menu21(void);
void menu22(void);
void menu23(void);
//定义按键操作数据
KEY_TABLE table[5] =
{
{0, 0, 1, 0, 2, (*menu11)},
{1, 0, 1, 1, 4, (*menu12)},
{2, 2, 3, 0, 2, (*menu21)},
{3, 2, 3, 0, 3, (*menu22)},
{4, 4, 4, 1, 4, (*menu23)},
};
//一级菜单1
void menu11(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Menu--");
display.setCursor(2, 25);//设置显示位置
display.println("->1.Tim");
display.setCursor(2, 50);//设置显示位置
display.println(" 2.About");
display.display(); // 开显示
}
//一级菜单2
void menu12(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Menu--");
display.setCursor(2, 25);//设置显示位置
display.println(" 1.Tim");
display.setCursor(2, 50);//设置显示位置
display.println("->2.About");
display.display(); // 开显示
}
//二级菜单1
void menu21(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Tim--");
display.setCursor(2, 25);//设置显示位置
display.println("->1.Date");
display.setCursor(2, 50);//设置显示位置
display.println(" 2.Time");
display.display(); // 开显示
}
//二级菜单2
void menu22(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--Tim--");
display.setCursor(2, 25);//设置显示位置
display.println(" 1.Date");
display.setCursor(2, 50);//设置显示位置
display.println("->2.Time");
display.display(); // 开显示
}
//二级菜单3
void menu23(void)
{
display.clearDisplay();//清屏
display.setTextSize(2); //设置字体大小
display.setCursor(15, 2);//设置显示位置
display.println("--About--");
display.setCursor(2, 25);//设置显示位置
display.println("Multi menu");
display.setTextSize(1); //设置字体大小
display.setCursor(70, 50);//设置显示位置
display.println("-TonyCode");
display.display(); // 开显示
}
//按键扫描函数
unsigned char keyScan(void)
{
static unsigned char keyUp = 1;
valueX = analogRead(pinX);
valueY = analogRead(pinY);
if (keyUp && ((valueX <= 10) || (valueX >= 1010) || (valueY <= 10) || (valueY >= 1010)))
{
delay(10);
keyUp = 0;
if (valueX <= 10)return 1;
else if (valueX >= 1010)return 2;
else if (valueY <= 10)return 3;
else if (valueY >= 1010)return 4;
} else if ((valueX > 10) && (valueX < 1010) && (valueY > 10) && (valueY < 1010))keyUp = 1;
return 0;
}
void setup()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(WHITE);//开像素点发光
display.clearDisplay();//清屏 vc
menu11();
}
void loop()
{
keyValue = keyScan();
if (keyValue != 0) //每发生一次有效按键就根据按键功能获取对应函数并执行
{
switch (keyValue)//获取按键对应序号
{
case 1: funIndex = table[funIndex].right; break;
case 2: funIndex = table[funIndex].left; break;
case 3: funIndex = table[funIndex].down; break;
case 4: funIndex = table[funIndex].up; break;
}
current = table[funIndex].operation;//根据需要获取对应需要执行的函数
(*current)();//执行获取到的函数
}
}
上UP————–同级别界面左移动或无法移动则保持本界面
下DOWN————–同级别界面右移动或无法移动则保持本界面
左LEFT————–返回上一级或无法返回则保持本界面
右RIGHT————–往下一级或无法往下则保持本界面
关于如何将按键返回值0-3转换为跳转功能0-8的,涉及到这个函数funIndex = table[funIndex].right,原始定义的index为0,按下一个按键,假如右键,那么对应的索引号为2,我们需要将数组中的数字提取出来,用的是这个转换funIndex = table[funIndex].right=2,
那么再转到执行函数current=table[2].operation,展示menu21的界面
可精简为:
table[funIndex].operation();