esp32上使用旋转编码器操作的oled汉字菜单-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 957|回复: 5

esp32上使用旋转编码器操作的oled汉字菜单

[复制链接]
发表于 2022-6-24 08:50 | 显示全部楼层 |阅读模式
最近做的小应用需要使用0.96oled屏显示,旋转编码器操作的汉字菜单。在百度上搜了一圈也没有啥合适的例程。

还好用Bing找到了一个英文的,https://github.com/alanesq/BasicOLEDMenu 测了一下挺好的,顺便用U8g2_for_Adafruit_GFX进行了一下汉化。贴出来大家参考一下。
在本社区潜水多年,第一次发帖,时间仓促,水平有限,请凑合着看,不足之处还望见谅。
测试硬件包括:
  • NodeMCU32板子
  • 0.96oled12864显示屏
  • EC11旋转编码器
  • 杜邦线

测试硬件

测试硬件


不废话了,直接上代码:
  1. /**************************************************************************************************
  2. *原版:
  3. *      在esp32上通过OLED屏实现简单的非阻塞模式菜单系统 - i2c版本SSD1306 - 2022年3月30日
  4. *      
  5. *      转自:  https://github.com/alanesq/BasicOLEDMenu
  6. *
  7. *
  8. **************************************************************************************************

  9. 概述,在oled上显示一个菜单,当选择一个项目时,它会设置一个标志并等待事件发生。128x64 oled上的最大菜单项为四个。

  10. 注意:    字号1 = 21 x 8   0.96oLED小屏幕使用英文显示的字符
  11.           字号2 = 10 x 4

  12. 更多有关oled的信息    参见: https://randomnerdtutorials.com/guide-for-oled-display-with-arduino/

  13. 有关如何使用菜单的示例,请参见“此处下方的菜单代码”部分

  14. 注意:如果显示屏上出现垃圾,设备锁定等,可能是旋转编码器接触不良



  15. **************************************************************************************************/
  16. /**************************************************************************************************
  17. *汉化版:
  18. *      在esp32、esp8266上通过OLED屏实现简单的非阻塞模式汉字菜单系统 - i2c版本SSD1306 - 2022年6月24日汉化改写
  19. *                           使用旋转编码器进行操作
  20. **************************************************************************************************

  21. 概述,在原版基础上对代码进行了部分改写,主要是使用U8g2_for_Adafruit_GFX库对菜单显示进行了汉化,顺便汉化了代码注释。

  22. 注:    字号1 = 12t汉字
  23.         字号2 = 14t汉字

  24. 实际上14t汉字在视觉上比12t大的有限,反而占用了不少程序空间。以esp32为例,使用12t和14t两种字体占用程序空间40%,而只使用12t一种字体占用29%。


  25. **************************************************************************************************/

  26. #include <Arduino.h>         // platformIO需要
  27. #include <Wire.h>
  28. #include <Adafruit_GFX.h>
  29. #include <Adafruit_SSD1306.h>
  30. #include <U8g2_for_Adafruit_GFX.h> //一套基于U8g2字体引擎来通过Adafruit GFX来显示文字的第三方库。

  31. // ----------------------------------------------------------------
  32. //                         设置
  33. // ----------------------------------------------------------------

  34.     //  esp32 NodeMcu32S
  35.       #define encoder0PinA  32                  // 旋转编码器CLK引脚 - 32
  36.       #define encoder0PinB  33                  // 旋转编码器DT引脚 - 33
  37.       #define encoder0Press 25                  // 旋转编码器SW引脚(按钮)- 25
  38.       #define OLEDC 22                          // oled屏clock引脚(设成 -1 表示使用缺省值) - 22
  39.       #define OLEDD 21                          // oled屏data引脚 - 21
  40.       #define OLEDE -1                          // oled屏enable引脚(设成 -1 表示未使用)


  41.     // oLED
  42.       #define OLED_ADDR 0x3C                    // OLED i2c 地址
  43.       #define SCREEN_WIDTH 128                  // OLED显示宽度,以像素表示(通常是128)
  44.       #define SCREEN_HEIGHT 64                  // OLED显示高度,以像素表示(0.96oLED小屏幕通常是64)
  45.       #define OLED_RESET -1                     // Reset引脚 (-1表示和Arduino板子共享reset引脚)

  46.     // 其他
  47.       const int serialDebug = 1;
  48.       const int iLED = 16;                      // 板载led灯引脚
  49.       #define BUTTONPRESSEDSTATE 0              // 旋转编码器按钮按下的逻辑值(通常是0)
  50.       #define DEBOUNCEDELAY 100                 // 按钮输入消抖
  51.       const int menuTimeout = 10;               // 菜单非活动超时(秒)
  52.       const bool menuLargeText = 1;             // 尽可能显示较大的文字(如果难以阅读较小的文字)
  53.       const int maxmenuItems = 12;              // 所有菜单中使用的最大项目数(尽可能低以节省内存)
  54.       const int itemTrigger = 1;                // 旋转编码器每个刻度的计数(在编码器转动中变化,通常为1或2)
  55.       const int topLine = 18;                   // 显示器下部区域的纵向(y)起始位置(两色分色屏通常是18)
  56.       const byte lineSpace1 = 12;               // 字号1的行距(正文,小字)
  57.       const byte lineSpace2 = 14;               // 字号2的行距(标题,大字)
  58.       const int displayMaxLines = 4;            // 使用字号1时oLED屏下部可显示的最大行数(通常0.96oLED小屏幕使用英文为5)
  59.       const int MaxmenuTitleLength = 20;        // 使用字号2(标题)时,每行最多字符数(通常为英文为10)

  60.       
  61. // -------------------------------------------------------------------------------------------------
  62.       #define FONT12 u8g2_font_wqy12_t_gb2312b //字号1
  63.       #define FONT14 u8g2_font_wqy14_t_gb2312b //字号2
  64. //--------------------------------------------------------------------------------------------------


  65. // 提前声明函数 - PlatformIO需要
  66.   void ICACHE_RAM_ATTR doEncoder();
  67.   void demoMenu();
  68.   void menuActions();
  69.   void value_non();
  70.   void menuValues();
  71.   void reUpdateButton();
  72.   void serviceMenu();
  73.   int  serviceValue(bool _blocking);
  74.   void createList(String _title, int _noOfElements, String *_list);
  75.   void displayMessage(String _title, String _message);
  76.   void resetMenu();



  77.   // 菜单系统可以处于的模式
  78.   enum menuModes {
  79.       off,                                  // 息屏
  80.       menu,                                 // 菜单处于活动状态
  81.       value,                                // “输入某值”非阻塞,处于活动状态
  82.       message,                              // 显示信息
  83.       blocking                              // 正在执行阻塞过程(请参见输入值)
  84.   };
  85.   menuModes menuMode = off;                 // 启动时的默认模式为关闭

  86.   struct oledMenus {
  87.     // 菜单
  88.     String menuTitle = "";                    // 活动模式时的标题
  89.     int noOfmenuItems = 0;                    // 活动菜单中的菜单项编号
  90.     int selectedMenuItem = 0;                 // 选择菜单项时,将在此处标记该菜单项,直到操作并清除为止
  91.     int highlightedMenuItem = 0;              // 菜单中当前突出显示哪个项目
  92.     String menuItems[maxmenuItems+1];         // 存储菜单项标题
  93.     uint32_t lastMenuActivity = 0;            // 菜单上次见到有所活动的时间(用于超时)
  94.     // '数值输入'
  95.     int mValueEntered = 0;                    // 存储输入菜单输入的数字
  96.     int mValueLow = 0;                        // 最低允许值
  97.     int mValueHigh = 0;                       // 最高允许值
  98.     int mValueStep = 0;                       // 转动旋转编码器时的步长
  99.   };
  100.   oledMenus oledMenu; // 定义oledMenus类

  101.   struct rotaryEncoders {
  102.     volatile int encoder0Pos = 0;             // 使用旋转编码器选择的当前值(通过中断例程更新)
  103.     volatile bool encoderPrevA;               // 用于旋转编码器消抖
  104.     volatile bool encoderPrevB;               // 用于旋转编码器消抖
  105.     uint32_t reLastButtonChange = 0;          // 上次更改按钮状态的时间(用于消抖)
  106.     bool encoderPrevButton = 0;               // 用于按钮消抖
  107.     int reButtonDebounced = 0;                // 消抖后当前按钮状态(按下时为1)
  108.     const bool reButtonPressedState = BUTTONPRESSEDSTATE;  // 按钮按下的逻辑值
  109.     const uint32_t reDebounceDelay = DEBOUNCEDELAY;        // 按钮消抖的延迟设置
  110.     bool reButtonPressed = 0;                 // 按下按钮时设置的标志(必须手动重置)
  111.   };
  112.   rotaryEncoders rotaryEncoder;

  113. // oled SSD1306 通过I2C方式连接进行显示
  114.   Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  115.   U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

  116. // -------------------------------------------------------------------------------------------------
  117. //                                         以下为菜单代码
  118. // -------------------------------------------------------------------------------------------------


  119. // 启动缺省菜单
  120. void defaultMenu() {
  121.   demoMenu();
  122. }
  123. //演示菜单1
  124. void menu1(){
  125.   displayMessage("演示菜单2", "汉字显示\n可以换成其他代码\nOK!");    // 每行21个英文字符,10个12_t的汉字"\n" = 下一行
  126.   }
  127. //演示菜单2
  128. void menu2(){
  129.   resetMenu();
  130.   menuMode = menu;
  131.   displayMessage("演示菜单2", "汉字显示\n中文测试通过!\nOK!");   
  132.   }
  133. //                -----------------------------------------------


  134. // 演示菜单
  135. void demoMenu() {
  136.   resetMenu();                          // 清除任何以前的菜单
  137.   menuMode = menu;                      // 启用菜单模式
  138.   oledMenu.noOfmenuItems = 8;           // 设置此菜单中的项目数
  139.   oledMenu.menuTitle = "菜单系统";     // 菜单标题(用于标识)
  140.   oledMenu.menuItems[1] = "测试菜单";      // 设置菜单项
  141.   oledMenu.menuItems[2] = "菜单验证";
  142.   oledMenu.menuItems[3] = "快速菜单";
  143.   oledMenu.menuItems[4] = "开或关";
  144.   oledMenu.menuItems[5] = "非阻塞输入值";
  145.   oledMenu.menuItems[6] = "阻塞式输入值";
  146.   oledMenu.menuItems[7] = "消息";
  147.   oledMenu.menuItems[8] = "关闭菜单";
  148. }  // demoMenu


  149. // 菜单选择的操作放在此处
  150. void menuActions() {

  151.   // 在"demo_menu"中选择项目时的操作
  152.   if (oledMenu.menuTitle == "菜单系统") {

  153.     // 演示菜单1
  154.     if (oledMenu.selectedMenuItem == 1) {
  155.       resetMenu();
  156.       menuMode = menu;
  157.       menu1();
  158.     }
  159.    
  160.     // 演示菜单2
  161.     if (oledMenu.selectedMenuItem == 2) {
  162.       menu2();
  163.     }
  164.     // 演示如何从列表中快速创建菜单
  165.     if (oledMenu.selectedMenuItem == 3) {
  166.       if (serialDebug) Serial.println("demo_menu: create menu from a list");
  167.       String tList[]={"子菜单0", "子菜单1", "子菜单2", "子菜单3", "子菜单4", "子菜单5"};
  168.       createList("快速菜单", 6, &tList[0]);
  169.     }

  170.     // 演示仅在2个选项之间进行选择
  171.     if (oledMenu.selectedMenuItem == 4) {
  172.       resetMenu();
  173.       menuMode = value;
  174.       oledMenu.menuTitle = "开或关";
  175.       oledMenu.mValueLow = 0;
  176.       oledMenu.mValueHigh = 1;
  177.       oledMenu.mValueStep = 1;
  178.       oledMenu.mValueEntered = 0;  // 设置参数
  179.     }

  180.     // 演示“数值输入”的用法(非阻塞)
  181.     if (oledMenu.selectedMenuItem == 5) {
  182.       if (serialDebug) Serial.println("demo_menu: none blocking enter value");
  183.       resetMenu();
  184.       value_non();       // 输入一个值
  185.     }

  186.     // 演示“输入值”(阻塞)的用法,该方法简单快捷,但在输入值完成之前会停止所有其他的任务
  187.     if (oledMenu.selectedMenuItem == 6) {
  188.       if (serialDebug) Serial.println("demo_menu: blocking enter a value");
  189.       // set perameters
  190.         resetMenu();
  191.         menuMode = value;
  192.         oledMenu.menuTitle = "阻塞式数值输入";
  193.         oledMenu.mValueLow = 0;
  194.         oledMenu.mValueHigh = 50;
  195.         oledMenu.mValueStep = 1;
  196.         oledMenu.mValueEntered = 5;
  197.       int tEntered = serviceValue(1);      // 需求值
  198.       Serial.println("The value entered was " + String(tEntered));
  199.       defaultMenu();
  200.     }

  201.     // 演示显示消息的用法
  202.     if (oledMenu.selectedMenuItem == 7) {
  203.       if (serialDebug) Serial.println("demo_menu: message");
  204.       displayMessage("这是个消息", "汉字显示\n是不是正常?");    // 每行21个英文字符,10个12_t的汉字"\n" = 下一行
  205.     }

  206.     // 关闭菜单/oLED息屏
  207.     else if (oledMenu.selectedMenuItem == 8) {
  208.       if (serialDebug) Serial.println("demo_menu: menu off");
  209.       resetMenu();    // 关闭菜单
  210.     }

  211.     oledMenu.selectedMenuItem = 0;                // 清除菜单项选定标志
  212.   }


  213.   // 在demo_list菜单中选择项目时的操作
  214.   if (oledMenu.menuTitle == "快速菜单") {

  215.     // 返回主菜单
  216.     if (oledMenu.selectedMenuItem == 1) {
  217.       if (serialDebug) Serial.println("demo_list: back to main menu");
  218.       defaultMenu();
  219.     }
  220.     if (oledMenu.selectedMenuItem == 2) {
  221.       if (serialDebug) Serial.println("demo_list: back to main menu2222222222222");
  222.       defaultMenu();
  223.     }
  224.     oledMenu.selectedMenuItem = 0;                // 清除菜单项选定标志
  225.   }

  226. }  // menuActions


  227. // -----------------------------------------------


  228. // 演示数值的输入
  229. void value_non() {
  230.   resetMenu();                           // 清除任何以前的菜单
  231.   menuMode = value;                      // 启用数值输入模式
  232.   oledMenu.menuTitle = "非阻塞数值输入";     // 标题(用于标识输入的编号)
  233.   oledMenu.mValueLow = 0;                // 允许的最小值
  234.   oledMenu.mValueHigh = 100;             // 允许的最大值
  235.   oledMenu.mValueStep = 1;               // 步长
  236.   oledMenu.mValueEntered = 50;           // 起始值

  237. }

  238. //--------------------------------------------------------------------------------------
  239. // 此处为数值输入的操作
  240. void menuValues() {

  241.   // "demo_value"的操作
  242.   if (oledMenu.menuTitle == "非阻塞数值输入") {
  243.     String tString = String(oledMenu.mValueEntered);
  244.     if (serialDebug) Serial.println("demo_value: The value entered was " + tString);
  245.     displayMessage("输入完成", "\n您输入的值为:\n    " + tString);
  246.     // 另外,在此处也可以使用“resetMenu()”在输入值后关闭菜单,或者使用“defaultMenu()”重新启动默认菜单
  247.   }

  248.   // “开或关”操作
  249.   if (oledMenu.menuTitle == "开或关") {
  250.     if (serialDebug) Serial.println("demo_menu: on off selection was " + String(oledMenu.mValueEntered));
  251.     defaultMenu();
  252.   }

  253. }


  254. // -------------------------------------------------------------------------------------------------
  255. //                                         以上为菜单代码
  256. // -------------------------------------------------------------------------------------------------


  257. // ----------------------------------------------------------------
  258. //                              -setup
  259. // ----------------------------------------------------------------
  260. // 从主设置调用

  261. void setup() {

  262.   Serial.begin(115200); while (!Serial); delay(50);       // 启动串行通信
  263.   Serial.println("\n\n\nStarting menu demo\n");

  264.   pinMode(iLED, OUTPUT);     // 板载led指示灯

  265.   // 配置旋转编码器的gpio引脚
  266.     pinMode(encoder0Press, INPUT_PULLUP);
  267.     pinMode(encoder0PinA, INPUT);
  268.     pinMode(encoder0PinB, INPUT);

  269.   // 初始化oled显示屏
  270.     // 启用引脚
  271.       if (OLEDE != 0) {
  272.         pinMode(OLEDE , OUTPUT);
  273.         digitalWrite(OLEDE, HIGH);
  274.       }
  275.     if (0 == OLEDC) Wire.begin();
  276.     else Wire.begin(OLEDD, OLEDC);
  277.     if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
  278.       if (serialDebug) Serial.println(("\nError initialising the oled display"));
  279.     }
  280. //---------------------------------------------------------------------------------------------   
  281.     u8g2_for_adafruit_gfx.begin(display);                 // 将u8g2程序连接到Adafruit GFX
  282.     u8g2_for_adafruit_gfx.setFont(FONT12);                // 将字体设为u8g2_font_wqy12_t_gb2312b
  283. //---------------------------------------------------------------------------------------------   
  284.   // 读取旋转编码器位置的中断
  285.     rotaryEncoder.encoder0Pos = 0;
  286.     attachInterrupt(digitalPinToInterrupt(encoder0PinA), doEncoder, CHANGE);

  287.   //defaultMenu();       // 启动默认菜单

  288.   // 显示问候语-按下按钮将进入“开始”菜单
  289.     displayMessage("欢迎使用", "汉字菜单系统\n测试版V1.0");

  290. }


  291. // ----------------------------------------------------------------
  292. //                              -loop
  293. // ----------------------------------------------------------------
  294. // 从主循环调用

  295. void loop() {

  296.   reUpdateButton();      // 更新旋转编码器按钮状态(如果按下激活默认菜单)
  297.   menuUpdate();          // 更新或操作oled菜单



  298.   // 闪烁板载led
  299.     static uint32_t ledTimer = millis();
  300.     if ( (unsigned long)(millis() - ledTimer) > 1000 ) {
  301.       digitalWrite(iLED, !digitalRead(iLED));
  302.       ledTimer = millis();
  303.     }

  304. }  // oledLoop


  305. // ----------------------------------------------------------------
  306. //                   -按钮消抖 (旋转编码器)
  307. // ----------------------------------------------------------------
  308. // 更新旋转编码器当前按钮状态

  309. void reUpdateButton() {
  310.     bool tReading = digitalRead(encoder0Press);        // 读取当前按钮状态
  311.     if (tReading != rotaryEncoder.encoderPrevButton) rotaryEncoder.reLastButtonChange = millis();     // 如果已更改重置计时器
  312.     if ( (unsigned long)(millis() - rotaryEncoder.reLastButtonChange) > rotaryEncoder.reDebounceDelay ) {  // 如果按钮状态稳定
  313.       if (rotaryEncoder.encoderPrevButton == rotaryEncoder.reButtonPressedState) {
  314.         if (rotaryEncoder.reButtonDebounced == 0) {    // 如果按钮已按下
  315.           rotaryEncoder.reButtonPressed = 1;           // 按下按钮时设置的标志
  316.           if (menuMode == off) defaultMenu();          // 如果显示屏关闭,则启动默认菜单
  317.         }
  318.         rotaryEncoder.reButtonDebounced = 1;           // 消抖后按钮状态(按下时为1)
  319.       } else {
  320.         rotaryEncoder.reButtonDebounced = 0;
  321.       }
  322.     }
  323.     rotaryEncoder.encoderPrevButton = tReading;            // 更新上次读取的状态
  324. }


  325. // ----------------------------------------------------------------
  326. //                    -更新活动菜单
  327. // ----------------------------------------------------------------

  328. void menuUpdate() {

  329.   if (menuMode == off) return;    // 如果菜单系统已关闭,不要执行其他操作

  330.   // 如果最近没有活动,则关闭oled
  331.     if ( (unsigned long)(millis() - oledMenu.lastMenuActivity) > (menuTimeout * 1000) ) {
  332.       resetMenu();
  333.       return;
  334.     }

  335.     switch (menuMode) {

  336.       // 如果有活动的菜单
  337.       case menu:
  338.         serviceMenu();
  339.         menuActions();
  340.         break;

  341.       // 如果有活动且非阻塞的“数值输入”
  342.       case value:
  343.         serviceValue(0);
  344.         if (rotaryEncoder.reButtonPressed) {                        // 如果按钮已按下
  345.           menuValues();                                             // 已输入一个值,对其进行操作
  346.           break;
  347.         }

  348.       // 如果正在显示消息
  349.       case message:
  350.         if (rotaryEncoder.reButtonPressed == 1) defaultMenu();    // 如果按下按钮,返回默认菜单
  351.         break;
  352.     }
  353. }


  354. // ----------------------------------------------------------------
  355. //                       -活动菜单处理
  356. // ----------------------------------------------------------------

  357. void serviceMenu() {

  358.     // 旋转编码器
  359.       if (rotaryEncoder.encoder0Pos >= itemTrigger) {
  360.         rotaryEncoder.encoder0Pos -= itemTrigger;
  361.         oledMenu.highlightedMenuItem++;
  362.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  363.       }
  364.       if (rotaryEncoder.encoder0Pos <= -itemTrigger) {
  365.         rotaryEncoder.encoder0Pos += itemTrigger;
  366.         oledMenu.highlightedMenuItem--;
  367.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  368.       }
  369.       if (rotaryEncoder.reButtonPressed == 1) {
  370.         oledMenu.selectedMenuItem = oledMenu.highlightedMenuItem;     // 已选择项目的标志
  371.         oledMenu.lastMenuActivity = millis();   // log time
  372.         if (serialDebug) Serial.println("menu '" + oledMenu.menuTitle + "' item '" + oledMenu.menuItems[oledMenu.highlightedMenuItem] + "' selected");
  373.       }

  374.     const int _centreLine = displayMaxLines / 2 -1;    // 列表中间位置
  375.     display.clearDisplay();
  376.     display.setTextColor(WHITE);

  377.     // 验证有效的突出显示项
  378.       if (oledMenu.highlightedMenuItem > oledMenu.noOfmenuItems) oledMenu.highlightedMenuItem = oledMenu.noOfmenuItems;
  379.       if (oledMenu.highlightedMenuItem < 1) oledMenu.highlightedMenuItem = 1;

  380.     // 标题
  381.       //display.setCursor(0, 0);
  382.       u8g2_for_adafruit_gfx.setCursor(0,14);
  383.       u8g2_for_adafruit_gfx.setFont(FONT14);
  384.       /*if (menuLargeText) {
  385.         display.setTextSize(2);
  386.         display.println(oledMenu.menuItems[oledMenu.highlightedMenuItem].substring(0, MaxmenuTitleLength));
  387.       } else {
  388.         if (oledMenu.menuTitle.length() > MaxmenuTitleLength) display.setTextSize(1);
  389.         else display.setTextSize(2);
  390.         display.println(oledMenu.menuTitle);
  391.       }*/
  392.       u8g2_for_adafruit_gfx.println(oledMenu.menuTitle);
  393.       display.drawLine(0, topLine-1, display.width(), topLine-1, WHITE);       // 在标题下画水平线

  394.     // 菜单
  395. //     display.setTextSize(1);
  396. //     display.setCursor(0, topLine);
  397.         u8g2_for_adafruit_gfx.setFont(FONT12);
  398.         u8g2_for_adafruit_gfx.setCursor(0,topLine+12);
  399.       /*for (int i=1; i <= displayMaxLines; i++) {
  400.         int item = oledMenu.highlightedMenuItem - _centreLine + i;
  401.         if (item == oledMenu.highlightedMenuItem) display.setTextColor(BLACK, WHITE);
  402.         else display.setTextColor(WHITE);
  403.         if (item > 0 && item <= oledMenu.noOfmenuItems) display.println(oledMenu.menuItems[item]);
  404.         else display.println(" ");*/
  405.         for (int i=1; i <= displayMaxLines; i++) {
  406.         int item = oledMenu.highlightedMenuItem - _centreLine + i;
  407.         if (item == oledMenu.highlightedMenuItem)
  408.            {
  409.             u8g2_for_adafruit_gfx.setFontMode(0);
  410.             u8g2_for_adafruit_gfx.setForegroundColor(BLACK);
  411.             u8g2_for_adafruit_gfx.setBackgroundColor(WHITE);
  412.            }
  413.         else {
  414.               u8g2_for_adafruit_gfx.setFontMode(0);
  415.               u8g2_for_adafruit_gfx.setForegroundColor(WHITE);
  416.               u8g2_for_adafruit_gfx.setBackgroundColor(BLACK);
  417.         }
  418.         if (item > 0 && item <= oledMenu.noOfmenuItems) {
  419.           u8g2_for_adafruit_gfx.println(oledMenu.menuItems[item]);
  420.          
  421.         }
  422.         else display.println(" ");
  423.       
  424.       }

  425.     //// 如何在菜单屏幕上显示一些更新信息
  426.     // display.setCursor(80, 25);
  427.     // display.println(millis());

  428.     display.display();
  429. }


  430. // ----------------------------------------------------------------
  431. //                        -数值输入处理
  432. // ----------------------------------------------------------------
  433. // 如果阻塞设置为1,则所有其他任务都将停止,直到输入数值为止
  434. int serviceValue(bool _blocking) {

  435.   const int _valueSpacingX = 30;      // 显示的数值X位置的间距
  436.   const int _valueSpacingY = 5;       // 显示的数值Y位置的间距

  437.   if (_blocking) {
  438.     menuMode = blocking;
  439.     oledMenu.lastMenuActivity = millis();   // 上次活动的日志记录时间(超时)
  440.   }
  441.   uint32_t tTime;

  442.   do {

  443.     // 旋转编码器
  444.       if (rotaryEncoder.encoder0Pos >= itemTrigger) {
  445.         rotaryEncoder.encoder0Pos -= itemTrigger;
  446.         oledMenu.mValueEntered-= oledMenu.mValueStep;
  447.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  448.       }
  449.       if (rotaryEncoder.encoder0Pos <= -itemTrigger) {
  450.         rotaryEncoder.encoder0Pos += itemTrigger;
  451.         oledMenu.mValueEntered+= oledMenu.mValueStep;
  452.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  453.       }
  454.       if (oledMenu.mValueEntered < oledMenu.mValueLow) {
  455.         oledMenu.mValueEntered = oledMenu.mValueLow;
  456.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  457.       }
  458.       if (oledMenu.mValueEntered > oledMenu.mValueHigh) {
  459.         oledMenu.mValueEntered = oledMenu.mValueHigh;
  460.         oledMenu.lastMenuActivity = millis();   // 日志记录时间
  461.       }

  462.       display.clearDisplay();
  463.       display.setTextColor(WHITE);

  464.       // 标题
  465.         //display.setCursor(0, 0);
  466.         u8g2_for_adafruit_gfx.setCursor(0,14);
  467. //        if (oledMenu.menuTitle.length() > MaxmenuTitleLength) display.setTextSize(1);
  468. //        else display.setTextSize(2);
  469.           u8g2_for_adafruit_gfx.setFont(FONT14);
  470.           u8g2_for_adafruit_gfx.print(oledMenu.menuTitle);
  471. //        display.println(oledMenu.menuTitle);
  472.         display.drawLine(0, topLine-1, display.width(), topLine-1, WHITE);       // 在标题下画水平线

  473.       // 选择的数值
  474.         display.setCursor(_valueSpacingX, topLine + _valueSpacingY);
  475.         display.setTextSize(3);
  476.         display.println(oledMenu.mValueEntered);
  477.         
  478.          
  479.       // 范围
  480.         display.setCursor(0, display.height() - lineSpace1 - 1 );   // 显示区域底部
  481.         display.setTextSize(1);
  482.         display.println(String(oledMenu.mValueLow) + " to " + String(oledMenu.mValueHigh));

  483.       // 状态条
  484.         int Tlinelength = map(oledMenu.mValueEntered, oledMenu.mValueLow, oledMenu.mValueHigh, 0 , display.width());
  485.         display.drawLine(0, display.height()-1, Tlinelength, display.height()-1, WHITE);

  486.       display.display();

  487.       reUpdateButton();        // 检查按钮状态
  488.       tTime = (unsigned long)(millis() - oledMenu.lastMenuActivity);      // 自上次活动以来的时间

  489.   } while (_blocking && rotaryEncoder.reButtonPressed == 0 && tTime < (menuTimeout * 1000));        // 如果处于阻塞模式,重复此操作,直到按下按钮或超时

  490.   if (_blocking) menuMode = off;

  491.   return oledMenu.mValueEntered;        // 在阻塞模式下使用

  492. }


  493. // ----------------------------------------------------------------
  494. //                           -列表的创建
  495. // ----------------------------------------------------------------
  496. // 更新以前的读数从列表中创建菜单
  497. // 例如:      String tList[]={"main menu", "2", "3", "4", "5", "6"};
  498. //            createList("demo_list", 6, &tList[0]);

  499. void createList(String _title, int _noOfElements, String *_list) {
  500.   resetMenu();                      // 清除任何以前的菜单
  501.   menuMode = menu;                  // 启用菜单模式
  502.   oledMenu.noOfmenuItems = _noOfElements;    // 设置此菜单中的项目数
  503.   oledMenu.menuTitle = _title;               // 菜单标题(用于标识)

  504.   for (int i=1; i <= _noOfElements; i++) {
  505.     oledMenu.menuItems[i] = _list[i-1];        // 设置菜单项
  506.   }
  507. }


  508. // ----------------------------------------------------------------
  509. //                         -消息显示
  510. // ----------------------------------------------------------------
  511. // 每行21个字符,"\n" = 下一行
  512. // 例如:  <     行1        ><     行2        ><     行3        ><     行4         >

  513. void displayMessage(String _title, String _message) {
  514.   resetMenu();
  515.   menuMode = message;

  516.   display.clearDisplay();
  517.   //display.setTextColor(WHITE);
  518.   u8g2_for_adafruit_gfx.setFontMode(1);                 // 使用u8g2透明模式 (缺省)
  519.   u8g2_for_adafruit_gfx.setFontDirection(0);            // 文字方向从左到右 (缺省)
  520.   
  521.   u8g2_for_adafruit_gfx.setForegroundColor(WHITE);
  522.   // 标题
  523.     //display.setCursor(0, 0);
  524.     u8g2_for_adafruit_gfx.setCursor(0,14);
  525.     if (menuLargeText) {
  526.       //display.setTextSize(2);
  527.       u8g2_for_adafruit_gfx.setFont(FONT14);
  528.       //display.println(_title.substring(0, MaxmenuTitleLength));
  529.       u8g2_for_adafruit_gfx.println(_title.substring(0, MaxmenuTitleLength));
  530.     } else {
  531.       if (_title.length() > MaxmenuTitleLength)
  532.           //display.setTextSize(1);
  533.           u8g2_for_adafruit_gfx.setFont(FONT12);
  534.       else
  535.           //display.setTextSize(2);
  536.           u8g2_for_adafruit_gfx.setFont(FONT14);
  537.       //display.println(_title);
  538.       u8g2_for_adafruit_gfx.println(_title);
  539.     }
  540.     display.drawLine(0, topLine-1, display.width(), topLine-1, WHITE);       // 在标题下画水平线
  541.   // 消息
  542.     //display.setCursor(0, topLine);
  543.     u8g2_for_adafruit_gfx.setCursor(0,topLine+12);
  544.     //display.setTextSize(1);
  545.     u8g2_for_adafruit_gfx.setFont(FONT12);
  546.     //display.println(_message);
  547.     u8g2_for_adafruit_gfx.println(_message);
  548.     display.display();

  549. }


  550. // ----------------------------------------------------------------
  551. //                        -重置菜单系统
  552. // ----------------------------------------------------------------

  553. void resetMenu() {
  554.   // 重置所有菜单变量/标志
  555.     menuMode = off;
  556.     oledMenu.selectedMenuItem = 0;
  557.     rotaryEncoder.encoder0Pos = 0;
  558.     oledMenu.noOfmenuItems = 0;
  559.     oledMenu.menuTitle = "";
  560.     oledMenu.highlightedMenuItem = 0;
  561.     oledMenu.mValueEntered = 0;
  562.     rotaryEncoder.reButtonPressed = 0;

  563.   oledMenu.lastMenuActivity = millis();   // 日志记录时间

  564.   // 清除oled显示屏
  565.     display.clearDisplay();
  566.     display.display();
  567. }


  568. // ----------------------------------------------------------------
  569. //                     -旋转编码器中断
  570. // ----------------------------------------------------------------
  571. // 旋转编码器中断程序,用于在旋转时更新位置计数器
  572. //     中断参考资料: https://www.gammon.com.au/forum/bbshowpost.php?id=11488

  573. void ICACHE_RAM_ATTR doEncoder() {

  574.   bool pinA = digitalRead(encoder0PinA);
  575.   bool pinB = digitalRead(encoder0PinB);

  576.   if ( (rotaryEncoder.encoderPrevA == pinA && rotaryEncoder.encoderPrevB == pinB) ) return;  // 自上次以来没有变化

  577.   // 同一方向(在一个方向上介于0,1和1,0之间,或在另一个方向上介于1,1和0,0之间)
  578.          if (rotaryEncoder.encoderPrevA == 1 && rotaryEncoder.encoderPrevB == 0 && pinA == 0 && pinB == 1) rotaryEncoder.encoder0Pos -= 1;
  579.     else if (rotaryEncoder.encoderPrevA == 0 && rotaryEncoder.encoderPrevB == 1 && pinA == 1 && pinB == 0) rotaryEncoder.encoder0Pos -= 1;
  580.     else if (rotaryEncoder.encoderPrevA == 0 && rotaryEncoder.encoderPrevB == 0 && pinA == 1 && pinB == 1) rotaryEncoder.encoder0Pos += 1;
  581.     else if (rotaryEncoder.encoderPrevA == 1 && rotaryEncoder.encoderPrevB == 1 && pinA == 0 && pinB == 0) rotaryEncoder.encoder0Pos += 1;

  582.   // 改变方向
  583.     else if (rotaryEncoder.encoderPrevA == 1 && rotaryEncoder.encoderPrevB == 0 && pinA == 0 && pinB == 0) rotaryEncoder.encoder0Pos += 1;
  584.     else if (rotaryEncoder.encoderPrevA == 0 && rotaryEncoder.encoderPrevB == 1 && pinA == 1 && pinB == 1) rotaryEncoder.encoder0Pos += 1;
  585.     else if (rotaryEncoder.encoderPrevA == 0 && rotaryEncoder.encoderPrevB == 0 && pinA == 1 && pinB == 0) rotaryEncoder.encoder0Pos -= 1;
  586.     else if (rotaryEncoder.encoderPrevA == 1 && rotaryEncoder.encoderPrevB == 1 && pinA == 0 && pinB == 1) rotaryEncoder.encoder0Pos -= 1;

  587.   //else if (serialDebug) Serial.println("Error: invalid rotary encoder pin state - prev=" + String(rotaryEncoder.encoderPrevA) + ","
  588.   //                                      + String(rotaryEncoder.encoderPrevB) + " new=" + String(pinA) + "," + String(pinB));

  589.   // 更新以前的读数
  590.     rotaryEncoder.encoderPrevA = pinA;
  591.     rotaryEncoder.encoderPrevB = pinB;
  592. }


  593. // ---------------------------------------------- 结束 ----------------------------------------------
复制代码
原版代码: BasicOLEDMenu-main.zip (901.85 KB, 下载次数: 7)




发表于 2022-6-27 12:42 | 显示全部楼层
U8g2 的字不全,我日
 楼主| 发表于 2022-6-27 21:24 | 显示全部楼层
亲测,比较全的,至少我这里用到的字都有。我用的u8g2的版本是2.32.15,以前的库确实不全。U8g2_for_Adafruit_GFX的版本是1.8.0。
 楼主| 发表于 2022-6-27 21:31 | 显示全部楼层
屏幕截图 2022-06-27 212925.png
发表于 2022-9-15 07:06 | 显示全部楼层
收藏慢慢读
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-1 05:51 , Processed in 0.157731 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表