A fork of pulp-os for the xteink4 adding custom apps
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rust 97.9%
HTML 2.1%
119 1 0

Clone this repository

https://tangled.org/pds.dad/pumpkin-os https://tangled.org/did:plc:rnpkyqnmsw4ipey6eotbdnnf/pumpkin-os
git@tangled.org:pds.dad/pumpkin-os git@tangled.org:did:plc:rnpkyqnmsw4ipey6eotbdnnf/pumpkin-os

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

A fork of pulp-os. The creator has VERY much so done the hard parts. This is just a fork playing around and adding custom apps to it

pulp-os -- e-reader firmware for the XTEink X4

bare-metal e-reader operating system for the XTEink X4 board (ESP32-C3 + SSD1677 e-paper). written in Rust. no std, no framebuffer, no dyn dispatch. async runtime via Embassy on esp-rtos.

hardware mcu ESP32-C3, single-core RISC-V RV32IMC, 160 MHz ram 400 KB DRAM; ~172 KB heap (108 KB main + 64 KB reclaimed) display 800x480 SSD1677 mono e-paper, DMA-backed SPI, portrait storage microSD over shared SPI bus (400 kHz probe, 20 MHz run) input 2 ADC ladders (GPIO1, GPIO2) + power button (GPIO3 IRQ) battery li-ion via ADC, 100K/100K divider on GPIO0

pin map:
  GPIO0   battery ADC          GPIO6   EPD BUSY
  GPIO1   button row 1 ADC     GPIO7   SPI MISO
  GPIO2   button row 2 ADC     GPIO8   SPI SCK
  GPIO3   power button         GPIO10  SPI MOSI
  GPIO4   EPD DC               GPIO12  SD CS (raw register GPIO)
  GPIO5   EPD RST              GPIO21  EPD CS

EPD and SD share SPI2, arbitrated by CriticalSectionDevice.

building requires stable Rust >= 1.88 and the riscv32imc-unknown-none-elf target. rust-toolchain.toml handles both automatically.

    cargo build --release
    espflash flash --monitor --chip esp32c3 target/...

or:

    cargo run --release

local path dependencies (sibling dirs):
  embedded-sdmmc    async FAT filesystem over SD/SPI (local fork)
  smol-epub         no_std epub/zip/html/image processing

features txt reader lazy page-indexed, read-ahead prefetch, proportional font wrapping epub reader ZIP/OPF/HTML-strip pipeline, chapter cache on SD, proportional fonts with bold/italic/heading styles, inline PNG/JPEG (1-bit Floyd-Steinberg dithered), TOC browser (NCX or inline), chapter navigation file browser paginated SD listing, background EPUB title scanner (resolves titles from OPF metadata) bookmarks 16-slot LRU in RAM, flushed to SD every 30 s; home screen bookmarks browser sorted by recency wifi upload HTTP file upload + mDNS (pulp.local); drag-and-drop web UI with delete support fonts regular/bold/italic TTFs rasterised at build time via fontdue; five sizes, book and UI independently configurable display partial DU refresh (~400 ms page turn), periodic full GC refresh (configurable interval) quick menu per-app actions + screen refresh + go home, triggered by power button settings sleep timeout, ghost clear interval, book font size, UI font size, wifi credentials sleep idle timeout + power long-press; EPD deep sleep (~3 uA) + ESP32-C3 deep sleep (~5 uA); GPIO3 wake

controls Prev / Next scroll or turn page PrevJump / NextJump page skip (files: full page; reader: chapter) Select open item Back go back; long-press goes home Power (short) open quick-action menu Power (long) deep sleep

runtime embassy async executor on esp-rtos. five concurrent tasks:

main            event loop: input dispatch, app work, rendering
input_task      10 ms ADC poll, debounce, battery read (30 s)
housekeeping    status bar (5 s), SD check (30 s), bookmark flush (30 s)
idle_timeout    configurable idle timer, signals deep sleep
worker_task     background CPU-heavy work (HTML strip, image decode)

CPU sleeps (WFI) whenever all tasks are waiting.

directory layout kernel/ pulp-kernel workspace crate (zero app imports) src/ lib.rs crate root, re-exports kernel/ mod.rs Kernel struct, resource ownership app.rs App trait, AppLayer trait, AppIdType, Transition, Redraw, AppContext, Launcher, QuickAction protocol types console.rs boot console (FONT_6X13, no fontdue) scheduler.rs main loop, render pipeline, sleep handle.rs KernelHandle (app I/O API) tasks.rs spawned embassy tasks work_queue.rs background work with generation cancellation bookmarks.rs LRU bookmark cache config.rs settings parser/writer dir_cache.rs sorted directory cache with title resolution wake.rs uptime helper (embassy monotonic clock) board/ board support (pin map, SPI wiring, button layout) mod.rs Board::init, peripheral splitting action.rs ActionEvent (semantic button actions) battery.rs voltage-to-percentage mapping button.rs physical button enum, ButtonMapper layout.rs button-to-action table raw_gpio.rs register-level GPIO for SD CS drivers/ hardware drivers mod.rs driver re-exports ssd1677.rs EPD display driver, 3-phase partial refresh strip.rs 4 KB strip buffer, rotation, glyph blitting sdcard.rs SD card init and SPI wiring storage.rs FAT filesystem ops, poll_once, with_fs! macros input.rs ADC button polling, debounce, repeat battery.rs ADC battery voltage sampling ui/ font-independent primitives mod.rs Region, Alignment, stack measurement stack_fmt.rs no_alloc formatting (StackFmt) statusbar.rs status bar rendering widget.rs widget trait and helpers

