PocketPages

Overview

A handheld e-book reader built on an ESP32 and a 3.5" ILI9488 TFT display — a self-contained device that reads plain-text books straight off an SD card, no phone or computer needed once it's loaded up. Four physical buttons drive the whole interface: a folder browser, a paginated reading view with proper pixel-based word wrap, an auto-detected table of contents, and a settings screen for tuning font, color, and spacing to taste. A DS3231 real-time clock tracks reading streaks day to day, and a built-in Wi-Fi access point lets you drop new books onto the SD card wirelessly instead of pulling the card every time. Bookmarks, per-book page caches, and file management (move/delete, straight from the device) all persist across power cycles, so the reader picks up exactly where you left it.

in development

Tech Stack

Firmware: Arduino framework on an ESP32, written in C++. Display driving goes through TFT_eSPI (Bodmer), which handles the ILI9488 over its own dedicated VSPI bus — chosen for its speed with large color TFTs and its built-in support for proportional GFXFF fonts alongside the classic bitmap font. Storage runs on a second, independent HSPI bus so the SD card and display never contend for the same SPI lines, which matters since both get hit constantly during page turns and file scans.

Text layout is done from scratch: a pixel-based word-wrap engine measures actual glyph widths via tft.textWidth() rather than counting characters, so wrapping stays correct whether the active font is the monospaced Classic style or one of the proportional Sans/Sans Bold TrueType-style fonts. Page boundaries are computed once per book and cached to a sidecar .pgifile on the SD card, so reopening a previously-indexed book skips the (slower) full re-scan. Bookmarks, reading stats, and settings all persist through the ESP32's NVS flash partition via the Preferences library — no SD write needed for anything except the books themselves and their page-index caches. Time and streak tracking runs off a DS3231 RTC module over I2C. Wireless file transfer is a minimal WebServer instance running in ESP32 soft-AP mode, serving a single-page upload form with no external dependencies.

Hardware / Wiring

TFT (ILI9488, default VSPI bus): VCC → 3.3V, GND → GND, CS → GPIO 5, RESET → GPIO 33, DC/RS → GPIO 27, SDI/MOSI → GPIO 23, SCK → GPIO 18, SDO/MISO → GPIO 19, LED → GPIO 32 (PWM-driven for brightness control).

SD card module (separate HSPI bus, independent from the TFT): 3V3 → 3.3V, GND → GND, CS → GPIO 4, MOSI → GPIO 16, SCK → GPIO 17, MISO → GPIO 35. MISO sits on an input-only pin specifically to free up GPIO 21/22 for the RTC's I2C bus — safe to do since MISO only ever receives data from the ESP32's side.

DS3231 RTC module (I2C): VCC → 3.3V, GND → GND, SDA → GPIO 21, SCL → GPIO 22. The SQW/32K pins are unused and left disconnected.

Buttons (INPUT_PULLUP, one leg to GND, other leg to GPIO): UP → GPIO 13, DOWN → GPIO 14, SELECT → GPIO 25, BACK → GPIO 26.

Software requirements

Arduino IDE with ESP32 board support installed. Two libraries from the Library Manager: TFT_eSPI (Bodmer) and RTClib (Adafruit) for the DS3231. WiFi.h, WebServer.h, Wire.h, Preferences.h, and SD.h all ship with the ESP32 core, so nothing further to install there. TFT_eSPI needs its User_Setup.h configured for the ILI9488 driver with SMOOTH_FONT enabled, which is what makes the GFXFF proportional fonts (FreeSans9pt7b, FreeSansBold9pt7b) available without any extra #include.

Setup

Wire the display, SD module, RTC, and four buttons per the pinout above, format an SD card as FAT32, and drop some .txt books onto it (nested folders are supported). Flash the sketch from the Arduino IDE with the correct ESP32 board selected. On first boot with a fresh RTC, the device auto-syncs the clock to the sketch's build time if it detects the RTC lost power — worth doing once with an internet-synced computer nearby so the streak tracking starts from an accurate date.

First run

Power on and the boot screen shows a live, Arch-style [ OK ] / [ FAIL ] log under a "PocketPages" header as each subsystem — display, RTC, SD card, NVS storage — comes online, so any wiring issue is visible immediately instead of a silent hang. Once boot finishes, it drops into the root file browser showing every .txt file and folder on the card, sorted alphabetically with folders grouped first.

