From 3e6ecf82e6f95490ac6b6d73833cc2ab17ef49a2 Mon Sep 17 00:00:00 2001 From: Sadeep Madurange Date: Sat, 27 Dec 2025 18:58:31 +0800 Subject: E-reader & bumblebee. --- _log/bumblebee.md | 2 +- _log/e-reader.md | 139 ++++++++++++++++++++++-------------------------------- 2 files changed, 58 insertions(+), 83 deletions(-) (limited to '_log') diff --git a/_log/bumblebee.md b/_log/bumblebee.md index 74fc06b..588ae42 100644 --- a/_log/bumblebee.md +++ b/_log/bumblebee.md @@ -37,7 +37,7 @@ raised → parsed into a token → insert to list → interpret event → look u instruction from a table → form instruction with event args → insert text to a parallel list → run both lists through optimizer → update Scintilla editor. -Problem: manual overriding via Scintilla editor mid-session causes the code +Limitation: manual overriding via Scintilla editor mid-session causes the code list to go out of sync with the event list. Optimizer can't handle this yet. Note to self: need to rethink the event/text list data structures in the diff --git a/_log/e-reader.md b/_log/e-reader.md index cab744a..2edd5dd 100644 --- a/_log/e-reader.md +++ b/_log/e-reader.md @@ -1,94 +1,69 @@ --- -title: Experimental e-reader +title: ESP32 e-reader prototype date: 2023-10-24 layout: post project: true thumbnail: thumb_sm.png --- -This project features an experimental e-reader powered by an ESP-WROOM-32 -development board and a 7.5-inch Waveshare -e-paper display built with the intention of learning about e-paper displays. +First project with e-paper displays and ESP32. -## Introduction - -The prototype e-reader comprises an ESP32 microcontroller, an e-paper display -HAT, and three buttons: yellow, blue, and white for turning the page backwards, -forwards, and putting the device to sleep, respectively. The prototype does not -store books on the microcontroller. It streams books from a server over HTTP. -The e-reader employs RTC memory to record the reading progress between -sessions. - -The most formidable challenge when trying to build an e-reader with an ESP32 is -its limited memory and storage. My ESP-WROOM-32 has a total of 512KB of SRAM -and 4MB of flash memory, which the freeRTOS, ESP-IDF, and the e-reader -application must share. To put things into perspective, a Kindle Paperwhite has -at least 256MB of memory and 8GB of storage. That is 500x more memory than what -I'd have to work with. - -Despite its size, as microcontrollers go, ESP32 is a powerful system-on-a-chip -with a 160MHz dual-core processor and integrated WiFi. So, I thought it’d be -amusing to embrace the constraints and build my e-reader using a $5 MCU and the -power of C programming. - -## The file format - -The file format dictates the complexity of the embedded software. So, I’ll -begin there. The e-reader works by downloading and rendering a rasterized -monochrome image of a page (a .ebm file). - -The EBM file contains a series of bitmaps, one for each page of the book. The -dimensions of each bitmap are equal to the size of the display. Each byte of -the bitmap encodes information for rendering eight pixels. For my display, -which has a resolution of 480x800, the bitmaps are laid out along 48KB -boundaries. This simple file format lends well to HTTP streaming, which is its -main advantage, as we will soon see. - -The pdftoebm.py script enclosed in the tarball at the end of the page converts -PDF documents to EBM files. - -## How does it work? - -As the e-reader has no storage, it can't store books locally. Instead, it -downloads pages of the EBM file over HTTP from the location pointed to by the -`EBM_ARCH_URL` setting in the Kconfig.projbuild file on demand. To read a -different book, we have to replace the old file with the new one or change the -`EBM_ARCH_URL` value. The latter requires us to recompile the embedded -software. - -Upon powering up, the e-reader checks the reading progress stored in the RTC -memory. It then downloads three pages (current, previous, and next) to a -circular buffer in DMA-capable memory. When the user turns a page by pressing a -button, one of the microprocessor's two cores transfers it from the buffer to -the display over a Serial Peripheral Interface (SPI). The other downloads a new -page in the background. I used the ESP-IDF task API to schedule the two tasks -on different cores of the multicore processor to make the reader more -responsive. - -I designed the EBM format with HTTP streaming in mind. Since the pages are laid -out in the EBM file along predictable boundaries, the e-reader can request -pages by specifying the offset and the chunk size in the HTTP Range header. Any -web server will process this request without custom logic. - -## Epilogue - -My fascination with e-paper began back in 2017, when I was tasked with -installing a few displays in a car park. Having no idea how they worked, I -remember watching the languid screens refresh like a Muggle witnessing magic. -This project was born out of that enduring curiosity and love of e-paper -technology. - -Why did I go to the trouble of building a rudimentary e-reader when I could -easily buy a more capable commercial e-reader? First of all, it's to prove to -myself that I can. More importantly, there's a quiet satisfaction to reading on -hardware you built yourself. You are no longer the powerless observer watching -the magic happen from the sidelines. You become the wizard who makes the -invisible particles swirl into form by whispering C to them. There's only one -way to experience that. - -Files: [source.tar.gz](source.tar.gz) +ESP-WROOM-32, 7.5" Waveshare e-paper display, three buttons (prev/next/sleep). + +No local storage—streams books over HTTP. RTC memory tracks reading progress +between sessions. + +ESP32: 512KB SRAM, 4MB flash (shared with FreeRTOS, ESP-IDF). Not enough to +store books. Stream from webserver instead. + +Custom EBM file format. Rasterized monochrome bitmaps, one per page (480x800). +One byte = 8 pixels. Pages on 48KB boundaries—HTTP Range requests work without +server logic. pdftoebm.py converts PDFs. + +Circular buffer holds 3 pages (prev/current/next)--page table. Single-threaded +approach too slow—user input, SPI, and HTTP on one core causes lag. + +Optimizations: pin the GPIO task (responding to user input and updating +display) to one core, pin the HTTP task to the other core. + +Better, but system unresponsive during screen updates. Made SPI transfer async: + +``` +void epd_draw_async(const uint8_t *buf, size_t n) +{ + static spi_transaction_t t[3]; + + memset(&t[0], 0, sizeof(t[0])); + t[0].length = 8; + t[0].tx_data[0] = 0x13; + t[0].user = (void*) 0; + t[0].flags = SPI_TRANS_USE_TXDATA; + + memset(&t[1], 0, sizeof(t[1])); + t[1].length = 8 * n; + t[1].tx_buffer = buf; + t[1].user = (void*) 1; + + memset(&t[2], 0, sizeof(t[2])); + t[2].length = 8; + t[2].tx_data[0] = 0x12; + t[2].user = (void*) 0; + t[2].flags = SPI_TRANS_USE_TXDATA; + + for (int i = 0; i < 3; i++) + spi_device_queue_trans(spi, &t[i], portMAX_DELAY); +} +``` + +Much better. Squeeze a few more cycles by moving SPI buffer to DMA. + +Can't think of anything else. + +Outcome: Works but limited. Led to [Etlas](../etlas/). + +Commit: +[7f691c4](https://git.asciimx.com/esp32-e-reader/commit/?id=7f691c46093933b67aab466c0ca582ace8ab73d4) -- cgit v1.2.3