EXPERIMENTAL E-READER
+ESP32 E-READER PROTOTYPE
24 OCTOBER 2023
- 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
+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.
+
+Commit:
+7f691c4
diff --git a/_site/log/index.html b/_site/log/index.html
index 645a982..59582a7 100644
--- a/_site/log/index.html
+++ b/_site/log/index.html
@@ -150,7 +150,7 @@
- Experimental e-reader
+ ESP32 e-reader prototype
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
+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.
+ +Commit: +7f691c4