简介
在完成了一个基于M5 Stack Tab5的SD卡文件浏览项目后,我想到一个有趣的需求:如果能在这块屏幕上舒适地阅读电子书那该多好。相比其他电子阅读设备,M5 Stack拥有充足的RAM和大容量的Flash,用它来实现一个简单但功能完整的文本阅读器是完全可行的。
于是我决定在Arduino环境下,利用M5UNIFIED和M5GFX这两个官方BSP库,开发一个具有以下功能的电子书阅读器
主要支持功能如下
本地TXT文件阅读:直接从SD卡读取文本文件
进度自动保存:记住每个文件的阅读位置,下次打开自动恢复
简易UI设计:清晰的导航栏、翻页操作和进度显示
翻页:触摸屏左右区域实现前后翻页
视频效果如下
核心设计思路1. 进度保存机制
与简单的文件浏览不同,阅读器需要记住用户的阅读位置。我采用了一个简洁的方案:
/* ===== 保存读取进度 ===== */
void saveProgress() {
if (!reading || currentFile.isEmpty())
return;
if (filePos == lastSavePos)
return; // 避免频繁写入
String progFile = String(PROGRESS_DIR) + "/" + currentFile + ".pos";
File f = SD.open(progFile, FILE_WRITE);
if (f) {
f.seek(0);
f.write((uint8_t *)&filePos, sizeof(filePos));
f.close();
lastSavePos = filePos;
}
}
uint32_t loadProgress(String filename) {
String progFile = String(PROGRESS_DIR) + "/" + filename + ".pos";
if (SD.exists(progFile)) {
File f = SD.open(progFile, FILE_READ);
if (f && f.size() >= sizeof(uint32_t)) {
uint32_t pos = 0;
f.read((uint8_t *)&pos, sizeof(pos));
f.close();
return pos;
}
if (f) f.close();
}
return 0;
}每个TXT文件都对应一个.pos文件,记录当前的字节位置。这样做的优点是
轻量级,不占用大量空间
快速查找和保存
支持多个文件的独立进度记录
2. 分页显示逻辑
为了充分利用屏幕空间,我设定每次读取256字节并逐行显示:
#define PAGE_BYTES 256 // 每次读取256字节 #define TOP_MARGIN 65 // 顶部导航栏高度
在绘制界面时,先绘制顶部导航栏,然后从TOP_MARGIN位置开始显示文本内容:
void drawPage() {
if (!bookFile) return;
M5.Display.clear(TFT_BLACK);
// 绘制顶部栏
M5.Display.fillRect(0, 0, M5.Display.width(), 45, TFT_DARKGREY);
M5.Display.drawLine(0, 45, M5.Display.width(), 45, TFT_WHITE);
// 返回按钮
M5.Display.fillRect(5, 2, 50, 35, TFT_BLUE);
M5.Display.drawRect(5, 2, 50, 35, TFT_WHITE);
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setCursor(14, 25);
M5.Display.println("Back");
// 文件名和进度
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.setCursor(65, 15);
M5.Display.printf("%s", currentFile.c_str());
// 进度显示在右上角
float progress = (float)filePos / maxFileSize * 100.0;
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.setCursor(M5.Display.width() - 100, 20);
M5.Display.printf("%.0f%%", progress);
// 内容显示
M5.Display.setFont(&fonts::FreeMono18pt7b);
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setCursor(10, TOP_MARGIN + 5);
bookFile.seek(filePos);
char buf[PAGE_BYTES + 1];
int n = bookFile.readBytes(buf, PAGE_BYTES);
buf[n] = 0;
M5.Display.print(buf);
}3. 触摸交互
文件列表界面和阅读界面分别处理触摸事件
void loop() {
M5.update();
// 定期保存进度(每5秒)
if (reading && millis() - lastSaveTime > 5000) {
saveProgress();
lastSaveTime = millis();
}
int touchCount = M5.Touch.getCount();
if (touchCount > 0) {
auto t = M5.Touch.getDetail();
if (!touchActive) {
touchActive = true;
if (!reading) {
checkFileClick(t.x, t.y); // 列表交互
} else {
int w = M5.Display.width();
if (t.y <= 40 && t.x <= 55) {
saveProgress();
if (bookFile) bookFile.close();
listDir(SD, "/"); // 返回列表
} else if (t.x < w / 2) {
prevPage(); // 左侧 - 上一页
} else {
nextPage(); // 右侧 - 下一页
}
}
}
} else {
touchActive = false;
}
}效果如下所示

文件列表

阅读界面
附件代码
我要赚赏金
