使用U8g2库为XFP1116-07AY(128x64 OLED)实现菜单功能,核心是通过按键控制菜单切换、光标移动和选项选中,结合U8g2的绘图/文本函数实现交互逻辑支持多级菜单(主菜单→子菜单→功能执行),并兼容ESP8266的按键输入。
一、核心思路
- 菜单结构设计:采用“数组+索引”管理菜单(主菜单包含多个选项,部分选项跳转至子菜单);
- 交互控制:通过2个按键(上/下移动光标、确认进入子菜单/执行功能);
- 显示逻辑:每次按键后清空缓冲区,重新绘制当前菜单和光标位置;
- 状态保存:记录当前菜单层级和光标位置,确保切换不丢失状态。
二、硬件接线
组件 | ESP8266引脚 | 说明 |
---|---|---|
上移按键 | D5 | 一端接D5,一端接GND(下拉) |
下移按键 | D6 | 一端接D6,一端接GND(下拉) |
确认按键 | D7 | 一端接D7,一端接GND(下拉) |
XFP1116-07AY | SDA=D2、SCL=D1 | OLED的I2C引脚(不变) |
三、完整代码(多级菜单+按键控制)
#include <Wire.h>
#include <U8g2lib.h>// 1. 初始化U8g2(适配XFP1116-07AY:SH1106控制器,128x64,I2C引脚D2=SDA、D1=SCL)
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, D1, D2);// 2. 定义菜单结构(菜单层级:0=主菜单,1=子菜单1,2=子菜单2)
// 菜单选项格式:{选项名称, 子菜单层级(-1=无子类,执行功能)}
const struct MenuItem {const char* name; // 选项文字int subMenuLevel; // 子菜单层级(-1=执行功能,0=主菜单,1=子菜单1,2=子菜单2)
} menuList[3][4] = {// 主菜单(层级0):4个选项{{"1. 系统设置", 1}, // 进入子菜单1(系统设置){"2. 显示控制", 2}, // 进入子菜单2(显示控制){"3. 关于设备", -1}, // 无子类,执行“关于”功能{"4. 退出", -1} // 无子类,执行“退出”功能},// 子菜单1(系统设置,层级1):3个选项{{"1.1 亮度调节", -1}, // 执行“亮度调节”{"1.2 恢复默认", -1}, // 执行“恢复默认”{"1.3 返回上一级", 0}, // 返回主菜单(层级0){NULL, -1} // 占位符(无更多选项)},// 子菜单2(显示控制,层级2):3个选项{{"2.1 字体切换", -1}, // 执行“字体切换”{"2.2 清屏测试", -1}, // 执行“清屏测试”{"2.3 返回上一级", 0}, // 返回主菜单(层级0){NULL, -1} // 占位符}
};// 3. 菜单状态变量(记录当前状态)
int currentMenuLevel = 0; // 当前菜单层级(默认主菜单0)
int currentCursor = 0; // 当前光标位置(默认第1个选项)
int menuItemCount[3] = {4, 3, 3}; // 各层级的选项数量(主菜单4个,子菜单1/2各3个)// 4. 按键引脚定义
const int KEY_UP = D5;
const int KEY_DOWN = D6;
const int KEY_CONFIRM = D7;// 5. 函数声明(提前声明,避免编译错误)
void drawMenu(); // 绘制当前菜单和光标
void handleKeyInput(); // 处理按键输入
void executeMenuAction(); // 执行选中选项的功能void setup() {// 初始化OLEDu8g2.begin();u8g2.enableUTF8Print(); // 启用UTF8(支持中文)u8g2.setFont(u8g2_font_wqy12_t_gb2312); // 中文支持字体// 初始化按键引脚(下拉输入:按键未按则为高电平,按下为低电平)pinMode(KEY_UP, INPUT_PULLUP);pinMode(KEY_DOWN, INPUT_PULLUP);pinMode(KEY_CONFIRM, INPUT_PULLUP);// 初始绘制主菜单drawMenu();
}void loop() {handleKeyInput(); // 持续检测按键delay(100); // 消抖,避免按键误触发
}// 绘制当前菜单:标题+选项+光标
void drawMenu() {u8g2.clearBuffer(); // 清空缓冲区// 1. 绘制菜单标题(不同层级显示不同标题)u8g2.setCursor(0, 15); // 标题位置(y=15,避免顶部裁切)switch (currentMenuLevel) {case 0: u8g2.print("主菜单"); break;case 1: u8g2.print("系统设置"); break;case 2: u8g2.print("显示控制"); break;}u8g2.drawHLine(0, 20, 128); // 标题下方画一条横线(分隔标题和选项)// 2. 绘制当前菜单的所有选项(从y=35开始,每个选项间隔18像素)for (int i = 0; i < menuItemCount[currentMenuLevel]; i++) {int yPos = 35 + i * 18; // 选项y坐标(间隔18像素,适配12号字体)u8g2.setCursor(10, yPos); // 选项左移10像素,避免贴边// 光标位置:当前选中的选项前加“> ”标记if (i == currentCursor) {u8g2.print("> "); // 光标符号} else {u8g2.print(" "); // 非选中项留空,对齐格式}// 绘制选项文字u8g2.print(menuList[currentMenuLevel][i].name);}u8g2.sendBuffer(); // 刷新屏幕,显示菜单
}// 处理按键输入:上移、下移、确认
void handleKeyInput() {// 上移按键(按下时电平为LOW)if (digitalRead(KEY_UP) == LOW) {delay(50); // 消抖(避免按键抖动导致多次触发)if (digitalRead(KEY_UP) == LOW) {currentCursor--; // 光标上移// 边界处理:光标到顶部后,循环到最后一个选项if (currentCursor < 0) {currentCursor = menuItemCount[currentMenuLevel] - 1;}drawMenu(); // 重新绘制菜单// 等待按键释放(避免长按连续触发)while (digitalRead(KEY_UP) == LOW);}}// 下移按键if (digitalRead(KEY_DOWN) == LOW) {delay(50);if (digitalRead(KEY_DOWN) == LOW) {currentCursor++; // 光标下移// 边界处理:光标到顶部后,循环到第一个选项if (currentCursor >= menuItemCount[currentMenuLevel]) {currentCursor = 0;}drawMenu();while (digitalRead(KEY_DOWN) == LOW);}}// 确认按键(进入子菜单或执行功能)if (digitalRead(KEY_CONFIRM) == LOW) {delay(50);if (digitalRead(KEY_CONFIRM) == LOW) {executeMenuAction(); // 执行当前选中选项的逻辑while (digitalRead(KEY_CONFIRM) == LOW);}}
}// 执行当前选中选项的功能(进入子菜单或显示功能提示)
void executeMenuAction() {// 获取当前选中选项的“子菜单层级”int targetLevel = menuList[currentMenuLevel][currentCursor].subMenuLevel;if (targetLevel != -1) {// 情况1:有子菜单,切换到目标层级,光标重置为0currentMenuLevel = targetLevel;currentCursor = 0;drawMenu(); // 绘制子菜单} else {// 情况2:无子类,执行对应功能(显示提示信息2秒后返回当前菜单)u8g2.clearBuffer();u8g2.setCursor(10, 30); // 提示文字居中// 根据当前选项执行不同提示if (currentMenuLevel == 0) {switch (currentCursor) {case 2: u8g2.print("设备:XFP1116-07AY"); break; // 关于设备case 3: u8g2.print("已退出!"); break; // 退出}} else if (currentMenuLevel == 1) {switch (currentCursor) {case 0: u8g2.print("亮度已调节为50%"); break; // 亮度调节case 1: u8g2.print("已恢复默认设置"); break; // 恢复默认}} else if (currentMenuLevel == 2) {switch (currentCursor) {case 0: u8g2.print("字体已切换为默认"); break; // 字体切换case 1: u8g2.clearBuffer(); u8g2.print("清屏测试中..."); break; // 清屏}}u8g2.sendBuffer(); // 显示提示delay(2000); // 提示显示2秒drawMenu(); // 返回当前菜单}
}
四、代码说明
1. 菜单结构扩展
若需增加更多菜单层级或选项,只需:
- 在
menuList
数组中新增层级(如menuList[3][...]
作为“子菜单3”); - 同步更新
menuItemCount
数组(记录新层级的选项数量); - 在
executeMenuAction()
中添加新层级的功能逻辑。
2. 字体与显示优化
- 若需更大字体,可替换
u8g2.setFont()
的参数(如u8g2_font_wqy16_t_gb2312
),但需同步调整选项的yPos
间隔(避免选项重叠); - 若需支持英文,可改用英文字体(如
u8g2_font_ncenB12_tr
),无需启用enableUTF8Print()
。
3. 按键优化
- 若按键触发不灵敏,可调整
delay(50)
的消抖时间(如改为delay(30)
); - 若需支持“长按快速移动光标”,可在
handleKeyInput()
中添加长按检测逻辑(如判断按键按下时间超过500ms后,每100ms移动一次光标)。
五、运行效果
- 上电后显示“主菜单”,光标默认停在“1. 系统设置”;
- 按“上/下键”移动光标,光标前的“> ”标记跟随移动;
- 按“确认键”:
- 选中“1. 系统设置”→ 进入“系统设置”子菜单;
- 选中“3. 关于设备”→ 显示设备信息2秒后返回主菜单;
- 选中“1.3 返回上一级”→ 从子菜单返回主菜单。
该代码可直接上传使用,也可根据实际需求(如增加参数调节、数据显示)扩展executeMenuAction()
中的功能逻辑。