A manga reader for M5PaperS3
Pure ESP-IDF app (main/, entry app_main in main/main.cpp).
Originally ported from an Arduino sketch; the old src/ + platformio.ini
were removed once the port was complete (still in git history if needed).
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyACM0 flash monitorsdkconfig.defaults already selects ESP32-S3, 16MB QIO flash, OPI PSRAM,
USB-CDC console and the custom partitions.csv (15MB factory app).
Dependencies (m5stack/m5unified, m5stack/m5gfx) come from the ESP
component registry and are pinned in dependencies.lock.
Each entry is either a folder of m5_0000.jpg, m5_0001.jpg, … files or a
.cbz archive (ZIP with JPG/PNG images, any filenames, read in natural
sort order). CBZ pages are extracted on demand straight to PSRAM — nothing
is unpacked onto the SD card — using a minimal ZIP reader (main/cbz.cpp)
with DEFLATE handled by the ESP32-S3 ROM tinfl. Encrypted, multi-disk and
ZIP64 archives are refused. .cbz files can be uploaded via the WiFi file
browser like any other file. CBZ pages grouped in top-level folders become
navigable chapters: the Book Menu (swipe up from the bottom while reading)
gains a < Chapter (i/n) > stepper below the page changer that jumps to the
neighboring chapter's first page. Flat archives and folder books show
NO CHAPTERS instead.
Covers are cached per book in /sdcard/.thumbs/ as pre-fitted 221x313
8-bit raw dumps: the first menu visit decodes page 0 once, later visits do
a single fread + memcpy with no image decode. Cache keys embed the cover's
size+mtime for automatic invalidation (plus a format magic), orphans are
purged on book delete and capped at 256 files.
Per-book structure is also indexed in /sdcard/.index/ (magic + size/mtime,
capped at 256): a CBZ caches its central directory, a folder caches its page
count. Opening a book then restores the index with one small read instead of
reparsing the archive or re-probing m5_NNNN.jpg files. A changed archive
(size/mtime) or a folder whose boundary no longer matches re-indexes
automatically; appends/removals are caught even when FAT leaves the directory
mtime untouched. Deletes/renames purge the affected index and leaving the
WiFi browser rescans the library.
String->std::string;File/SD.h-> POSIXfopen/opendir/staton the SDMMC mount/sdcard(MANGA_ROOT=/sdcard/manga,PIC_ROOT=/sdcard/pic).Preferences-> NVS (nvs_flash, namespacemanga:lastPath,lastPage).WiFi.h+WebServer->esp_wifisoft-AP +esp_http_server(streaming multipart upload parser, recursive delete,mkdir -p).- JPEG decode is bitbank2/JPEGDEC, vendored under
components/jpegdec(upstream86282979; ESP-IDF 6-Werrorfixes + S3-SIMD wiring documented in its CMakeLists). Full-res pages render through it; PNG and thumbnails stay on M5GFX's built-in codecs. millis/delay/random->esp_timer_get_time/vTaskDelay/esp_random(seemain/compat.h);setCpuFrequencyMhz()is a no-op, clock scaling is left toCONFIG_PM_ENABLE.setup()/loop()->app_main()with a FreeRTOS loop; display, touch, power and EPD APIs are unchanged (M5Unified/M5GFX work on both frameworks).- Quality refresh is Kindle-style:
tools/patch_epd_lut.py(run at configure time, hash-pinned to the M5GFX version independencies.lock) replaces the stock ~36-scanlut_eraser/lut_qualitytables with a short black flash -> white -> image script (~18 scans). See the script header for tuning and revert instructions. fullRefresh()(ui.cpp) renders through a solid full waveform for the power-off splash that persists on screen: the same script keeps long-saturate black/white tables plus aPanel_EPD::refreshStockWaveform()one-shot (frame counts tunable viaSOLID_*_FRAMES). The power-off splash never switches back (reboot re-expands the Kindle tables).- Opening a book and leaving the Book Menu also take one clean stock-waveform
repaint:
drawPage()(ui.cpp) callsrefreshStockWaveform()for that frame and thenPanel_EPD::restoreFastWaveform()to re-expand the Kindle tables, so page turns after it stay fast. Requested viaforceStockRefresh.