src/                    distro / app layer
  bin/main.rs           entry point, hardware init, boot
  lib.rs                crate root
  ui/
    mod.rs              app-side UI helpers
  fonts/
    mod.rs              font size tiers, FontSet lookups
    bitmap.rs           build-time bitmap font data
  apps/
    mod.rs              AppId enum, type aliases binding kernel generics
    manager.rs          AppLayer impl, with_app! dispatch, lifecycle
    home.rs             launcher menu + bookmarks browser
    files.rs            SD file browser + background title scanner
    settings.rs         settings UI
    upload.rs           wifi upload server
    reader/
      mod.rs            state machine, lifecycle, draw, quick actions
      paging.rs         text wrapping, page navigation, load/prefetch
      epub_pipeline.rs  ZIP/OPF parsing, chapter caching, background strip
      images.rs         image detection, decode dispatch, dithering
    widgets/
      mod.rs            widget re-exports
      bitmap_label.rs   proportional text label (uses fonts/)
      quick_menu.rs     power-button overlay menu
      button_feedback.rs  button press visual feedback

build.rs                fontdue TTF rasterisation at compile time
assets/fonts/           TTF files (regular, bold, italic)
assets/upload.html      web UI for wifi upload mode

design notes kernel / app split. the kernel crate (kernel/) has zero imports from apps/ or fonts/. the scheduler is generic over AppLayer; it never names a concrete app. AppId is defined by the distro, not the kernel -- the kernel only knows AppIdType::HOME.

no dyn dispatch. with_app!() macro matches AppId, expands to
concrete calls per app struct. all monomorphised; no vtable,
no Box.

strip rendering. 12 x 40-row strips (4 KB each) instead of a
48 KB framebuffer. draw callback fires per strip during SPI
transfer. blit_1bpp_270 fast path walks physical memory linearly
for the portrait rotation. windowed mode for partial refresh.

3-phase partial refresh. write BW RAM, kick DU waveform, collect
input during ~400 ms refresh, then sync RED RAM. phase3 skipped
during rapid navigation (RED marked stale; next partial uses
inv_red recovery). full GC promoted after configurable number
of partials to clear ghosting.

SPI bus sharing. EPD and SD share one SPI2 bus. all SD I/O
completes before any EPD render pass. busy_wait_with_input()
collects only input events, no background work. violating the
ordering panics (RefCell double-borrow), never corrupts.

poll_once. embedded-sdmmc's async API wraps blocking SPI+DMA
that never pends. poll_once drives every future to completion
in a single poll, avoiding task spawn overhead.

KernelHandle. apps never touch hardware. KernelHandle borrows
the Kernel for one lifecycle method and exposes file I/O, dir
cache, bookmarks. every async method does sync work then
yield_now() for executor fairness.

smol-epub sync bridge. smol-epub I/O uses closures, not async.
with_sync_reader() provides a scoped closure that completes
all storage access before returning -- no borrows across await.

heavy statics. large structs (ReaderApp ~28 KB, DirCache ~10 KB,
StripBuffer ~4 KB) live in ConstStaticCell / StaticCell so the
async future stays ~200 B.

nav stack. Launcher<Id> holds a 4-deep stack. transitions
(Push/Pop/Replace/Home) drive on_suspend / on_enter / on_resume
lifecycle. Push degrades to Replace when stack is full.

dirty-region tracking. apps call ctx.mark_dirty(region); regions
are unioned per frame. partial DU or full GC issued accordingly.

work queue. dedicated embassy task for CPU-heavy work (HTML strip,
image decode). generation-based cancellation: bump a counter and
drain channels; worker checks generation before and after
processing. channel capacity 1 for back-pressure.

input. ADC ladders at 100 Hz, 4-sample oversampling, 15 ms
debounce, 1 s long-press, 150 ms repeat. ButtonMapper translates
physical buttons to semantic actions. apps never see hardware.

fonts. build.rs rasterises TTFs via fontdue into 1-bit bitmaps
at five sizes (xsmall through xlarge), three styles (regular,
bold, italic). ASCII direct-indexed, extended unicode binary-
searched. book and UI sizes independently hot-swappable.

boot console. kernel renders text during hardware init using
built-in FONT_6X13 mono font. works with zero fontdue, zero
TTFs. if the SD card is missing, user still sees boot progress.

bookmarks. 16-slot LRU, RAM-resident, binary format on SD.
flushed every 30 s if dirty, plus on sleep. lookup by fnv1a
hash + case-insensitive name comparison.

settings. key=value text in _PULP/SETTINGS.TXT. parsed at boot,
saved on change. font size changes propagate to all apps.

wifi upload. bypasses normal dispatch. HTTP server on port 80,
mDNS on 5353 (pulp.local). multipart upload with 8.3 filename
sanitisation. radio torn down before returning to app loop.

memory budget. ~172 KB heap for epub text and image decode
(alloc::vec). everything else is static or stack. ~56 KB stack,
painted 0xDEAD_BEEF at boot, high-water mark logged every 5 s.

forkable kernel. designed to be extracted as a standalone crate.
a fork defines its own AppId, implements AppLayer, brings its
own fonts and apps, writes a main.rs. the kernel provides
drivers, scheduling, storage, bookmarks, config, and a working
EPD with mono boot console.

license MIT