Usage / Controls

  • Browser — UP/DOWN move the selection, SELECT opens a folder/file or the Settings, Reading Stats, and Wi-Fi Upload rows (root only), BACK goes up a folder. Long-pressing SELECT on a book opens a file-options menu instead (see below).
  • Reading — UP/DOWN turn pages, BACK returns to the browser and credits the session's elapsed time to your reading stats, short-press SELECT opens the table of contents if any chapter markers were detected.
  • Bookmark/resume (hold SELECT) — while reading, hold SELECT for ~0.8s to save your current page's byte offset to NVS flash, keyed to that file's path. Reopening the same file auto-detects the bookmark and jumps back near that spot — a byte offset rather than a page number, so it still lands close even if font size or spacing (and therefore pagination) has changed since. A green "Bookmark saved" toast confirms it.
  • Table of contents — chapter headings are detected heuristically (lines starting with "Chapter"/"Part", or short ALL-CAPS lines) while a book is indexed. Entries wrap across multiple lines instead of truncating, so the full heading is always readable before you commit to jumping there. UP/DOWN wrap around at the list's edges, and the selection persists — reopening the TOC lands back on whatever entry you last had selected, not the top.
  • File options (hold SELECT on a book in the browser) — Move to folder, Delete, or Cancel. Move opens a folders-only picker you can navigate into; selecting "Move Here" relocates the file, carrying its page-index cache and saved bookmark along with it. If a file with the same name already exists at the destination, you're asked to confirm an overwrite before anything is replaced. Delete asks for confirmation before removing the file and its cache.
  • Settings — UP/DOWN move between rows, SELECT cycles that row's value, BACK returns to the browser. A live preview at the bottom of the screen reflects font, color, and spacing changes immediately.
  • Reading Stats (root only) — total pages read, total time read, current and longest daily reading streaks, and the last date you read, all pulled from the RTC-backed streak tracker.
  • Wi-Fi Upload (root only) — starts a Wi-Fi access point and shows the network name, password, and upload URL on screen, along with a live "Device connected" / "Waiting for connection..." indicator so you know the moment a phone or laptop actually joins the AP. BACK stops the server and rescans the folder for anything just uploaded.

Configuration options

  • Font size — Small / Medium / Large (maps to text sizes 1/2/3). Only affects the Classic font, since the two Sans fonts are fixed at 9pt.
  • Font style — Classic (mono, the original bitmap font, size-adjustable), Sans, and Sans Bold (proportional TrueType-style fonts bundled with TFT_eSPI).
  • Text color — White / Green / Yellow / Cyan / Orange.
  • Background — Black / Navy / Dark Grey.
  • Line spacing — Compact / Normal / Relaxed, adding 0/4/8px between lines.
  • Brightness — 25/50/75/100%, driving the backlight pin via PWM rather than a simple on/off.

All six settings persist in NVS flash and reload automatically on next boot. Changing any layout-affecting setting (font style, size, or line spacing) invalidates a book's cached page index, so it gets silently re-scanned and re-cached the next time that book is opened.

Troubleshooting

  • SD card mount fails on boot — reseat the card first, since loose contact in cheap SD module sockets is the most common cause, especially after any nearby wiring was disturbed. If that doesn't fix it, confirm MISO is actually on GPIO 35 (not the old GPIO 21) and check Serial Monitor at 115200 baud for a more specific error than the TFT's plain OK/FAIL.
  • RTC fails to initialize — almost always SDA/SCL wired to the wrong pins or swapped, or the module not getting 3.3V. Confirm SDA → GPIO 21, SCL → GPIO 22.
  • Wi-Fi Upload page won't load on a phone — confirm the phone is actually connected to the PocketPages network (not just visible in the list), that the address is typed as plain http:// rather than https://, and that the phone hasn't auto-switched back to mobile data after detecting no internet access on the AP.
  • Long filenames or chapter titles overflow onto the next row — this is handled via pixel-width truncation/wrapping throughout the UI, but if it's still happening somewhere, the fix is always the same pattern: measure with tft.textWidth() before printing, using whatever font/size is active at that point in the draw call.
  • Page counter is unreadable — this was a real issue with Dark Grey background plus the default grey footer text; the footer now switches to a lighter grey automatically whenever Dark Grey is the active background.

Installation

proceed to pocketpages installer