Skip to content

hansmrtn/pulp-os

Repository files navigation

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

About

a minimal e-ink os targeting the esp32c3 and xteink x4 e-reader

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

 
 
 

Contributors