summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2025-12-27 18:58:31 +0800
committerSadeep Madurange <sadeep@asciimx.com>2025-12-27 21:02:36 +0800
commit3e6ecf82e6f95490ac6b6d73833cc2ab17ef49a2 (patch)
tree9bf5d5284c4c88c94c20f37dcc2892a18db4d405
parent15ab870972f3fbeeec24ae70f1eb2ad19bc0be11 (diff)
downloadwww-3e6ecf82e6f95490ac6b6d73833cc2ab17ef49a2.tar.gz
E-reader & bumblebee.
-rw-r--r--_log/bumblebee.md2
-rw-r--r--_log/e-reader.md139
-rw-r--r--_site/feed.xml2
-rw-r--r--_site/index.html2
-rw-r--r--_site/log/bumblebee/index.html2
-rw-r--r--_site/log/e-reader/index.html141
-rw-r--r--_site/log/index.html2
-rw-r--r--_site/posts.xml2
-rw-r--r--_site/projects/index.html4
9 files changed, 123 insertions, 173 deletions
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 <a href="https://www.waveshare.com/"
-class="external" target="_blank" rel="noopener noreferrer">Waveshare</a>
-e-paper display built with the intention of learning about e-paper displays.
+First project with e-paper displays and ESP32.
<video style="max-width:100%;" controls="" poster="poster.png">
<source src="ereader.mp4" type="video/mp4">
</video>
-## 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)
diff --git a/_site/feed.xml b/_site/feed.xml
index eee3cb4..2840acb 100644
--- a/_site/feed.xml
+++ b/_site/feed.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-27T18:35:47+08:00</updated><id>/feed.xml</id><title type="html">ASCIIMX | Log</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">Matrix Rain: 2025 refactor</title><link href="/log/matrix-digital-rain/" rel="alternate" type="text/html" title="Matrix Rain: 2025 refactor" /><published>2025-12-21T00:00:00+08:00</published><updated>2025-12-21T00:00:00+08:00</updated><id>/log/matrix-digital-rain</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[The 2022 version worked but had some loose ends. Unicode support was inflexible–couldn’t mix ASCII with Katakana; Phosphor decay was stored in a separate array when it should’ve been packed with RGB; Code was harder to read than it needed to be.]]></summary></entry><entry><title type="html">Fingerprint door lock (LP)</title><link href="/log/fpm-door-lock-lp/" rel="alternate" type="text/html" title="Fingerprint door lock (LP)" /><published>2025-08-18T00:00:00+08:00</published><updated>2025-08-18T00:00:00+08:00</updated><id>/log/fpm-door-lock-lp</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Second iteration of the RF door lock. Old version worked but drew too much quiescent current. Sensor and servo pulled 13.8mA and 4.6mA idle. Linear regulators were a disaster. Battery didn’t last 24 hours.]]></summary></entry><entry><title type="html">High-side MOSFET switching</title><link href="/log/mosfet-switches/" rel="alternate" type="text/html" title="High-side MOSFET switching" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>/log/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Needed low-power switching for the fingerprint door lock. Servo and FPM draw high quiescent current–had to cut power electronically during sleep. MOSFETs can do this.]]></summary></entry><entry><title type="html">ATmega328P at 3.3V and 5V</title><link href="/log/arduino-uno/" rel="alternate" type="text/html" title="ATmega328P at 3.3V and 5V" /><published>2025-06-10T00:00:00+08:00</published><updated>2025-06-10T00:00:00+08:00</updated><id>/log/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Quick reference for wiring ATmega328P ICs at 5V and 3.3V. 5V uses 16MHz crystal, 3.3V uses 8MHz.]]></summary></entry><entry><title type="html">Fingerprint door lock (RF)</title><link href="/log/fpm-door-lock-rf/" rel="alternate" type="text/html" title="Fingerprint door lock (RF)" /><published>2025-06-05T00:00:00+08:00</published><updated>2025-06-05T00:00:00+08:00</updated><id>/log/fpm-door-lock-rf</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Wanted to unlock door with fingerprint, wirelessly to avoid drilling.]]></summary></entry><entry><title type="html">Bumblebee: browser automation</title><link href="/log/bumblebee/" rel="alternate" type="text/html" title="Bumblebee: browser automation" /><published>2025-04-02T00:00:00+08:00</published><updated>2025-04-02T00:00:00+08:00</updated><id>/log/bumblebee</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Built with Andy Zhang for an employer. Tool to automate web scraping script generation.]]></summary></entry><entry><title type="html">ATSAM3X8E bare-metal programming</title><link href="/log/arduino-due/" rel="alternate" type="text/html" title="ATSAM3X8E bare-metal programming" /><published>2024-09-16T00:00:00+08:00</published><updated>2024-09-16T00:00:00+08:00</updated><id>/log/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Notes on programming ATSAM3X8E chips (Arduino Due) without bootloader. Tested on OpenBSD.]]></summary></entry><entry><title type="html">Etlas: e-paper dashboard</title><link href="/log/etlas/" rel="alternate" type="text/html" title="Etlas: e-paper dashboard" /><published>2024-09-05T00:00:00+08:00</published><updated>2024-09-05T00:00:00+08:00</updated><id>/log/etlas</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.]]></summary></entry><entry><title type="html">Experimental e-reader</title><link href="/log/e-reader/" rel="alternate" type="text/html" title="Experimental e-reader" /><published>2023-10-24T00:00:00+08:00</published><updated>2023-10-24T00:00:00+08:00</updated><id>/log/e-reader</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[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.]]></summary></entry><entry><title type="html">Neo4J A* search</title><link href="/log/neo4j-a-star-search/" rel="alternate" type="text/html" title="Neo4J A* search" /><published>2018-03-06T00:00:00+08:00</published><updated>2018-03-06T00:00:00+08:00</updated><id>/log/neo4j-a-star-search</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Back in 2018, we used Neo4J graph database to track the movement of marine vessels. We were interested in the shortest path a ship could take through a network of about 13,000 route points. Graph theoretic algorithms provide optimal solutions to such problems, and the set of route points lends itself well to graph-based modelling.]]></summary></entry></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-27T21:02:00+08:00</updated><id>/feed.xml</id><title type="html">ASCIIMX | Log</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">Matrix Rain: 2025 refactor</title><link href="/log/matrix-digital-rain/" rel="alternate" type="text/html" title="Matrix Rain: 2025 refactor" /><published>2025-12-21T00:00:00+08:00</published><updated>2025-12-21T00:00:00+08:00</updated><id>/log/matrix-digital-rain</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[The 2022 version worked but had some loose ends. Unicode support was inflexible–couldn’t mix ASCII with Katakana; Phosphor decay was stored in a separate array when it should’ve been packed with RGB; Code was harder to read than it needed to be.]]></summary></entry><entry><title type="html">Fingerprint door lock (LP)</title><link href="/log/fpm-door-lock-lp/" rel="alternate" type="text/html" title="Fingerprint door lock (LP)" /><published>2025-08-18T00:00:00+08:00</published><updated>2025-08-18T00:00:00+08:00</updated><id>/log/fpm-door-lock-lp</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Second iteration of the RF door lock. Old version worked but drew too much quiescent current. Sensor and servo pulled 13.8mA and 4.6mA idle. Linear regulators were a disaster. Battery didn’t last 24 hours.]]></summary></entry><entry><title type="html">High-side MOSFET switching</title><link href="/log/mosfet-switches/" rel="alternate" type="text/html" title="High-side MOSFET switching" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>/log/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Needed low-power switching for the fingerprint door lock. Servo and FPM draw high quiescent current–had to cut power electronically during sleep. MOSFETs can do this.]]></summary></entry><entry><title type="html">ATmega328P at 3.3V and 5V</title><link href="/log/arduino-uno/" rel="alternate" type="text/html" title="ATmega328P at 3.3V and 5V" /><published>2025-06-10T00:00:00+08:00</published><updated>2025-06-10T00:00:00+08:00</updated><id>/log/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Quick reference for wiring ATmega328P ICs at 5V and 3.3V. 5V uses 16MHz crystal, 3.3V uses 8MHz.]]></summary></entry><entry><title type="html">Fingerprint door lock (RF)</title><link href="/log/fpm-door-lock-rf/" rel="alternate" type="text/html" title="Fingerprint door lock (RF)" /><published>2025-06-05T00:00:00+08:00</published><updated>2025-06-05T00:00:00+08:00</updated><id>/log/fpm-door-lock-rf</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Wanted to unlock door with fingerprint, wirelessly to avoid drilling.]]></summary></entry><entry><title type="html">Bumblebee: browser automation</title><link href="/log/bumblebee/" rel="alternate" type="text/html" title="Bumblebee: browser automation" /><published>2025-04-02T00:00:00+08:00</published><updated>2025-04-02T00:00:00+08:00</updated><id>/log/bumblebee</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Built with Andy Zhang for an employer. Tool to automate web scraping script generation.]]></summary></entry><entry><title type="html">ATSAM3X8E bare-metal programming</title><link href="/log/arduino-due/" rel="alternate" type="text/html" title="ATSAM3X8E bare-metal programming" /><published>2024-09-16T00:00:00+08:00</published><updated>2024-09-16T00:00:00+08:00</updated><id>/log/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Notes on programming ATSAM3X8E chips (Arduino Due) without bootloader. Tested on OpenBSD.]]></summary></entry><entry><title type="html">Etlas: e-paper dashboard</title><link href="/log/etlas/" rel="alternate" type="text/html" title="Etlas: e-paper dashboard" /><published>2024-09-05T00:00:00+08:00</published><updated>2024-09-05T00:00:00+08:00</updated><id>/log/etlas</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU D1, featuring a 7.5-inch Waveshare e-paper display and a DHT22 sensor module.]]></summary></entry><entry><title type="html">ESP32 e-reader prototype</title><link href="/log/e-reader/" rel="alternate" type="text/html" title="ESP32 e-reader prototype" /><published>2023-10-24T00:00:00+08:00</published><updated>2023-10-24T00:00:00+08:00</updated><id>/log/e-reader</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[First project with e-paper displays and ESP32.]]></summary></entry><entry><title type="html">Neo4J A* search</title><link href="/log/neo4j-a-star-search/" rel="alternate" type="text/html" title="Neo4J A* search" /><published>2018-03-06T00:00:00+08:00</published><updated>2018-03-06T00:00:00+08:00</updated><id>/log/neo4j-a-star-search</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Back in 2018, we used Neo4J graph database to track the movement of marine vessels. We were interested in the shortest path a ship could take through a network of about 13,000 route points. Graph theoretic algorithms provide optimal solutions to such problems, and the set of route points lends itself well to graph-based modelling.]]></summary></entry></feed> \ No newline at end of file
diff --git a/_site/index.html b/_site/index.html
index b544a60..24e96cf 100644
--- a/_site/index.html
+++ b/_site/index.html
@@ -160,7 +160,7 @@
<tr>
<td class="posts-td posts-td-link">
- <a href="/log/e-reader/" class="link-decor-none">Experimental e-reader</a>
+ <a href="/log/e-reader/" class="link-decor-none">ESP32 e-reader prototype</a>
</td>
<td class="posts-td posts-td-time">
<span class="post-meta">
diff --git a/_site/log/bumblebee/index.html b/_site/log/bumblebee/index.html
index 2aebc2f..0962a27 100644
--- a/_site/log/bumblebee/index.html
+++ b/_site/log/bumblebee/index.html
@@ -74,7 +74,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.</p>
-<p>Problem: manual overriding via Scintilla editor mid-session causes the code
+<p>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.</p>
<p>Note to self: need to rethink the event/text list data structures in the
diff --git a/_site/log/e-reader/index.html b/_site/log/e-reader/index.html
index 3d61417..3eef05d 100644
--- a/_site/log/e-reader/index.html
+++ b/_site/log/e-reader/index.html
@@ -2,12 +2,12 @@
<html>
<head>
<meta charset="utf-8">
- <title>Experimental e-reader</title>
+ <title>ESP32 e-reader prototype</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title>Experimental e-reader</title>
+ <title>ESP32 e-reader prototype</title>
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/css/skeleton.css">
</head>
@@ -41,94 +41,69 @@
<main>
<div class="container">
<div class="container-2">
- <h2 class="center" id="title">EXPERIMENTAL E-READER</h2>
+ <h2 class="center" id="title">ESP32 E-READER PROTOTYPE</h2>
<h6 class="center">24 OCTOBER 2023</h5>
<br>
- <div class="twocol justify"><p>This project features an experimental e-reader powered by an ESP-WROOM-32
-development board and a 7.5-inch <a href="https://www.waveshare.com/" class="external" target="_blank" rel="noopener noreferrer">Waveshare</a>
-e-paper display built with the intention of learning about e-paper displays.</p>
+ <div class="twocol justify"><p>First project with e-paper displays and ESP32.</p>
<video style="max-width:100%;" controls="" poster="poster.png">
<source src="ereader.mp4" type="video/mp4" />
</video>
-<h2 id="introduction">Introduction</h2>
-
-<p>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.</p>
-
-<p>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.</p>
-
-<p>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.</p>
-
-<h2 id="the-file-format">The file format</h2>
-
-<p>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).</p>
-
-<p>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.</p>
-
-<p>The pdftoebm.py script enclosed in the tarball at the end of the page converts
-PDF documents to EBM files.</p>
-
-<h2 id="how-does-it-work">How does it work?</h2>
-
-<p>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
-<code class="language-plaintext highlighter-rouge">EBM_ARCH_URL</code> 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
-<code class="language-plaintext highlighter-rouge">EBM_ARCH_URL</code> value. The latter requires us to recompile the embedded
-software.</p>
-
-<p>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.</p>
-
-<p>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.</p>
-
-<h2 id="epilogue">Epilogue</h2>
-
-<p>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.</p>
-
-<p>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.</p>
-
-<p>Files: <a href="source.tar.gz">source.tar.gz</a></p>
+<p>ESP-WROOM-32, 7.5” Waveshare e-paper display, three buttons (prev/next/sleep).</p>
+
+<p>No local storage—streams books over HTTP. RTC memory tracks reading progress
+between sessions.</p>
+
+<p>ESP32: 512KB SRAM, 4MB flash (shared with FreeRTOS, ESP-IDF). Not enough to
+store books. Stream from webserver instead.</p>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>Optimizations: pin the GPIO task (responding to user input and updating
+display) to one core, pin the HTTP task to the other core.</p>
+
+<p>Better, but system unresponsive during screen updates. Made SPI transfer async:</p>
+
+<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void epd_draw_async(const uint8_t *buf, size_t n)
+{
+ static spi_transaction_t t[3];
+
+ memset(&amp;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(&amp;t[1], 0, sizeof(t[1]));
+ t[1].length = 8 * n;
+ t[1].tx_buffer = buf;
+ t[1].user = (void*) 1;
+
+ memset(&amp;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 &lt; 3; i++)
+ spi_device_queue_trans(spi, &amp;t[i], portMAX_DELAY);
+}
+</code></pre></div></div>
+
+<p>Much better. Squeeze a few more cycles by moving SPI buffer to DMA.</p>
+
+<p>Can’t think of anything else.</p>
+
+<p>Outcome: Works but limited. Led to <a href="../etlas/">Etlas</a>.</p>
+
+<p>Commit:
+<a href="https://git.asciimx.com/esp32-e-reader/commit/?id=7f691c46093933b67aab466c0ca582ace8ab73d4">7f691c4</a></p>
</div>
<p class="post-author right">by W. D. Sadeep Madurange</p>
</div>
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 @@
<tr>
<td class="posts-td posts-td-link">
- <a href="/log/e-reader/" class="link-decor-none">Experimental e-reader</a>
+ <a href="/log/e-reader/" class="link-decor-none">ESP32 e-reader prototype</a>
</td>
<td class="posts-td posts-td-time">
<span class="post-meta">
diff --git a/_site/posts.xml b/_site/posts.xml
index c92221f..38aac29 100644
--- a/_site/posts.xml
+++ b/_site/posts.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/posts.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-27T18:35:47+08:00</updated><id>/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/posts.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-27T21:02:00+08:00</updated><id>/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
diff --git a/_site/projects/index.html b/_site/projects/index.html
index 8891836..71a41e6 100644
--- a/_site/projects/index.html
+++ b/_site/projects/index.html
@@ -93,8 +93,8 @@
<td class="project-item">
<a href="../log/e-reader" class="link-decor-none">
- <img src="../log/e-reader/thumb_sm.png" alt="Experimental e-reader">
- <h5>Experimental e-reader</h5>
+ <img src="../log/e-reader/thumb_sm.png" alt="ESP32 e-reader prototype">
+ <h5>ESP32 e-reader prototype</h5>
</a>
</td>