功能:ESP32 + I2C‑OLED,上下两个按键,实现滚动菜单,
>箭头标记当前选中条目。
一、硬件接线
1.OLED SSD1306 I2C(128*64)
| OLED 引脚 | ESP32 引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 不要接 5V |
| GND | GND | 共地,必须接 |
| SDA | GPIO21 | I2C 数据 |
| SCL | GPIO22 | I2C 时钟 |
2.按键(2 个,不需要外部电阻)
模式
INPUT_PULLUP:GPIO 内部自带上拉电阻,按键只需要把引脚和 GND 短接。
| 按键 | ESP32 引脚 | 另一端 |
|---|---|---|
| 向上按键 BUTTON_UP | GPIO12 | GND |
| 向下按键 BUTTON_DOWN | GPIO14 | GND |
cpp
#include <Wire.h>
#include <Arduino.h>
#include <U8g2lib.h>
#define MENU_SIZE 4
#define BUTTON_UP 12
#define BUTTON_DOWN 14
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
char *menu[MENU_SIZE] = {"Item 1", "Item 2", "Item 3", "Item 4"};
int order = 0;
void display_menu(int index);
void setup()
{
Wire.begin(21,22);
u8g2.setI2CAddress(0x78);
u8g2.begin();
u8g2.setFont(u8g2_font_6x12_tr);
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
}
void loop()
{
if(!digitalRead(BUTTON_UP))
{
order --;
if(order<0)order = MENU_SIZE-1;
delay(150);
}
if(!digitalRead(BUTTON_DOWN))
{
order ++;
if(order>=MENU_SIZE)order=0;
delay(150);
}
display_menu(order);
delay(30);
}
void display_menu(int index)
{
u8g2.clearBuffer();
u8g2.drawStr(0,12,"Menu");
u8g2.drawHLine(0,14,128);
for(int i=0;i<MENU_SIZE;i++)
{
int y = (i+2) * 12 +2;
if(i==index)
{
u8g2.drawStr(5,y,">");
u8g2.drawStr(20,y,menu[i]);
}
else{
u8g2.drawStr(5,y,menu[i]);
}
}
u8g2.sendBuffer();
}

二、程序逻辑流程
1.setup () 初始化
Wire.begin(21,22):初始化 I2C 总线 (SDA=21,SCL=22)- 设置 OLED I2C 写地址
0x78,启动 OLED,设置英文字体 - 按键引脚配置为内部上拉输入 INPUT_PULLUP,按键按下引脚拉到 GND 变为低电平
2.loop () 主循环
- 检测 UP 按键:按下 →
order−1;到最顶部 (order<0),跳到最后一项MENU_SIZE‑1 - 检测 DOWN 按键:按下 →
order+1;到最底部,回到 0 号条目 delay(150)简单软件消抖,避免一次按动跳多行- 调用
display_menu(order)刷新屏幕菜单 - 小延时 30ms,降低 CPU 占用
3.display_menu (int index) 菜单渲染函数
参数index就是当前选中的菜单下标order
clearBuffer()清空内存画面缓存- 绘制标题
Menu,画一条水平分隔线 - for 循环遍历 4 条菜单:
- 如果循环变量
i == index:打印>箭头 + 菜单文字(代表当前选中) - 否则只打印菜单文字
- 如果循环变量
sendBuffer()把整帧画面一次性发送到 OLED 屏幕显示
数组
menu[]存放菜单字符串;order是全局变量,保存当前选中项下标,范围 0~3。