PaperOS is custom firmware for the M5Stack PaperS3, with a touch launcher, clocks, weather, books, notes, drawing, games, and utilities. It is a modular C++ application on Arduino-ESP32/FreeRTOS, not a desktop OS with isolated application processes.
Screenshot placeholder:
docs/images/launcher.png— add a home-screen image here.
- Development setup
- Navigation and first boot
- Settings and AP Mode
- SD-card structure
- Apps
- Uploading books
- PDF books
- Architecture and customization
- Testing and troubleshooting
- Limitations and credits
Use a PaperS3, a USB data cable, and a microSD card for content. A card reader is the easiest way to copy books/images. This firmware targets PaperS3, not the original M5Paper.
| Tool/library | Purpose |
|---|---|
| VS Code + PlatformIO IDE | Editing, dependencies, compiler, build, upload, serial monitor |
| C++ / Arduino-ESP32 / FreeRTOS | Application code, hardware APIs, task scheduling |
| M5Unified / M5GFX | Display, touch, RTC, power, buzzer, fonts and image decoding |
| SD, SPI, WiFi, WebServer, Preferences | Storage, network, setup portal and persistent settings |
| ArduinoJson | JSON parsing for weather and flashcards |
| LVGL, PubSubClient | Declared integration dependencies; most current UI uses M5GFX directly |
| Python 3, Pillow, Poppler | Optional desktop PDF conversion |
| Git | Source history and collaboration |
platformio.ini is authoritative: environment papers3, platform espressif32@7.0.1, Arduino framework, board definition esp32-s3-devkitm-1, 16 MB flash configuration, and qio_opi memory configuration for PSRAM. M5Unified detects the actual PaperS3. Do not change the generic board name simply because it differs from the product name.
- Install VS Code and the PlatformIO IDE extension.
- Open the project root containing
platformio.ini,include, andsrc. - Let PlatformIO download its dependencies. The first build needs internet access.
- Open a PlatformIO terminal in this folder; use it if
piois not available in your normal terminal. - Connect the device with a data cable and run:
pio run
pio device list
pio run -t upload
pio device monitor -b 115200If necessary, select the port explicitly:
# Substitute the port listed on your Mac
pio run -t upload --upload-port /dev/cu.usbmodem101
pio device monitor --port /dev/cu.usbmodem101 -b 115200On Windows use the listed port, such as COM5. Exit the monitor with Ctrl+C, including on macOS, before uploading. To clean and rebuild, run pio run -t clean followed by pio run. Firmware upload does not copy SD assets.
Wi-Fi can be configured on-device; a secrets header is optional. include/config/Secrets.h.example provides a developer fallback where needed. Never commit credentials/API keys. The ESP32 platform is deliberately pinned after earlier Network/WiFi framework compatibility issues.
If the compiler succeeds but IntelliSense cannot find sdkconfig.h, FreeRTOS, or M5Unified headers, run PlatformIO: Rebuild C/C++ Project Index, then reload VS Code. Let PlatformIO manage include paths. After moving the project, reopen the new root and rebuild the index; do not copy framework headers into the project.
| Launcher row | Left | Middle | Right |
|---|---|---|---|
| 1 | Weather | Clock | Focus |
| 2 | Notes | To Do | Calendar |
| 3 | eReader | Photos | Sketch |
| 4 | Games | Flashcards | Utilities |
Tap a tile to open it. Games and Utilities use blocking selection popups; Close dismisses them. Launcher footer controls provide power options, manual Home refresh, and Settings. The header contains date, title, Wi-Fi and battery/charging indicators.
Footer sections are full touch targets. Home returns to the launcher; Back returns to a parent page; Close leaves the current reader/modal; Prev/Next change items/pages. Reader navigation is Prev/Close/Next; flashcard category navigation is Prev/Back/Next. Swipe support is app-specific—use visible controls where swipes are unavailable.
E-paper retains its last image without continuous drawing. Brief flash/ghosting can occur during updates; static controls should stay still during value changes, although some older screens still redraw larger regions.
Open Settings from the launcher footer. Tap rows/arrows to enter subpages and Back to return. Device preferences generally survive restart; SD files are stored separately.
| Page | Controls |
|---|---|
| Wi-Fi & Network | Connection, saved SSID, signal, asynchronous scan, password entry, AP Mode |
| Home Address | Address, city, country, timezone; edit via portal |
| Clock & Time | Manual date/time, 12/24-hour, timezone, NTP sync, temperature unit, face |
| Weather Updates | Offline retry interval and weather face |
| Power & Sleep | Inactivity timeout, Sleep now, power-off confirmation |
| Button Sounds | Direct Yes/No toggle for shared touch feedback |
| System & Recovery | Monitor, restart, confirmed factory reset |
| Display & Font | Typography scale and 180-degree display flip |
| Flashcard AI | Key status, model names, link to AP configuration |
- Go to Wi-Fi & Network → Search Wi-Fi.
- Wait for results, select a 2.4 GHz network, and type its password.
- Use
ABC/abcfor case,123for symbols, Space and Back for editing. The native input currently displays entered characters. - Tap Connect and check status. For a hidden/unlisted SSID, use the portal's manual network field.
- Enable AP Mode (disabled by default).
- Scan its QR with your phone camera, or join PaperOS-Setup, password paperossetup.
- Stay connected even if the phone reports no internet.
- Open http://192.168.4.1 manually; automatic captive-portal opening is not required.
- Save changes. The page redirects and reads stored settings. Saved secret values are not displayed back as plaintext.
- Disable AP Mode when finished; the help and QR disappear.
The QR joins Wi-Fi; it does not submit settings or open the portal automatically. Its credentials share the same source as the access point.
The portal supports Wi-Fi/manual SSID, address/city/country, coordinates, POSIX timezone, 24-hour format, sleep/retry intervals, sounds, Gemini configuration, and uploads. It does not expose every device setting. Address edits do not automatically geocode: update latitude/longitude for weather too. The timezone expects a POSIX expression such as the project's India setting IST-5:30.
General uploads go to /PaperOS/uploads with an 8 MB limit. Font uploads go to /PaperOS/fonts with a 2 MB limit. Uploads use temporary .part files. Storing a font does not automatically make it selectable. Move books from uploads to books using Files or a card reader.
Screenshot placeholders:
docs/images/settings.png,docs/images/wifi-ap-qr.png,docs/images/setup-portal.png.
Use Clock & Time to adjust day/month/year/hour/minute and save when NTP is unavailable. The time service writes the hardware RTC when available. Shared settings control 12/24-hour format and Celsius/Fahrenheit.
An inactivity timeout of zero disables automatic sleep. Clock, Weather and Focus inhibit global inactivity sleep while active; Focus also has its own idle view. Sleep displays a static page, and touch wake returns to the launcher. Use power options for Sleep, Off or Cancel; preserve the device's hardware power-button safety behavior. Turn the device on using its hardware power control after shutdown.
Factory reset clears preferences/credentials but preserves SD files. An SD backup does not include preferences stored in device flash.
Use a card compatible with Arduino's SD library; FAT32 is the recommended starting format. Back up files before formatting. These paths are on the card:
/PaperOS/
books/ EPUB, TXT, MD, converted PDF folders
photos/ JPG/JPEG, PNG, BMP
notes/YYYY-MM-DD/ note_001.txt, note_002.txt, ...
todo/tasks.txt Task data shared with Calendar
writing/ Dated saved sketches
flashcards/ Category JSON decks
images/ Card illustrations
chess/ Twelve chess-piece PNGs
icons/weather/ Eight weather PNGs
fonts/ Uploaded fonts
uploads/ General portal uploads
cache/ EPUB chapter/image cache
backup/ Diagnostic snapshot
pomodoro/ Optional pomodoro.png idle image
Power off before removing the card. Safely eject it from your computer and reopen the relevant app after reinsertion. USB SD Mass Storage mode is not implemented.
Configure router connectivity and location coordinates first. Weather uses Open-Meteo with a background request service. Use the refresh icon for new data; offline retries use Settings → Weather Updates. Forecasts are outdoor estimates, not onboard sensor readings. Normally landscape, with double-tap fullscreen and context-dependent Prev/Next forecast navigation. Select a face through settings or the app's face-change interaction.
| Face | Display |
|---|---|
| Dashboard | Current conditions, hourly strip and selected-day detail |
| Minimal | Large current temperature and essential conditions |
| Hourly | Next-hours forecast |
| Daily | Selected-day forecast |
| Today Card | Compact current weather card |
| Timeline | Hourly temperature timeline |
| 3-Day | Three forecast-day cards |
| Weather Clock | Weather and time |
Copy eight PNGs from assets/weather to /PaperOS/icons/weather, preserving names. Missing files use built-in fallback drawings. See asset mapping. Labels use configured city/country.
Screenshot placeholder:
docs/images/weather-faces.png.
Tap content once to cycle faces, or choose Settings → Clock & Time → Clock face. Double-tap enters/exits fullscreen. Single taps do not cycle while fullscreen. The face selection persists.
| Face | Layout |
|---|---|
| Flip clock | Landscape split cards for individual time digits |
| Minimal digital | Landscape large time, date, outdoor temperature |
| Analog | Landscape bold dial/hands, date and temperature |
| Date & temperature | Landscape date-focused layout with time/weather |
| Dotted | Landscape dot-matrix time and date/temperature |
| Dashboard panel | Landscape split time/information panel |
| Dark flip | Portrait dark tiles: hour/minute, month/date/day, temperature/humidity |
Faces use shared time/unit preferences and display flip. Dark flip selects portrait automatically; other faces use landscape. Leaving Clock restores portrait. Dark flip updates changed tiles on minute ticks; older faces vary in redraw scope.
Weather values are cached. Open Weather and fetch successfully to populate them; Clock does not perform a separate live weather request. -- means unavailable. Values can be stale; Dark flip labels them as cached outdoor readings and draws its degree symbol geometrically.
Screenshot placeholders:
docs/images/clock-faces.png,docs/images/clock-dark-flip.png.
Choose 25, 5, or 30 minutes, then Start, Pause or Stop. Tap the timer to cycle Ring, Minimal, Progress, and Focus styles. The upper refresh icon redraws it. Timer changes repaint the timer region instead of the six static controls. The buzzer signals actions/completion. Reopening the app starts a new session; it is not a persistent background timer. Its idle view optionally uses /PaperOS/pomodoro/pomodoro.png.
Use the QWERTY keyboard, Shift, punctuation, Space and Back. Save creates /PaperOS/notes/YYYY-MM-DD/note_001.txt and subsequent numbers, starting again each date. Set the correct date first. Clear empties the draft. Retrieve files via Files or a computer. This is plain-text entry, not a rich-text editor.
To Do provides task entry and task-management/completion controls with a Notes-style keyboard. Data persists at /PaperOS/todo/tasks.txt. Calendar displays the month grid and date-filtered tasks; Prev/Next browse months. Check task dates if an item does not appear for today. These apps do not synchronize with Microsoft To Do or Google Calendar.
Calendar now opens as a combined day dashboard: a live stacked clock, day-progress percentage with 24 hourly dots, a Monday-first month calendar, and open tasks for the selected date. It uses the system RTC/NTP time and 12/24-hour preference. Tap a date to filter tasks, tap Today (or the month heading) to return to today, and use PREV/NEXT or horizontal swipes to browse months. Two tasks are shown per page; tap the task area to cycle through additional pages. Long task text is ellipsized; use To Do to read/edit the complete task. Completed tasks are excluded, and undated legacy tasks count as today's tasks. No sample or online calendar events are fabricated.
While awake, time updates once per minute; selecting a date redraws only the changed date cells and task panel. The SD task list is checked once per minute and redrawn only if changed. Midnight advances the selection only when you were viewing today. Existing sleep settings still apply. Without a valid date/time the dashboard shows placeholders and asks you to set the clock; without an SD card it explains that tasks are unavailable.
Tap a Library entry, then use Prev / Close / Next. The list shows format, size and reading status. Supported reading progress is saved; retain a book's path when possible so its saved position remains associated.
Tap the book title for Reader Settings: Serif/Sans, Normal/Large text, Compact/Normal/Relaxed line spacing, Chapters, and Jump Forward (+10%). Chapters opens a blocking selector; Close returns to settings. Text formatting changes apply to supported text, not text embedded in PDF page images.
The local EPUB adapter handles container/manifest/spine, semantic blocks, supported JPEG/PNG illustrations and SD cache. It is bounded and does not provide full browser-grade CSS/layout or the complete upstream CrossPoint engine. See upload instructions below.
Screenshot placeholders:
docs/images/reader-library.png,docs/images/reader-page.png,docs/images/reader-settings.png.
Copy JPG/JPEG, PNG or BMP files directly to /PaperOS/photos. Use Prev/Next or horizontal swipes. Double-tap toggles fullscreen. Images are fitted/centered with aspect ratio preserved, so margins are normal. Loading feedback appears before decoding; resize large photos on a computer for better performance.
Draw inside the canvas. Toolbar: Pen, Erase, Undo, Clear, New, View, Save. Empty/cleared drawings are not saved. Save overwrites the current drawing until New starts another. View browses sketches under /PaperOS/writing, grouped by date when available. Capacitive touch drawing does not provide pressure sensitivity, palm rejection or handwriting recognition.
Select Dinosaurs, Space, Animals, Science, Geography or History on the landing page. Inside a category use Prev/Back/Next. Generate requests another item when Wi-Fi and Gemini configuration are available. Cards are saved to category JSON for offline use and duplicate names are checked. Facts/metadata and optional images are displayed; historical reveal-button descriptions may not match the current screen.
Deck files are /PaperOS/flashcards/<category>.json. Firmware seeds the dinosaur starter deck; do not assume every category already contains 200 items. Example:
{"deck":"DINOSAURS","cards":[{
"name":"Tyrannosaurus rex",
"fact":"A large theropod from Late Cretaceous North America.",
"era":"Late Cretaceous","diet":"Carnivore","length":"About 12 m",
"region":"North America","image":"images/tyrannosaurus-rex.png"
}]}Images are relative JPEG/PNG paths; empty image is valid. Prefer modest illustrations around 180×130 pixels. Legacy metadata keys also carry category-specific information. Back up decks before editing.
Configure Gemini key/models via Settings → Flashcard AI → Enable AP Setup and the portal. Use models available to your Google project. Free/paid quotas and image eligibility depend on that project. Text can save even if image generation fails; review AI facts for accuracy.
tools/generate_flashcard_decks.py accepts GEMINI_API_KEY, --model, --count, and --output to generate bulk decks. It makes real API requests and overwrites output category files; use a staging folder and an available model rather than assuming its hard-coded default works. It creates image placeholders, not finished illustrations.
| Game | Controls and scope |
|---|---|
| Chess | Play White: tap piece then destination; Black replies. New Game requires Yes/No. Captures, king safety, queen promotion and checkmate/stalemate for both sides are implemented. Castling, en passant, repetition/fifty-move draws are not. |
| Sudoku | Select cell then number; Clear Cell and New Puzzle controls. Fixed clues cannot be edited. |
| Snake | Direction buttons or swipes; collect food, avoid walls/body. Game-over sound and persistent high score. |
| Tetris | Buttons or left/right/down swipes; up rotates. Next-piece preview, score/high score and reset. E-paper limits motion smoothness. |
| Minesweeper | 9×9, ten mines. Reveal opens cells; Flag marks suspects. First reveal and neighbours are safe; Reset confirms. Safe opening does not guarantee a no-guess board. |
For chess images, copy the twelve PNGs from assets/chess to /PaperOS/chess; otherwise letters are used. Chess now updates only affected squares and changed status text during play. These are lightweight game implementations; device playtesting remains important.
Screenshot placeholder:
docs/images/games.png.
| Utility | Usage and status |
|---|---|
| Calculator | Arithmetic, sign, percent, decimal, AC, backspace, M+ and MR. Previous entry is shown above result; calculations refresh the display area. |
| Converter | Change Unit cycles Celsius→Fahrenheit, km→miles, kg→lb, litres→US gallons, cm→inches, kPa→PSI. Basic directional conversion with numeric keypad. |
| Files | Browse SD folders, select items, create folders, delete with confirmation, move selected files. Back returns to parent; Prev/Next paginate. |
| Alarm | Schedule-entry UI only: hour/minute and enable controls. Reliable background ringing is not implemented. |
| Backup | Create Snapshot writes diagnostic /PaperOS/backup/system.txt. Not a full settings/content backup or restore system. |
System Monitor lives in Settings → System & Recovery. HA Home remains hidden; its module is registered for future integration. Shopping List is not exposed. Compass was removed because the onboard IMU does not provide magnetic heading.
For a real backup, copy the entire /PaperOS SD directory to your computer and retain firmware source separately. NVS device preferences are not included in that copy.
- Power off, remove the card, and mount it in your computer.
- Copy DRM-free
.epub,.txtor.mdfiles directly to/PaperOS/books. - Prefer UTF-8 text and ordinary reflowable EPUBs. Markdown is treated as text; full Markdown rendering is not promised.
- Safely eject/reinsert the card, start PaperOS, open eReader and tap the book.
Do not unpack EPUBs. Raw PDFs require conversion. Arbitrary nested book directories are not equivalent to recognized PDF folders.
Join AP Mode and upload through SD Card Updates. Files up to 8 MB go to /PaperOS/uploads. Move them into /PaperOS/books through Utilities → Files and reopen eReader. Use a card reader for larger books or PDF directories; the portal does not upload/unpack complete directory trees or ZIP books.
PDF pages are rendered on your computer into PNGs; the device does not parse raw PDFs. This preserves layout but provides fixed page images, without PDF text reflow, selection or search.
With Homebrew already available, install Poppler. Use a Python environment for Pillow:
brew install poppler
python3 -m venv /tmp/paperos-pdf-venv
source /tmp/paperos-pdf-venv/bin/activate
python -m pip install Pillow
python tools/pdf_to_papers3.py "$HOME/Downloads/book.pdf" \
--output "/Volumes/SDCARD/PaperOS/books" --title "My Book"Replace SDCARD with your mounted volume. The temporary Python environment may need recreating after cleanup; a persistent location outside the repository is also fine.
Install Python 3 and Poppler and ensure pdftoppm is on PATH. The included wrapper provides this setup:
winget install oschwartz10612.Poppler
py -m pip install Pillow
.\tools\Convert-PdfToPaperS3.ps1 -Pdf "$HOME\Downloads\book.pdf" `
-Output "E:\PaperOS\books" -Title "My Book"Replace E: with your card. If script execution is restricted, invoke Python directly:
py tools\pdf_to_papers3.py "$HOME\Downloads\book.pdf" --output "E:\PaperOS\books" --title "My Book"The converter supports --dpi (default 150); the wrapper uses -Dpi. Output pages are 540×780 grayscale PNGs, centered and quantized to four levels:
/PaperOS/books/book/
manifest.txt
page_001.png
page_002.png
...
The manifest contains PAPERS3_PDF=1, TITLE=..., and PAGES=.... Keep names unchanged and copy the whole folder when converting to local storage first. Use a fresh destination when reconverting a different book with the same name. Large PDFs require temporary space for rendered pages.
After reinserting the card, choose the converted book and use Prev/Close/Next. Progress associates with its path. Very small PDF print may still be hard to read on a 540-pixel-wide display; crop/simplify the source on a computer if necessary.
platformio.ini Build/dependency configuration
partitions.csv Flash layout
src/main.cpp Register services/apps/navigation
include/app/ + src/app/ IApp, AppContext, AppManager
include/apps/ + src/apps/ Individual app models/views
src/platform/ Device capabilities and workers
include/services/Services.h Shared service interfaces/state
src/services/Services.cpp Service composition/ticking
include/ui/ + src/ui/ Chrome, Theme, Typography, Icons, Popup
assets/ Optional SD assets/licenses
tools/ PDF/deck desktop helpers
test/ Regression checks
docs/ Detailed guides, audit, test plan
For .NET developers: IApp is a lifecycle interface (onStart, onTick, onStop); AppContext supplies dependencies; AppManager coordinates navigation. All apps share one address space and one hardware initialization. A crash can restart the whole device.
Add an app with its own header/source, implement IApp, add an AppId, register/instantiate in main.cpp, and add a launcher/modal destination. Do not import standalone setup()/loop() or reinitialize display/SD. Stop owned work and release resources when leaving.
| Customization | Source |
|---|---|
| Header/footer labels, geometry, hit areas | src/ui/Chrome.cpp, include/ui/Chrome.h |
| Borders, radii, shared sizes/colors | include/ui/Theme.h, src/ui/Theme.cpp |
| Font roles/scaling | include/ui/Typography.h, src/ui/Typography.cpp |
| Icons | src/ui/Icons.cpp and associated assets |
| Modal headers/Close | src/ui/Popup.cpp |
| Launcher layout and folders | src/apps/launcher/LauncherApp.cpp |
| Clock rendering and saved face names | src/apps/clock/ClockApp.cpp, src/platform/time/ClockFaceService.cpp |
| Settings UI/persistence | src/apps/settings/SettingsApp.cpp, src/platform/settings/DeviceSettingsService.cpp |
| AP HTML/upload routes | src/platform/network/NetworkService.cpp |
Use runtime Chrome bounds, Theme's three-pixel frames and semantic Typography roles. Draw static buttons/borders on entry and refresh changed areas afterward. Match hitboxes to geometry. Some older apps still have direct fonts/fixed coordinates, so global changes do not automatically update every element.
Large EPUB buffers use bounded PSRAM allocations; inflater state uses internal memory to avoid loop-task stack overflow. Images are decoded as needed. Avoid blocking work in ticks and close files/free buffers. Network workers transfer results to the UI. E-paper updates, HTTPS, AP transfers, image decoding and games consume power; partial updates reduce work but do not guarantee zero flicker.
There is no fixed resource-usage percentage: pio run reports static RAM/flash, and System Monitor reports runtime resources. Free heap does not measure CPU utilization. Content size, active app and Wi-Fi activity affect headroom/battery life.
Run pio run and python3 test/chess_rules_check.py. See TEST_PLAN.md for device testing. Build success does not prove touch alignment, QR scanning, image appearance or battery behavior.
| Symptom | Check |
|---|---|
| Cannot upload | Exit monitor, check data cable and pio device list |
| Editor-only header errors | Rebuild PlatformIO C/C++ index |
| Network.h missing after upgrades | Restore pinned project configuration and inspect framework packages |
| Partition overlap | Check partitions.csv; do not independently move offsets |
| Missing SD files | Mount status, exact folder/extension, reopen app |
| PDF missing | Convert first; keep manifest and numbered pages together |
| EPUB section too large | Try a smaller reflowable EPUB; bounded parser limits apply |
| Stale EPUB after replacement | Back up/remove its files in /PaperOS/cache, reopen |
| AP portal absent | Stay joined; manually open http://192.168.4.1 |
| Weather offline | Router/internet, coordinates, retry setting and serial logs |
| Clock temperature/humidity stale | Open Weather and fetch successfully |
| Gemini failure | Key, model access, quota, internet, [GEMINI] serial output |
| Chess letter pieces | Copy twelve asset PNGs to /PaperOS/chess |
Check logs for secrets before sharing them. Factory reset clears preferences, not SD content; use it as recovery rather than the first troubleshooting step.
- No USB SD Mass Storage, application package installation or implemented OTA update workflow.
- Alarm ringing and full backup/restore are incomplete.
- Reader lacks full EPUB CSS fidelity and user-facing margins/orientation/cache-clear settings.
- Uploaded fonts are stored; arbitrary font activation is incomplete.
- HA Home is hidden, and Chess special moves/draw rules remain incomplete.
- Centralized typography and minimum-area refresh have not yet reached every older screen.
- Local preferences are not an encrypted credential vault.
PaperOS is a unified implementation, but its apps were informed by these projects:
| Repository | PaperOS usage |
|---|---|
| Boisti13/papers3-dashboard | Dashboard, Home Assistant and power-management reference |
| squirmen/PaperS3Weather | Weather data and e-paper presentation reference |
| juicecultus/EPub-M5Stack-Paper-S3 | Earlier EPUB parsing/pagination reference |
| juicecultus/crosspoint-reader-papers3 | EPUB architecture, spine, cache and reader-experience reference |
| omeriko9/M5Paper_PaperS3_eBookReader | Reader behavior and format research |
| arunmathaisk/PaperS3-chess | Chess layout, initial turn flow and optional PNG pieces |
PaperOS is released under the root MIT License, copyright © 2026 Saurav Singh.