自制的Arduino多级菜单类

博主因网上难寻合适的OLED菜单开发库,自行编写了一个菜单类库。该库功能丰富,有无限制菜单项目数量、极简捷菜单项目定义等特点,但也存在一些不足,如运行效率不如C语言、代码量稍多、运行内存占用稍多等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为要做一个小应用,里面有些参数需要在运行时设置,需要用户在OLED屏上做一些简单的设定,我本想在网上找一些支持菜单开发的库,但找了很久都找不到,在论坛里也只是一些十分简单的例子,全部都是代码写死了菜单,把菜单的显示逻辑、按键响应、菜单的显示样式、菜单的内容完成写成固定的了,我觉得实在有点难看,也不利于之后的再次利用,因此,花了几天的时间写了一个菜单类库。这个库有以下的特点(好象有点自吹自擂了):实现的功能
1.无限制菜单项目、参数项目数量,无限制级联菜单。
2.极简捷菜单项目定义;
3.通过旋转编码器可快速选取菜单项目(可使用两个按键代替旋转编码器);
4.通过旋转编码器可快速调整参数项目值(同上);
5.参数项目的值可以设定为 整数 或 候选列表(返回值也是整数);
6.采用回调函数的方式(类似C#的委托)实现客户化显示,可方便调整成自己需要的显示样式;
7.支持运行期间设定及读取参数项目的值。
8.支持调整菜单时自动触发对应事件,事件与参数项目对应绑定。
9.理论上可支持多种字体,理化上支持中文,但未测试;
10.与u8g2库只是轻度耦合,稍作调整即可切换成其它第三方显示库。

待改进
1.采用C++编写,好像运行效率并无C语言的高。
2.代码量稍多,9KB。
3.运行内存占用稍多。若要同时实现多种功能,建议使用大内存的Arduino板子。

例如,要构建以下的菜单:


Main Menu(主菜单,名字可改)
-menu1 Value
  -menu11
   -value111 (参数,整型)
-menu2 Cities
  -China Cities(参数,列表参数,候选列表内容为 psCitiesChina 。)
  -USA Cities(参数,列表参数,候选列表内容为 psCitiesUSA 。)
-menu3 Animals
  -Animal Type (参数,列表参数,候选列表内容为 psAnimals 。)
-MENU4
  -MENU41
   -MENU411
    -MENU4111
     -MENU41111
-MENU5
-MENU6
-MENU7
-MENU8
-MENU9
-MENU0
-value00(参数,整型)
 

使用起来的代码如下:

#include "flexmenu.h"
#include "flexmisc.h"
#include <Arduino.h>
#include <U8g2lib.h>
 
U8G2_SSD1306_128X64_NONAME_1_4W_SW_SPI u8g2(U8G2_R0, /*DO clock=*/ 13, /*DI data=*/ 11, /*CS cs=*/ 10, /*DC dc=*/ 9, /* RES reset=*/ 8);
 
TFlexMenu FlexMenu; //我自己写的菜单类 
TFlexRotaryEncoder re(2, 3, 4); //我自己写的旋转编码器类,可参看这个贴子:https://www.arduino.cn/thread-86535-1-1.html
 
char *psCitiesChina[6] ={"Beijing", "Shanghai", "Guangzhou", "Shenzhen","Tianjin", "Hangzhou"};
char *psCitiesUSA[4] ={"NewYork", "Manhattan", "Washington", "Seattle"};
char *psAnimals[5] ={"dog", "cat", "mouse", "pig", "cow"};
 
void reRotateEvent(int direct)
{
    if (direct == -1) FlexMenu.clickUp();
    if (direct == 1)  FlexMenu.clickDown();
}
 
void Int0Event()
{
  re.checkState();
}
 
void fmOnDisplayTiltle(char *content, int ttype) //显示标题
{
    u8g2.setFont(u8g2_font_unifont_t_chinese1);
    u8g2.drawUTF8(0, 15, content);
    u8g2.drawLine(0, 16, 128, 16);
}
 
void fmOnDisplayContent(int idx, char *content, int ctype) //显示菜单内容
{
    if (ctype == 1)  // 需显示的是:一般菜单项
    {
        u8g2.drawUTF8(5, (idx + 1) * 16,content);
    }
    else if (ctype == 2) //需显示的是:光标指示的一般菜单项
    {
        u8g2.drawUTF8(5, (idx + 1) * 16,content);
        u8g2.drawTriangle(0, 8 + idx * 16, 0, 16 + idx * 16, 4, 12+ idx * 16);
    }
    else if (ctype == 3) //需显示的是:待调整的值
    {
       int ll = strlen(content) * 8;
       int sp = (128 - ll) / 2;
       u8g2.drawUTF8(sp, 3 * 16, content);
    }
}
 
void fmOnAdjustValueEvent(int id, int value, int upDown)
{
   String s = "fmOnAdjustValueEvent: id: " + id ;
   s = s + " value: " + value;
   s = s + " UpDown: " + upDown;
   //Serial.println(s);  
}
 
void fmOnOKValueEvent(int id, int value)
{
 // Serial.println("fmOnOKValueEvent: id:%d, value: %d ", id, value);  
}
 
void initMenu()
{
    TFlexMenuItem *mi1, *mi2; // 创建两个临时变量,用于创建层级菜单。
 
 
    //在根菜单下增加一个菜单,名为“menu1 Value”,NULL指的是没有父菜单项目,addMenuItem返回的是增加后该菜单项目的句柄(地址)
    mi1 = FlexMenu.addMenuItem("menu1 Value", NULL);
 
    //在“menu1 Value“菜单下增加一个菜单,名为“menu11”,mi1 传入的是menu1 Value的句柄
    mi2 = FlexMenu.addMenuItem("menu11", mi1);
 
    //增加一个整数型参数,这个参数的名字叫“value111”,id是 1(用于在运行时设置及读取),最大值是 200, 最小值是3,默认值是 100,
    FlexMenu.addMenuValue(1,"value111", mi2, 100, 200, 3);
    
    mi1 = FlexMenu.addMenuItem("menu2 Cities", NULL);
 
 
    //增加一个列表型参数,这个参数的名字叫“China Cities”,id是 2(用于在运行时设置及读取),候选的列表是psCitiesChina,默认是选第1个, 共6个候选值
    FlexMenu.addMenuValueList(2,"China Cities", mi1, 0, 6, psCitiesChina);
 
    //增加一个列表型参数,这个参数的名字叫“USA Cities”,id是 3(用于在运行时设置及读取),候选的列表是psCitiesUSA,默认是选第1个, 共4个候选值
    FlexMenu.addMenuValueList(3,"USA Cities", mi1, 0, 4, psCitiesUSA);
    
    mi1 = FlexMenu.addMenuItem("menu3 Animals", NULL);
    FlexMenu.addMenuValueList(4,"Animal Type", mi1, 0, 5, psAnimals);
    
    mi1 = FlexMenu.addMenuItem("MENU4", NULL);
    mi1 = FlexMenu.addMenuItem("MENU41", mi1);
    mi1 = FlexMenu.addMenuItem("MENU411", mi1);
    mi1 = FlexMenu.addMenuItem("MENU4111", mi1);
    mi1 = FlexMenu.addMenuItem("MENU41111", mi1);
    
    mi1 = FlexMenu.addMenuItem("menu5", NULL);
    mi1 = FlexMenu.addMenuItem("menu6", NULL);
    mi1 = FlexMenu.addMenuItem("menu7", NULL);
    mi1 = FlexMenu.addMenuItem("menu8", NULL);
    mi1 = FlexMenu.addMenuItem("menu9", NULL);
    mi1 = FlexMenu.addMenuItem("menu0", NULL);
    
    FlexMenu.addMenuValue(100,"value00", NULL, 9, 1000, 3);
    
    FlexMenu.init();
}
 
void setup()
{
    //Serial.begin (9600);
 
    FlexMenu.pU8G2 = &u8g2;
    FlexMenu.pageRow = 3;
    FlexMenu.attachDisplayTitle(fmOnDisplayTiltle);
    FlexMenu.attachDisplayContent(fmOnDisplayContent);
    FlexMenu.attachAdjustValueEvent(fmOnAdjustValueEvent);
    FlexMenu.attachOKValueEvent(fmOnOKValueEvent);
    
    initMenu();
    
    re.attachRotateEvent(reRotateEvent);
    re.setClickResetCount(true);
 
    u8g2.begin();
    attachInterrupt(0, Int0Event, CHANGE);
}
 
void loop()
{
    FlexMenu.display();
    
    if (re.isClick()){ FlexMenu.clickOK();};
}

 

 

<think>好的,用户想了解如何使用ESP8266和Arduino制作天气站。首先,我需要整理现有的引用资料,看看有哪些相关项目可以参考。引用1提到ESP8266和STM32获取天气时间信息,可能涉及网络请求和数据解析。引用2是彩色天气站项目,使用ILI9341显示屏,可能提供界面设计的思路。引用3提到了Blinker库和U8G2驱动LCD屏,这可能适用于显示部分。引用4讨论了断网处理和自动同步功能,这对提高稳定性有帮助。 接下来,我需要确定用户可能需要的步骤。通常,制作天气站需要硬件连接、网络配置、API获取、数据处理和显示。根据引用中的信息,ESP8266的驱动函数和网络请求是关键。用户可能需要使用Arduino IDE进行开发,所以需要安装相关库,比如ESP8266WiFi、ArduinoJson、U8g2等。 然后,考虑用户可能的痛点,比如如何获取天气API、处理JSON数据、断网后的处理。引用4提到自动同步和断网后展示最新数据,这部分需要详细说明。此外,代码示例可能需要简化,确保用户容易理解。 还需要检查引用中的项目链接,确保提供的资源有效。比如引用2的项目地址是否可用,可能需要测试。同时,用户可能对硬件连接有疑问,比如ESP8266与显示屏的引脚连接,引用3和4提供了不同的示例,需要统一并给出常见配置。 最后,生成相关问题时要覆盖硬件选择、API获取、显示优化、低功耗和扩展功能,帮助用户深入探索。确保回答结构清晰,分步骤指导,并引用相关资料作为支持。</think>以下是使用ESP8266和Arduino制作天气站的详细教程: ### 一、硬件准备 1. **核心部件**:ESP8266开发板(NodeMCU/ESP-01等) 2. **显示模块**:0.96寸OLED(SSD1306)或TFT彩屏(ILI9341)[^3][^4] 3. **连接线**:杜邦线若干 4. **电源**:USB数据线或5V电源适配器 ### 二、软件环境搭建 1. 安装Arduino IDE(1.8.x以上版本) 2. 添加开发板支持: ```arduino 文件 -> 首选项 -> 附加开发板管理器URL添加: http://arduino.esp8266.com/stable/package_esp8266com_index.json ``` 3. 安装库: - ESP8266WiFi - ArduinoJson(v6.x) - U8g2(OLED显示)或TFT_eSPI(彩屏显示) ### 三、关键代码实现 ```arduino #include <ESP8266WiFi.h> #include <ArduinoJson.h> const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; const String API_KEY = "your_api_key"; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // 连接成功后获取天气数据 getWeatherData(); } void getWeatherData() { WiFiClient client; const String url = "http://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=" + API_KEY; if (client.connect("api.openweathermap.org", 80)) { client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: api.openweathermap.org\r\n" + "Connection: close\r\n\r\n"); while(client.connected()){ String line = client.readStringUntil('\n'); if (line == "\r") break; } // 解析JSON数据 DynamicJsonDocument doc(1024); deserializeJson(doc, client); float temp = doc["main"]["temp"] - 273.15; // 转换开尔文为摄氏度 Serial.println("当前温度:" + String(temp) + "℃"); } client.stop(); } ``` ### 四、功能优化建议 1. **断网处理**:添加RTC模块或软件计时器实现离线时钟 2. **多天气显示**:使用心知天气等支持多日预报的API 3. **低功耗模式**:通过`ESP.deepSleep()`实现定时唤醒[^2] 4. **界面美化**:使用U8g2库实现多级菜单切换[^3] ### 五、常见问题解决 1. **API限制**:免费天气API通常有调用次数限制(OpenWeatherMap 60次/分钟) 2. **内存不足**:使用`PROGMEM`存储静态数据,优化JSON解析缓存 3. **显示闪烁**:采用双缓冲机制,先绘制到内存再刷新屏幕
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值