从旋钮到屏幕手把手教你用AB相编码器在Arduino/ESP32上做菜单交互完整项目在智能硬件开发中人机交互设计往往决定了产品的用户体验。传统的按钮操作方式已经无法满足现代设备对快速导航和参数调整的需求而旋转编码器以其直观的交互方式成为许多创客项目的首选。本文将带你从零开始使用常见的EC11编码器和OLED屏幕为你的Arduino或ESP32设备打造一个完整的菜单控制系统。1. 硬件准备与连接1.1 所需材料清单主控板Arduino Uno/Nano或ESP32开发板本文示例兼容两者旋转编码器EC11型AB相编码器带按压功能显示屏0.96寸I2C OLED屏幕128x64分辨率其他面包板、杜邦线、10kΩ上拉电阻如编码器内部无上拉1.2 电路连接示意图将各组件按以下方式连接组件引脚Arduino/ESP32连接编码器A相D2支持外部中断的引脚编码器B相D3支持外部中断的引脚编码器按键D4普通数字输入编码器GNDGND编码器VCC3.3V/5V根据编码器规格OLED SDAA4Arduino或D21ESP32OLED SCLA5Arduino或D22ESP32OLED GNDGNDOLED VCC3.3V注意ESP32的引脚分配更灵活但建议优先使用支持硬件中断的GPIO2. 软件环境搭建2.1 必需库安装在Arduino IDE中安装以下库Encoder库用于高效处理编码器脉冲作者Paul StoffregenU8g2库强大的OLED显示驱动库作者olikraus安装方法打开Arduino IDE点击工具→管理库...搜索并安装上述库2.2 基础代码框架创建一个新项目包含以下基本结构#include Encoder.h #include U8g2lib.h // 硬件引脚定义 #define ENC_A 2 #define ENC_B 3 #define ENC_SW 4 // 初始化对象 Encoder myEnc(ENC_A, ENC_B); U8g2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); void setup() { pinMode(ENC_SW, INPUT_PULLUP); u8g2.begin(); u8g2.setFont(u8g2_font_6x10_tf); } void loop() { // 主循环代码将在这里实现 }3. 编码器处理与防抖3.1 高效读取编码器状态使用Encoder库可以简化方向判断long oldPosition -999; void checkEncoder() { long newPosition myEnc.read(); if (newPosition ! oldPosition) { int direction (newPosition oldPosition) ? 1 : -1; oldPosition newPosition; // 根据direction值处理菜单导航 navigateMenu(direction); } }3.2 按键消抖处理编码器的按键通常需要软件消抖bool readButton() { static unsigned long lastDebounceTime 0; static bool lastButtonState HIGH; bool buttonState digitalRead(ENC_SW); if (buttonState ! lastButtonState) { lastDebounceTime millis(); } if ((millis() - lastDebounceTime) 50) { if (buttonState LOW) { lastDebounceTime millis(); return true; } } lastButtonState buttonState; return false; }4. 菜单系统实现4.1 菜单数据结构设计采用分层式菜单结构struct MenuItem { const char* name; int value; int minVal; int maxVal; MenuItem* parent; MenuItem* child; MenuItem* next; }; // 示例菜单项定义 MenuItem brightness {亮度, 50, 0, 100}; MenuItem contrast {对比度, 70, 30, 100}; MenuItem settingsMenu {设置, 0, 0, 0, nullptr, brightness}; MenuItem mainMenu {主菜单, 0, 0, 0, nullptr, settingsMenu}; MenuItem* currentMenu mainMenu; MenuItem* currentItem mainMenu;4.2 菜单导航逻辑实现基本的菜单操作函数void navigateMenu(int direction) { if (currentItem-child ! nullptr) { // 进入子菜单 currentMenu currentItem; currentItem currentItem-child; } else if (currentItem-value ! -1) { // 调整参数值 currentItem-value direction; if (currentItem-value currentItem-maxVal) currentItem-value currentItem-maxVal; if (currentItem-value currentItem-minVal) currentItem-value currentItem-minVal; } else { // 在兄弟菜单项间导航 if (direction 0 currentItem-next ! nullptr) { currentItem currentItem-next; } else if (direction 0 currentItem ! currentMenu-child) { MenuItem* temp currentMenu-child; while (temp-next ! currentItem) temp temp-next; currentItem temp; } } drawMenu(); } void handleButtonPress() { if (currentItem-parent ! nullptr) { // 返回上级菜单 currentItem currentItem-parent; currentMenu currentItem-parent; } drawMenu(); }4.3 菜单渲染实现使用U8g2库绘制菜单界面void drawMenu() { u8g2.clearBuffer(); // 绘制标题栏 u8g2.drawStr(0, 10, currentMenu-name); u8g2.drawHLine(0, 12, 128); // 绘制菜单项 MenuItem* item currentMenu-child; int yPos 25; while (item ! nullptr) { if (item currentItem) { u8g2.drawBox(0, yPos-8, 128, 10); u8g2.setDrawColor(0); } char buf[32]; if (item-value ! -1) { snprintf(buf, sizeof(buf), %s: %d, item-name, item-value); } else { snprintf(buf, sizeof(buf), %s, item-name); } u8g2.drawStr(5, yPos, buf); if (item currentItem) { u8g2.setDrawColor(1); } item item-next; yPos 12; } u8g2.sendBuffer(); }5. 完整项目集成5.1 主循环实现将各个模块整合到主程序中void loop() { checkEncoder(); if (readButton()) { handleButtonPress(); while (readButton()); // 等待按键释放 } // 其他任务可以在这里添加 delay(10); }5.2 进阶功能扩展为菜单系统添加更多实用功能参数保存使用EEPROM保存设置#include EEPROM.h void saveSettings() { int addr 0; MenuItem* item brightness; while (item ! nullptr) { EEPROM.put(addr, item-value); addr sizeof(int); item item-next; } EEPROM.commit(); } void loadSettings() { int addr 0; MenuItem* item brightness; while (item ! nullptr) { EEPROM.get(addr, item-value); addr sizeof(int); item item-next; } }动画效果为菜单切换添加过渡动画多语言支持根据用户选择显示不同语言5.3 性能优化技巧使用ESP32的双核特性将编码器处理放在一个核心显示和菜单逻辑放在另一个核心部分刷新只更新屏幕上变化的部分减少刷新时间事件驱动使用中断代替轮询检测编码器变化6. 实际应用案例6.1 智能家居控制面板将这套系统应用于智能家居中控控制灯光亮度和色温调节空调温度查看传感器数据温湿度等6.2 3D打印机控制界面为3D打印机添加便捷的操作面板调整打印速度和温度选择打印文件控制电机移动6.3 音频设备控制器制作专业的音频控制界面调节音量、平衡切换输入源设置EQ参数在最近的一个智能温室项目中这套菜单系统被用来控制光照周期、通风设置和灌溉参数。通过旋转编码器的直观操作用户可以快速在不同参数间切换调整而OLED屏幕则清晰地显示了当前状态和设置值。实际使用中发现为频繁调整的参数如光照强度设置快速访问快捷键能显著提升用户体验。
从旋钮到屏幕:手把手教你用AB相编码器在Arduino/ESP32上做菜单交互(完整项目)
从旋钮到屏幕手把手教你用AB相编码器在Arduino/ESP32上做菜单交互完整项目在智能硬件开发中人机交互设计往往决定了产品的用户体验。传统的按钮操作方式已经无法满足现代设备对快速导航和参数调整的需求而旋转编码器以其直观的交互方式成为许多创客项目的首选。本文将带你从零开始使用常见的EC11编码器和OLED屏幕为你的Arduino或ESP32设备打造一个完整的菜单控制系统。1. 硬件准备与连接1.1 所需材料清单主控板Arduino Uno/Nano或ESP32开发板本文示例兼容两者旋转编码器EC11型AB相编码器带按压功能显示屏0.96寸I2C OLED屏幕128x64分辨率其他面包板、杜邦线、10kΩ上拉电阻如编码器内部无上拉1.2 电路连接示意图将各组件按以下方式连接组件引脚Arduino/ESP32连接编码器A相D2支持外部中断的引脚编码器B相D3支持外部中断的引脚编码器按键D4普通数字输入编码器GNDGND编码器VCC3.3V/5V根据编码器规格OLED SDAA4Arduino或D21ESP32OLED SCLA5Arduino或D22ESP32OLED GNDGNDOLED VCC3.3V注意ESP32的引脚分配更灵活但建议优先使用支持硬件中断的GPIO2. 软件环境搭建2.1 必需库安装在Arduino IDE中安装以下库Encoder库用于高效处理编码器脉冲作者Paul StoffregenU8g2库强大的OLED显示驱动库作者olikraus安装方法打开Arduino IDE点击工具→管理库...搜索并安装上述库2.2 基础代码框架创建一个新项目包含以下基本结构#include Encoder.h #include U8g2lib.h // 硬件引脚定义 #define ENC_A 2 #define ENC_B 3 #define ENC_SW 4 // 初始化对象 Encoder myEnc(ENC_A, ENC_B); U8g2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); void setup() { pinMode(ENC_SW, INPUT_PULLUP); u8g2.begin(); u8g2.setFont(u8g2_font_6x10_tf); } void loop() { // 主循环代码将在这里实现 }3. 编码器处理与防抖3.1 高效读取编码器状态使用Encoder库可以简化方向判断long oldPosition -999; void checkEncoder() { long newPosition myEnc.read(); if (newPosition ! oldPosition) { int direction (newPosition oldPosition) ? 1 : -1; oldPosition newPosition; // 根据direction值处理菜单导航 navigateMenu(direction); } }3.2 按键消抖处理编码器的按键通常需要软件消抖bool readButton() { static unsigned long lastDebounceTime 0; static bool lastButtonState HIGH; bool buttonState digitalRead(ENC_SW); if (buttonState ! lastButtonState) { lastDebounceTime millis(); } if ((millis() - lastDebounceTime) 50) { if (buttonState LOW) { lastDebounceTime millis(); return true; } } lastButtonState buttonState; return false; }4. 菜单系统实现4.1 菜单数据结构设计采用分层式菜单结构struct MenuItem { const char* name; int value; int minVal; int maxVal; MenuItem* parent; MenuItem* child; MenuItem* next; }; // 示例菜单项定义 MenuItem brightness {亮度, 50, 0, 100}; MenuItem contrast {对比度, 70, 30, 100}; MenuItem settingsMenu {设置, 0, 0, 0, nullptr, brightness}; MenuItem mainMenu {主菜单, 0, 0, 0, nullptr, settingsMenu}; MenuItem* currentMenu mainMenu; MenuItem* currentItem mainMenu;4.2 菜单导航逻辑实现基本的菜单操作函数void navigateMenu(int direction) { if (currentItem-child ! nullptr) { // 进入子菜单 currentMenu currentItem; currentItem currentItem-child; } else if (currentItem-value ! -1) { // 调整参数值 currentItem-value direction; if (currentItem-value currentItem-maxVal) currentItem-value currentItem-maxVal; if (currentItem-value currentItem-minVal) currentItem-value currentItem-minVal; } else { // 在兄弟菜单项间导航 if (direction 0 currentItem-next ! nullptr) { currentItem currentItem-next; } else if (direction 0 currentItem ! currentMenu-child) { MenuItem* temp currentMenu-child; while (temp-next ! currentItem) temp temp-next; currentItem temp; } } drawMenu(); } void handleButtonPress() { if (currentItem-parent ! nullptr) { // 返回上级菜单 currentItem currentItem-parent; currentMenu currentItem-parent; } drawMenu(); }4.3 菜单渲染实现使用U8g2库绘制菜单界面void drawMenu() { u8g2.clearBuffer(); // 绘制标题栏 u8g2.drawStr(0, 10, currentMenu-name); u8g2.drawHLine(0, 12, 128); // 绘制菜单项 MenuItem* item currentMenu-child; int yPos 25; while (item ! nullptr) { if (item currentItem) { u8g2.drawBox(0, yPos-8, 128, 10); u8g2.setDrawColor(0); } char buf[32]; if (item-value ! -1) { snprintf(buf, sizeof(buf), %s: %d, item-name, item-value); } else { snprintf(buf, sizeof(buf), %s, item-name); } u8g2.drawStr(5, yPos, buf); if (item currentItem) { u8g2.setDrawColor(1); } item item-next; yPos 12; } u8g2.sendBuffer(); }5. 完整项目集成5.1 主循环实现将各个模块整合到主程序中void loop() { checkEncoder(); if (readButton()) { handleButtonPress(); while (readButton()); // 等待按键释放 } // 其他任务可以在这里添加 delay(10); }5.2 进阶功能扩展为菜单系统添加更多实用功能参数保存使用EEPROM保存设置#include EEPROM.h void saveSettings() { int addr 0; MenuItem* item brightness; while (item ! nullptr) { EEPROM.put(addr, item-value); addr sizeof(int); item item-next; } EEPROM.commit(); } void loadSettings() { int addr 0; MenuItem* item brightness; while (item ! nullptr) { EEPROM.get(addr, item-value); addr sizeof(int); item item-next; } }动画效果为菜单切换添加过渡动画多语言支持根据用户选择显示不同语言5.3 性能优化技巧使用ESP32的双核特性将编码器处理放在一个核心显示和菜单逻辑放在另一个核心部分刷新只更新屏幕上变化的部分减少刷新时间事件驱动使用中断代替轮询检测编码器变化6. 实际应用案例6.1 智能家居控制面板将这套系统应用于智能家居中控控制灯光亮度和色温调节空调温度查看传感器数据温湿度等6.2 3D打印机控制界面为3D打印机添加便捷的操作面板调整打印速度和温度选择打印文件控制电机移动6.3 音频设备控制器制作专业的音频控制界面调节音量、平衡切换输入源设置EQ参数在最近的一个智能温室项目中这套菜单系统被用来控制光照周期、通风设置和灌溉参数。通过旋转编码器的直观操作用户可以快速在不同参数间切换调整而OLED屏幕则清晰地显示了当前状态和设置值。实际使用中发现为频繁调整的参数如光照强度设置快速访问快捷键能显著提升用户体验。