This is a tiny project to interface an eInk Paper Display to a microcontroller. Nothing really new here, but the goal was to discover EPD technology and build a useful device from it.
The objective is to display some information collected from HomeAssistant (from local home network) and display them on this e-Ink Paper display. It's very close to TRMNL but with the fun of building it for my own purpose.
Three steps for this project.
- use an ESP WROOM (with ESP32 DevKit V4 board) to develop the display side of an image retrieved from a web server
- write a tiny Python server that fetches data from HomeAssistant and build a image suitable for the eInk paper display (EPD)
- design custom hardware (electronics and housing)
The third step is definitely optional but is the whole fun of DIY.
The 7.5" display is available from two sources. I guess they both use the same OEM.
- ThinkINK 7.5" Tri-Color EPD is from Adafruit. Here is the product datasheet.
- 7.5 Inch Tri-Color E Ink / Electronic Paper Display, Low Power – GDEY075Z08 from GoodDisplay. Here is the product datasheet.
Both based on a UC8179 driver chip.
Since it comes with FPC24 0.5mm ribbon only, I had to find an adapter. The DESPI-C02 from GoodDisplay is perfect. The adapter uses the exact same components that are described in the product application note. From which we can also figure out that the RESE value is 0.47ohms and the DESPI-C02 switch must be positionned accordingly.
It seems that FPC24 EPD pinout is quite standard, the front electronics that can't be included in the display is in charge of communicating with host and also of driving the DC/DC boost regulator. These displays need +/-20V voltages, our main power supply is 3.3V.
The wiring between the Adapter and the ESP32-DevKitC V4 board is the following.
Besides using SPI pins, 3.3V and Ground, three other GPIO pins have been asigned to BUSY, D/C and RESET signals.
The UC8179 uses SPI and I2C but only SPI signals go through the FPC. SPI has many possible configurations and some basics understandings are needed to have the correct setup.
- UC8179 is slave and ESP32 is master.
- 4 wire connection. One wire is used to distinguish Commands from Data over SPI communication (D/C). Both will be sent as 8bits per word.
- No dual SPI mode.
- Only MOSI (SDA). We won't be able to read values from UC8179 (unless MISO loops back into MOSI, I didn't try).
From datasheet we can figure out CPOL/CPHA are SPI Mode 0 configured.
4Mhz for the clock seems ok since datasheet refers to a 10MHz maximum. SPI throughput is really not the problem with EPDs.
Well it's quite complicated, put it in short, panels encapsulate ink bubbles that are "oriented" with series of voltage values (called waveforms). It takes a long time to get something on a screen. This model takes approximately 17 sec for a full refresh. Once done, you can turn power off, drawing will remain.
An insightful article can be found here.
This panel has Black & White layer and a Red one. White is more gray than full white. You have to program the two layers independently and then refresh the whole display. You'll get a mix of Black, Grey and Red colors.
For this little device, we just use the BW layer.
Program runs on a ESP32-WROOM-32E-N4:
esptool v5.2.0
Connected to ESP32 on /dev/ttyUSB0:
Chip type: ESP32-D0WDQ6 (revision v1.0)
Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz, Vref calibration in eFuse, Coding Scheme None
Crystal frequency: 40MHz
MAC: 30:ae:a4:eb:08:54
Stub flasher running.
Flash Memory Information:
=========================
Manufacturer: 20
Device: 4016
Detected flash size: 4MB
Flash voltage set by a strapping pin: 3.3V
It is recommended to setup the display server prior to testing this part.
You need Espressif IDF framework (didn't choose Arduino, the beginner way).
You need to configure the project with your own local Wifi setup and server. For that, just run:
$ source ~/.espressif/tools/activate_idf_v6.0.sh
(venv)$ cd esp32
(venv)$ idf.py menuconfig
Select the EPD Wifi Connection Configuration menu entry and fill in the needed parameters according to your needs. You have to provide:
- SSID of your local wifi network
- Password to your Wifi network
- IP address of the Display Server
- TCP Port of the Display server
By default, logs are disabled during boot and at application level too. You can enable them again for debugging purpose:
- (Top) → Bootloader config → Log → Bootloader log verbosity: check Info
- (Top) → Component config → Log → Log Level → Default log verbosity: check Info
The sdkconfig file will be generated and it allows to compile, flash, run the project to your device and monitor output with:
(venv)$ idf.py build flash monitor
...
It will try to fetch hash and image from server. If successful the display will be refreshed.
As you can guess the display doesn't draw much but a remotely built PBM (P4) image. The haPanel.py python script allows to get some information from a HomeAssistant server and create the PBM image. You can adapt the script to whatever you want since it currently contains useful stuff for me only.
Script needs some parameters and secrets that are defined in params.py file. Only params_example.py is provided here from which you derive your personal parameters:
# Here are defined the needed variables for haPanel.py script.
#
# Where:
# HA_TOKEN is a Long-Live token generated from your HA environment
# HA_WEB_URL is the base URL for accessing your HA server
# PANEL_WEB_PORT is the TCP port on which the HaPanel web server is available
# PANEL_WEB_DIRECTORY is where files are to be created and served. Do not use '.' for obvious security reason.
# PANEL_UPDATE_INTERVAL is the amount of seconds between updates
#
# This file MUST be renamed to params.py to be properly read by haPanel
#
HA_TOKEN="xxxxxxxxxxxxxxxxxxx"
HA_WEB_URL="http://192.168.xx.yy:8123"
PANEL_WEB_PORT=8080
PANEL_WEB_DIRECTORY='public'
PANEL_UPDATE_INTERVAL=1800If you want to query your HomeAssistant server, you'd need a Long-Live Token (the HA_TOKEN parameter). Check-out Home Assistant documentation for that.
The script has some dependencies that should be resolved with:
$ pip install -r requirements.txt
(It is recommended to use venv instead of system wide installation)
You can run it in test mode with static content to check if this works with the ESP32 device. It will simply create a simple image with current date as text and serve it (via http://localhost:8080):
$ ./haPanel.py --test
Now, edit the haPanel.py script for your own needs (using Home Assistant or not). Do whatever you want. With the little hardware requirements used so far you can stop here and mess about. If you're interested in the dedicated PCB and the humble housing used here, please checkout the rest of this documentation.
Enjoy!
For a custom hardware, the main goal is to make the device run on a few batteries, preferably 3xAA ou 3xAAA. So consumption is to be estimated and confirmed. ESP32 is Sleep Mode is announced at 10µA, it's impossible to measure this on the DevKit because the LDO will drain 3.3V to led and USB-Serial chip. We have to trust datasheet. Actually, the ESP32 does consume 10µA. Good. The EPD datasheet is lying, at least it consumes 30µA in deep sleep mode. Fair enough. Whene ESP32 fires up Wifi, the consumption is really high, it peaks at 500mA and has a mean of 250mA during connection and communication. The consumption breakdown is:
- 500mA for 5 seconds. High but to maximize the consumption estimation when connecting and retrieving data from display server.
- 30mA for 17 seconds. ESP32 + EPD during refresh.
- 50µA for 3572 secs. The sleeping devices consumption during a total of one hour. Device is waken up every hour.
This gives an average 0.88mA consumption. 3 batteries of AAA type are 3750mAh. This provides 4261 hours of autonomy, that are approximately 8 months. A target of one year would have been better. Anyway, the current design will be based on this estimation. Switching to stronger batteries is an option (AA).
From batteries to 3.3V I decided to go buck. Two decent DC/DC regulators came out of the research. 3.3V LDO with low quiscient current and more than 500mA of output current:
- RT9080-33GJ5 with 600mA max output
- TLV75733PDBVR (Ti) with 1A max ouput
Both have same footprint and pinout, so totally interchangeable.
It is based on the application note from the manufacturer. Which happens to be the same components as the DESPI-CO2 adapter. Don't mess with the inductor. It must be chosen carefully (Isat > 1.2A, DCR<0.5ohm, 10µH).
The whole device is powered by either LiPo batteries (no charger) or classic batteries. Depending of future autonmy tests, design will be updated. In both cases, a LDO is used here.
Schematics here
PCB designed with KiCAD 7.
Make sure to insert EPD ribbon the right way. Pin numbers must match:
Prior to flashing, using a lab power supply is better. Once flasing is done, connect to battery pack.
When electronics assembly is done, flashing the ESP32 is needed to get everything started.
This part can be a nightmare with ESP32. Mileage varies a lot.
You need a USB-Serial-TTL3.3V adapter (5V versions are not recommended). Then wiring the adapter to the board is achieved this way:
Follow these steps (assuming /dev/ttyUSB0 is the path to the USB-Serial adpater):
- Device MUST NOT be powered
- Start picocom (or equivalent) on the command line with:
$ picocom -b 115200 /dev/ttyUSB0
- Powerup the device, output should be like:
rst:0x1 (POWERON_RESET),boot:0x3 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download
The ESP bootloader is ready to receive code for flashing. Quit picocom (with Ctrl^A Ctrl^X).
- Enter esp-idf venv if you're not already in it and type:
(venv)$ idf.py build
...
(venv)$ cd build
(venv)$ esptool --chip esp32 -p /dev/ttyUSB0 -b 115200 --before=no-reset --after=no-reset write-flash --flash-mode dio --flash-freq 40m --flash-size 2MB 0x1000 bootloader/bootloader.bin 0x8000 partition_table/partition-table.bin 0x10000 epd-uc8179.bin
Connected to ESP32 on /dev/ttyUSB0:
Chip type: ESP32-D0WDQ6 (revision v1.0)
Features: Wi-Fi, BT, Dual Core + LP Core, 240MHz, Vref calibration in eFuse, Coding Scheme None
Crystal frequency: 40MHz
MAC: 30:ae:a4:eb:08:54
Stub flasher running.
Configuring flash size...
SHA digest in image updated.
Flash will be erased from 0x00001000 to 0x00007fff...
Wrote 26128 bytes (16570 compressed) at 0x00001000 in 0.8 seconds (260.4 kbit/s).
Hash of data verified.
Flash will be erased from 0x00008000 to 0x00008fff...
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.1 seconds (396.1 kbit/s).
Hash of data verified.
Flash will be erased from 0x00010000 to 0x000d3fff...
Wrote 800736 bytes (523268 compressed) at 0x00010000 in 12.4 seconds (516.8 kbit/s).
Hash of data verified.
After flashing, you may want to observe program output (monitor). Just disconnect BOOT wire from ground, leave it up.
Launch picocom again. Power down and up the device to make it reset. Watch program output on serial line and check everything is ok (Wifi connection mostly, then requests to display server). Display will refresh.
In some cases esptool refuses to connect to ESP32. The following worked for me:
- Power up the device with the BOOT wire connected to ground
- Start esptool this way:
(venv)$ esptool --chip esp32 -p /dev/ttyUSB0 -b 115200 --connect-attempts 0 --before=no-reset --after=no-reset write-flash --flash-mode dio --flash-freq 40m --flash-size 2MB 0x1000 bootloader/bootloader.bin 0x8000 partition_table/partition-table.bin 0x10000 epd-uc8179.bin
- Manually reset the board by bringing reset pad to ground and let it up (to create a reset signal).
Esptool should start flashing, slowly, but surely. Retry if flashing fails.
Here is a humble housing for this display, made with FreeCAD.







