使用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()
中的功能邏輯。