<think>我们需要在现有菜单系统中添加一个设置RTC时间的子菜单。由于屏幕尺寸为256x128,我们可以将时间显示为一行,通过左右切换要修改的字段(年、月、日、时、分),上下调整数值。
步骤:
1. 在系统设置菜单中添加一个“设置时间”选项。
2. 当选择“设置时间”时,进入时间设置界面,显示当前时间(格式:2023-10-25 14:30),并有一个指示器(如下划线或三角形)指示当前选中的字段。
3. 使用左右键切换选中的字段(年、月、日、时、分),使用上下键调整当前字段的值。
4. 确认键保存设置,返回键取消。
具体修改如下:
1. 在Menu.h中:
- 添加新的菜单页面枚举值:MENU_RTC_SETTINGS
- 添加用于时间编辑的成员变量(当前编辑的时间、当前选中的字段索引等)
2. 在Menu.cpp中:
- 在系统设置菜单中增加一项“设置时间”
- 实现时间设置页面的显示(showRTCTime函数)
- 在handleKey函数中处理时间设置页面的按键(上下左右和确认返回)
- 在acknowledge函数中处理确认操作(进入编辑、保存等)
- 在showLast函数中处理返回操作
考虑到现有代码结构,我们做以下修改:
在Menu.h中:
```cpp
class Menu {
// ... 其他代码 ...
enum MenuPage {
MENU_CLOSED,
MENU_SETTINGS,
MENU_ABOUT,
MENU_Buzzer,
MENU_Bzz,
MENU_LCDA,
MENU_RTC_SETTINGS // 新增的时间设置菜单
};
// ... 其他代码 ...
private:
// ... 其他成员变量 ...
// 用于RTC时间设置
int8_t timeEditField; // 当前编辑的时间字段:0=年,1=月,2=日,3=时,4=分
DateTime editTime; // 正在编辑的时间
bool isEditingTime; // 是否处于编辑模式(用于时间设置)
// ... 其他函数 ...
void showRTCTime(); // 显示RTC时间设置界面
};
```
在Menu.cpp中:
1. 修改系统设置菜单项,增加“设置时间”选项:
```cpp
void Menu::showSettings(int16_t page) {
// ... 已有代码 ...
switch (page) {
case -1: {
items[0] = "蜂鸣器开关";
items[1] = "震动开关";
items[2] = "背光灯开关";
items[3] = "设置时间"; // 新增
items[4] = "关于"; // 将原来的关于移后
for (int i = 0, c = 38; i < 5; ++i, c += 18) {
display->drawUTF8(0, c, items[i].c_str());
}
break;
}
// ... 其他页面 ...
}
}
```
2. 在acknowledge函数中,当选择“设置时间”时进入时间设置菜单:
```cpp
void Menu::acknowledge() {
// ... 已有代码 ...
case MENU_SETTINGS:
if (sub_page == -1) {
switch (selected_item) {
// ... 其他case ...
case 3: // 设置时间
menu_page = MENU_RTC_SETTINGS;
// 初始化编辑时间为当前RTC时间
editTime = rtc.now();
timeEditField = 0; // 从年开始
isEditingTime = false; // 初始为选择字段模式
showRTCTime();
break;
case 4: // 关于
menu_page = MENU_ABOUT;
sub_page = 1;
showAbout(1);
break;
}
}
break;
// ... 其他菜单页的处理 ...
case MENU_RTC_SETTINGS:
// 在时间设置页面,确认键用于切换编辑模式或保存
if (!isEditingTime) {
// 第一次按确认:进入编辑模式(当前字段开始编辑)
isEditingTime = true;
} else {
// 再次按确认:退出编辑模式(保存当前字段并回到选择字段模式)
isEditingTime = false;
// 保存到RTC
rtc.adjust(editTime);
// 提示保存成功
showMessage("提示", "时间已保存!");
}
showRTCTime();
break;
}
```
3. 实现showRTCTime函数,显示时间设置界面:
```cpp
void Menu::showRTCTime() {
clearAll();
display->setFont(u8g2_font_wqy16_t_gb2312);
display->drawUTF8(96, 16, "设置时间");
display->drawHLine(0, 20, 256);
// 显示时间:年-月-日 时:分
char timeStr[32];
snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d",
editTime.year(), editTime.month(), editTime.day(),
editTime.hour(), editTime.minute());
// 使用大一点字体显示时间
display->setFont(u8g2_font_10x20_tf);
display->drawUTF8(20, 60, timeStr);
// 显示字段指示器(在对应字段下方显示三角形)
// 字段位置:年(0-3)在x=20, 月(5-7)在x=65, 日(8-10)在x=100, 时(12-13)在x=135, 分(15-16)在x=170
int16_t fieldPos[5] = {20, 65, 100, 135, 170};
// 画一个三角形在选中字段下方
int16_t x = fieldPos[timeEditField];
display->drawTriangle(x, 70, x-5, 80, x+5, 80);
// 显示操作提示
display->setFont(u8g2_font_wqy12_t_gb2312);
if (isEditingTime) {
display->drawUTF8(40, 100, "上下:调整值 确认:保存");
} else {
display->drawUTF8(40, 100, "左右:切换字段 确认:编辑");
}
display->drawUTF8(70, 120, "返回:取消");
display->sendBuffer();
}
```
4. 修改handleKey函数,支持在时间设置页面的按键操作:
```cpp
void Menu::handleKey(bool up) {
// ... 已有代码 ...
case MENU_RTC_SETTINGS:
if (isEditingTime) {
// 编辑模式:上下键修改当前字段的值
switch (timeEditField) {
case 0: // 年
editTime = DateTime(editTime.year() + (up?1:-1),
editTime.month(), editTime.day(),
editTime.hour(), editTime.minute());
break;
case 1: // 月
editTime = DateTime(editTime.year(),
(editTime.month() + (up?1:12-1)) % 12 + 1, // 1-12
editTime.day(),
editTime.hour(), editTime.minute());
break;
case 2: // 日
// 注意:这里简化处理,实际应根据月份调整最大天数,但DateTime会自动调整
editTime = DateTime(editTime.year(), editTime.month(),
editTime.day() + (up?1:-1),
editTime.hour(), editTime.minute());
break;
case 3: // 时
editTime = DateTime(editTime.year(), editTime.month(), editTime.day(),
(editTime.hour() + (up?1:24-1)) % 24,
editTime.minute());
break;
case 4: // 分
editTime = DateTime(editTime.year(), editTime.month(), editTime.day(),
editTime.hour(),
(editTime.minute() + (up?1:60-1)) % 60);
break;
}
} else {
// 选择字段模式:左右键切换字段(这里用上下键切换,但原函数是上下键,所以用上下键切换字段)
if (up) {
timeEditField = (timeEditField - 1 + 5) % 5;
} else {
timeEditField = (timeEditField + 1) % 5;
}
}
showRTCTime();
break;
}
```
5. 修改showLast函数,支持从时间设置菜单返回:
```cpp
void Menu::showLast() {
if (is_msg) {
is_msg = false;
// ... 消息处理 ...
}
switch (menu_page) {
// ... 其他菜单的返回 ...
case MENU_RTC_SETTINGS:
if (isEditingTime) {
// 如果正在编辑,则退出编辑模式(不保存当前字段的修改)
isEditingTime = false;
// 重新加载当前RTC时间(放弃修改)
editTime = rtc.now();
showRTCTime();
} else {
// 返回系统设置菜单
menu_page = MENU_SETTINGS;
sub_page = -1;
selected_item = 3; // 定位到“设置时间”项
showSettings(sub_page);
highlightItem(selected_item);
}
break;
}
}
```
注意:以上代码中,我们假设已经有一个RTC对象(rtc)在Menu类中,并且在初始化时已经设置好。
由于原代码中系统设置菜单项只有4个,我们增加到了5个,因此需要调整菜单滚动的逻辑。在handleKey的MENU_SETTINGS部分,需要将selected_item的范围从0-3改为0-4,并修改翻页逻辑(原代码有翻页,但我们这里只有一页,所以可以去掉翻页,改为循环滚动)。
修改handleKey中的MENU_SETTINGS部分:
```cpp
case MENU_SETTINGS:
if (up)
selected_item--;
else
selected_item++;
// 改为0-4循环
if (selected_item < 0)
selected_item = 4;
if (selected_item > 4)
selected_item = 0;
showSettings(sub_page);
highlightItem(selected_item);
break;
```
同时,在showSettings函数中,我们绘制了5个菜单项(i从0到4),所以循环条件改为i<5。
这样,我们就添加了设置RTC时间的功能。
注意:由于原代码中使用了sub_page,我们在时间设置菜单中没有使用sub_page,所以将其设为-1(或0)均可。另外,在时间设置菜单中,我们使用上下键进行字段切换和数值修改,左右键也可以考虑加入(但原函数handleKey只有上下键参数,所以这里用上下键切换字段,上下键修改数值)。如果需要左右键,需要修改handleKey函数参数(增加左右键)或使用其他按键组合。
由于原handleKey函数只有上下键(一个参数表示上下),所以这里我们使用上下键同时负责切换字段和修改数值,通过isEditingTime来区分两种模式。
如果硬件支持左右键,建议修改handleKey函数,增加一个参数(如bool left)来表示左右键。但根据现有代码,我们只使用上下键,所以按上述方式实现。
最后,如果屏幕尺寸允许,可以调整时间显示的字体和位置,使其更美观。
以上代码仅供参考,可能需要根据实际情况调整。</think>根据你的需求,我将为你的菜单系统添加RTC时间设置功能。以下是完整的修改方案和代码实现:
### 修改方案:
1. 在系统设置菜单中添加"设置时间"选项
2. 创建时间设置页面(横排显示时间字段)
3. 实现时间字段选择和修改逻辑
4. 添加时间修改后的保存功能
### 修改后的完整代码:
```cpp
// 在Menu.h中添加以下内容
#include <RTClib.h> // 包含RTC库
class Menu {
// ... 已有成员 ...
// 添加以下成员变量
RTC_DS3231 rtc; // DS3231 RTC对象
uint8_t timeEditState; // 0-年,1-月,2-日,3-时,4-分
DateTime editTime; // 正在编辑的时间
bool isEditingTime; // 是否处于编辑模式
// 添加成员函数声明
void showRTCTime();
void saveRTCTime();
};
```
```cpp
// 在Menu.cpp中添加以下实现
// 初始化RTC(在构造函数或初始化函数中)
Menu::Menu() : /* 其他初始化... */ {
// 初始化RTC
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
// 这里可以添加错误处理
}
if (!rtc.isrunning()) {
Serial.println("RTC is NOT running!");
// 设置默认时间(编译时间)
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
// 修改系统设置菜单项
void Menu::showSettings(int16_t page) {
if (!display)
return;
clearAll();
display->setFont(u8g2_font_wqy16_t_gb2312);
display->drawUTF8(96, 16, "系统设置");
display->drawHLine(0, 20, 256);
switch (page) {
case -1: {
items[0] = "蜂鸣器开关";
items[1] = "震动开关";
items[2] = "背光灯开关";
items[3] = "设置时间"; // 新增时间设置选项
items[4] = "关于";
for (int i = 0, c = 38; i < 5; ++i, c += 18) {
display->drawUTF8(0, c, items[i].c_str());
}
break;
}
default:
break;
}
}
// 时间设置页面
void Menu::showRTCTime() {
clearAll();
display->setFont(u8g2_font_wqy16_t_gb2312);
display->drawUTF8(96, 16, "设置时间");
display->drawHLine(0, 20, 256);
// 获取当前时间
DateTime now = isEditingTime ? editTime : rtc.now();
// 格式: 2023-10-25 14:30
char timeStr[32];
snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d",
now.year(), now.month(), now.day(), now.hour(), now.minute());
// 显示时间
display->setFont(u8g2_font_10x20_tf);
display->drawUTF8(20, 60, timeStr);
// 显示当前选中的字段
const char* fields[] = {"年", "月", "日", "时", "分"};
int16_t fieldPos[][2] = {{20, 70}, {65, 70}, {100, 70}, {135, 70}, {170, 70}};
// 绘制选中字段指示器
display->drawTriangle(
fieldPos[timeEditState][0],
fieldPos[timeEditState][1],
fieldPos[timeEditState][0] - 5,
fieldPos[timeEditState][1] + 8,
fieldPos[timeEditState][0] + 5,
fieldPos[timeEditState][1] + 8
);
// 显示操作提示
display->setFont(u8g2_font_wqy12_t_gb2312);
if (isEditingTime) {
display->drawUTF8(60, 100, "上下:修改值 确认:保存");
} else {
display->drawUTF8(50, 100, "左右:切换字段 确认:编辑");
}
display->drawUTF8(70, 120, "返回:取消");
display->sendBuffer();
}
// 修改handleKey函数
void Menu::handleKey(bool up) {
if (!display)
return;
if (!is_menu)
return;
if (is_msg)
return;
switch (menu_page) {
case MENU_CLOSED:
break;
case MENU_SETTINGS:
if (up)
selected_item--;
else
selected_item++;
// 修改为0-4范围(5个菜单项)
if (selected_item < 0) {
selected_item = 4;
}
if (selected_item > 4) {
selected_item = 0;
}
showSettings(sub_page);
highlightItem(selected_item);
break;
// 添加时间设置菜单处理
case MENU_RTC_SETTINGS:
if (isEditingTime) {
// 编辑模式:修改当前字段值
DateTime now = editTime;
switch (timeEditState) {
case 0: // 年
editTime = DateTime(up ? now.year() + 1 : now.year() - 1,
now.month(), now.day(), now.hour(), now.minute());
break;
case 1: // 月
editTime = DateTime(now.year(),
up ? (now.month() % 12) + 1 : (now.month() + 10) % 12 + 1,
min(now.day(), daysInMonth(now.month(), now.year())),
now.hour(), now.minute());
break;
case 2: // 日
editTime = DateTime(now.year(), now.month(),
up ? (now.day() % daysInMonth(now.month(), now.year())) + 1 :
(now.day() + daysInMonth(now.month(), now.year()) - 2) % daysInMonth(now.month(), now.year()) + 1,
now.hour(), now.minute());
break;
case 3: // 时
editTime = DateTime(now.year(), now.month(), now.day(),
up ? (now.hour() + 1) % 24 : (now.hour() + 23) % 24,
now.minute());
break;
case 4: // 分
editTime = DateTime(now.year(), now.month(), now.day(),
now.hour(),
up ? (now.minute() + 1) % 60 : (now.minute() + 59) % 60);
break;
}
} else {
// 浏览模式:切换字段
if (up) {
timeEditState = (timeEditState == 0) ? 4 : timeEditState - 1;
} else {
timeEditState = (timeEditState + 1) % 5;
}
}
showRTCTime();
break;
// ... 其他菜单页处理 ...
}
}
// 辅助函数:计算某月的天数
uint8_t Menu::daysInMonth(uint8_t month, uint16_t year) {
if (month == 2) {
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28;
} else if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
} else {
return 31;
}
}
// 修改acknowledge函数
void Menu::acknowledge() {
if (!display)
return;
if (is_msg) {
showLast();
return;
}
switch (menu_page) {
case MENU_SETTINGS:
if (sub_page == -1) {
switch (selected_item) {
case 0:
menu_page = MENU_Buzzer;
showBuzzer();
selected_item = 0;
highlightItem(selected_item);
break;
case 1:
menu_page = MENU_Bzz;
showBzz();
selected_item = 0;
highlightItem(selected_item);
break;
case 2:
menu_page = MENU_LCDA;
showLCDA();
selected_item = 0;
highlightItem(selected_item);
break;
case 3: // 设置时间
menu_page = MENU_RTC_SETTINGS;
editTime = rtc.now(); // 加载当前时间
timeEditState = 0; // 从年开始
isEditingTime = false; // 初始为浏览模式
showRTCTime();
break;
case 4: // 关于
menu_page = MENU_ABOUT;
sub_page = 1;
showAbout(1);
break;
default:
break;
}
}
break;
// 时间设置菜单确认处理
case MENU_RTC_SETTINGS:
if (isEditingTime) {
// 保存修改
rtc.adjust(editTime);
showMessage("提示", "时间已保存!");
// 退出编辑模式
isEditingTime = false;
} else {
// 进入编辑模式
isEditingTime = true;
}
showRTCTime();
break;
// ... 其他菜单页处理 ...
}
}
// 修改showLast函数
void Menu::showLast() {
if (is_msg) {
is_msg = false;
// ... 显示上一级菜单 ...
}
switch (menu_page) {
case MENU_Buzzer:
sub_page = -1;
showSettings(sub_page);
selected_item = 0;
highlightItem(selected_item);
menu_page = MENU_SETTINGS;
break;
case MENU_Bzz:
sub_page = -1;
showSettings(sub_page);
selected_item = 1;
highlightItem(selected_item);
menu_page = MENU_SETTINGS;
break;
case MENU_LCDA:
sub_page = -1;
showSettings(sub_page);
selected_item = 2;
highlightItem(selected_item);
menu_page = MENU_SETTINGS;
break;
case MENU_ABOUT:
sub_page = -1;
showSettings(sub_page);
selected_item = 4; // 注意:关于现在是第5项
highlightItem(selected_item);
menu_page = MENU_SETTINGS;
break;
case MENU_RTC_SETTINGS:
if (isEditingTime) {
// 退出编辑模式但不保存
isEditingTime = false;
showRTCTime();
} else {
// 返回系统设置
menu_page = MENU_SETTINGS;
sub_page = -1;
selected_item = 3; // 选中"设置时间"选项
showSettings(sub_page);
highlightItem(selected_item);
}
break;
}
}
// 修改highlightItem函数以支持更多菜单项
void Menu::highlightItem(int8_t item) {
// 根据菜单页确定高亮位置
int yPos = 23 + item * 18;
// 对于时间设置菜单特殊处理
if (menu_page == MENU_RTC_SETTINGS) {
// 时间设置菜单不需要高亮项
return;
}
display->drawBox(0, yPos, 256, 18);
display->setDrawColor(0);
display->drawUTF8(0, yPos + 15, items[item].c_str()); // 调整文本位置
display->setDrawColor(1);
display->sendBuffer();
}
```
### 代码说明:
1. **RTC集成**:
- 使用`RTC_DS3231`类与DS3231模块通信
- 在构造函数中初始化RTC并检查运行状态
- 如果RTC未运行,使用编译时间作为默认时间
2. **菜单结构调整**:
- 系统设置菜单从4项扩展到5项
- 新增"设置时间"作为第4项(原"关于"变为第5项)
- 调整菜单滚动范围为0-4
3. **时间设置页面**:
- 横排显示时间:年-月-日 时:分
- 使用三角形指示器标记当前选中字段
- 底部显示操作提示
- 两种模式:浏览模式(选择字段)和编辑模式(修改值)
4. **时间编辑逻辑**:
- **浏览模式**:上下键切换选中字段
- **编辑模式**:上下键修改当前字段值
- 确认键在浏览/编辑模式间切换
- 返回键取消修改或返回上级菜单
- 智能日期处理(考虑不同月份天数)
5. **显示优化**:
- 时间使用大字体显示(10x20)
- 操作提示使用小字体(wqy12)
- 屏幕底部显示操作指南
- 适配256x128屏幕尺寸
### 使用说明:
1. 在系统设置菜单中选择"设置时间"
2. 使用上下键切换要修改的字段(年/月/日/时/分)
3. 按确认键进入编辑模式
4. 使用上下键修改数值
5. 再次按确认键保存修改
6. 按返回键取消修改或返回上级菜单
### 注意事项:
1. 需要确保DS3231已正确连接
2. `daysInMonth`函数处理了闰年情况
3. 时间保存后立即更新RTC
4. 屏幕尺寸为256x128,布局已优化
5. 日期有效性检查确保不会出现无效日期(如2月30日)
### 显示效果示例:
```
系统设置
-------------
蜂鸣器开关
震动开关
背光灯开关
设置时间
关于
设置时间
-------------
2023-10-25 14:30
▲ ▲ ▲ ▲ ▲
年 月日时分
左右:切换字段 确认:编辑
返回:取消
```