The NM-EPD-420 is an ESP32-S3 based 4.2-inch tri-color E-ink development board. It integrates Wi-Fi / BLE, LoRa, audio codec, temperature/humidity sensing, SD card, battery management, and other common peripherals — a ready-to-use hardware platform for low-power info panels, weather stations, Meshtastic terminals, dashboards, and more.
The firmware in this repository is a factory test firmware. Its only purpose is to help the production line or a developer quickly verify that all on-board peripherals work after powering on. See Section 4 for the test sequence, workflow, and build instructions.
中文版本: README_cn.md
The NM-EPD-420 packs the core resources needed for E-ink projects onto a single board:
- MCU: ESP32-S3 (16 MB Flash, PSRAM, dual-core 240 MHz, 2.4 GHz Wi-Fi and BLE 5)
- Display: 4.2" 400×300 tri-color E-ink panel (black / white / red), model GDEY042Z98; black-and-white E-ink panel (GxEPD2_420_GYE042A87); and a four-color panel (black / white / red / yellow, GxEPD2_420c_GDEY0420F51). All three use the same pin definitions, but the controller protocol must match — select the variant according to the panel actually fitted.
- Audio: ES8311 audio codec + external Class-D amplifier + onboard speaker, plus an LMD4737 PDM digital microphone
- Environment sensor: AHT20 temperature/humidity sensor with independent power switch
- Wireless extension: Header for SX126x family LoRa modules (shares SPI bus with the SD card) (optional)
- Storage & I/O: µSD card slot, USER / BOOT buttons, battery ADC, JST 1.25 2-Pin battery connector
- Low-power design: Independent enable pins for each peripheral module allow complete power-down before ESP32 deep sleep
You can run one of the already-supported projects listed below, or treat the NM-EPD-420 as a general ESP32-S3 carrier and start your own application using the pin definitions in this document.
The NM-EPD-420 currently supports the tri-color GDEY042Z98 panel, the black-and-white GYE042A87 panel, and the four-color GDEY0420F51 panel. Their refresh characteristics are as follows:
-
GDEY042Z98 tri-color panel:
- SKU: NM-EPD-420
- Full refresh (black / white / red) takes approximately 10 seconds; partial refresh is not supported.
- The tri-color panel provides richer visuals for weather stations, dashboards, and similar applications, but refreshes more slowly and is best suited to mostly static content.
- The standard NM-EPD-420 tri-color version does not include a LoRa module and is intended for general desktop applications.
-
GYE042A87 black-and-white panel:
- SKU: NM-EPD-420-BW
- Full refresh (black / white) takes approximately 2-3 seconds, with partial refresh supported in approximately 1 second (download nm-epd420-bw-demo to try it).
- The black-and-white panel is recommended for applications that need faster content updates.
- The NM-EPD-420-BW version is suitable for fast-refresh applications and includes LoRa support by default, making it suitable for indoor desktop LoRa nodes.
-
GDEY0420F51 four-color panel:
- SKU: NM-EPD-420-4C
- Full refresh (black / white / red / yellow) takes approximately 25-30 seconds; partial refresh is not supported.
- The four-color panel is recommended for applications that need more colors and do not require frequent updates.
- Build environment:
nm-epd-420-4c; driver:GxEPD2_420c_GDEY0420F51(400×300, black / white / red / yellow). - Build with
pio run -e nm-epd-420-4c; T1 checks white, black, red, and yellow full-screen fills, then black/red/yellow text.
The NM-EPD-420 base board has also been validated with a 7.5-inch tri-color panel (GxEPD2_750c_Z08, GD7965 controller). The test confirmed base-board compatibility and driver feasibility — content refreshes and displays normally. Note: the 7.5-inch panel is not the standard panel shipped with NM-EPD-420 and is used for testing only. For real applications, adjust the case and mechanical structure as needed.
The following projects have been ported to the NM-EPD-420. Clone the linked branch and build:
| Project | Description | Adapted repository / branch |
|---|---|---|
| Meshtastic | Off-grid LoRa mesh messaging; shows node info, messages, and sensor data on the 4.2" E-ink panel (HT-RA62 module, SX1262) | RockBase-iot/meshtastic-firmware@nm-epd-420 |
| TRMNL-Firmware | TRMNL E-ink content framework; fetches images/content from a server on a schedule | RockBase-iot/trmnl-firmware |
| Biscuit | Multi-purpose tool / entertainment firmware for E-ink devices | RockBase-iot/biscuit@master |
| ESP32-weather-epd | Low-power weather station; fetches data from OpenWeatherMap and displays it on E-ink | RockBase-iot/esp32-weather-epd@main |
| ESP32-Dashboard | Multi-function E-ink dashboard: weather, air quality, indoor T/RH, Web config portal, etc. | RockBase-iot/ESP32-Dashboard@main |
| MeshCore | Lightweight, low-power LoRa gateway firmware | RockBase-iot/meshcore-firmware@nm-epd-420 |
| AgentDeck | Displays your AI coding agent on a physical screen | puritysb/AgentDeck |
| nm-epd420-bw-demo | NM-EPD420 black-and-white panel demo: partial refresh and fast refresh | RockBase-iot/nm-epd420-bw-demo |
| Inkstone-firmware | Local-push multi-color E-ink display framework | RockBase-iot/Inkstone-firmware |
Application firmware for the related projects is already available on RockBase IoT Web Flash.
┌───────────────────────────────────┐
│ ESP32-S3 │
│ (16 MB Flash, PSRAM, BLE/Wi-Fi) │
└───────────────────────────────────┘
SPI2 (FSPI) ──┐ SPI3 (HSPI) ──┐ I²C ──┐ I²S ──┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────┐ ┌────────┐
│ EPD GDEY042 │ │ µSD card │ │AHT20 │ │ ES8311 │ → PA → SPK
│ Z98 (3C) │ │ + LoRa │ └──────┘ │ codec │ ← DMIC LMD4737
│ 400×300 4.2"│ │ modem │ │ │
└──────────────┘ └──────────┘ └────────┘
┌────────┐ ┌────────┐ ┌──────────────┐
│ USER │ │ BOOT │ │ Battery ADC │
│ button │ │ button │ │ IO43+IO3 │
└────────┘ └────────┘ └──────────────┘
| Block | Part | Interface | Notes |
|---|---|---|---|
| MCU | ESP32-S3 (qio_opi PSRAM) | — | 16 MB flash, dual-core 240 MHz |
| E-paper | GDEY042Z98 4.2" 400×300 | SPI (FSPI) | Tri-color B/W/R, GxEPD2 driver |
| Codec | ES8311 | I²C 0x18 + I²S | DAC out → external PA → 8 Ω speaker |
| Mic | LMD4737 PDM DMIC | I²S (DMIC mode) | Sample rate 16 kHz |
| T/RH sensor | AHT20 | I²C 0x38 | Power-gated via PIN_TEMP_CTL |
| SD card | µSD | SPI (HSPI) | Shared bus with LoRa |
| LoRa modem | HT-RA62 SX1262 family | SPI (HSPI) | CS / RST / BUSY / DIO1 GPIOs |
| Buttons | USER, BOOT | GPIO | Active LOW, external pull-up |
| Audio amp | External Class-D | EN GPIO | Enabled by PIN_PA_CTRL HIGH |
Authoritative source: src/config.h.
| Group | Signal | GPIO | Direction | Notes |
|---|---|---|---|---|
| EPD (FSPI) | SCK | 2 | OUT | |
| MOSI | 1 | OUT | ||
| MISO | — | — | Panel pin NC (write-only) | |
| CS | 46 | OUT | ||
| DC | 4 | OUT | ||
| RST | 5 | OUT | ||
| BUSY | 6 | IN | HIGH while refresh in progress | |
| SD + LoRa (HSPI) | SCK | 9 | OUT | Shared bus |
| MOSI | 10 | OUT | ||
| MISO | 11 | IN | ||
| SD CS | 7 | OUT | ||
| LoRa NSS | 8 | OUT | ||
| LoRa RST | 12 | OUT | ||
| LoRa BUSY | 13 | IN | ||
| LoRa DIO1 | 14 | IN | Used by specific LoRa applications | |
| I²S (ES8311) | MCLK | 21 | OUT | 4.096 MHz (256 × 16 kHz) |
| BCLK | 15 | OUT | ||
| LRCK / WS | 17 | OUT | ||
| DOUT (ESP→DAC) | 18 | OUT | ES8311 DSDIN | |
| DIN (ADC→ESP) | 16 | IN | ES8311 ASDOUT | |
| I²C | SDA | 39 | I/O | AHT20 + ES8311 shared bus |
| SCL | 38 | OUT | ||
| TEMP_CTL | 40 | OUT | AHT20 power gate (HIGH = on) | |
| Audio | PA_CTRL | 41 | OUT | External amplifier enable |
| User I/O | USER button | 45 | IN | Active LOW, external pull-up |
| BOOT button | 0 | IN | Active LOW, RTC GPIO, external pull-up | |
| Module EN | LoRa EN | 47 | OUT | LoRa module power enable (HIGH = on) |
| Codec EN | 44 | OUT | ES8311 power enable (HIGH = on) | |
| ADC EN | 43 | OUT | Battery ADC circuit enable (HIGH = on) | |
| Battery ADC | BATT_ADC | 3 | IN | Battery voltage sense (resistor divider) |
Note: Applications may use different peripherals. Control unused peripherals through their enable pins to optimize power consumption.
The LoRa version includes an HT-RA62 module (SX1262) for Meshtastic, MeshCore, and other LoRa applications. The no-LoRa version does not include the module and is intended for general applications. The NM-EPD-420-BW version supports LoRa by default so LoRa applications can benefit from its faster display refresh.
- 3D case: STL files are in docs/case, including buttons, top plate, back cover, etc.; ready for direct 3D printing.
- Battery: A 3.7 V Li-Po battery with protection circuit, capacity ≥ 500 mAh, is recommended. The PCB has a JST 1.25 PH 2-Pin connector (red = positive, black = negative). Recommended size: 603030. Buy on AliExpress JST 1.25 2Pin 603030 600mAh
The NM-EPD-420 base board has been validated with a 7.5-inch tri-color panel (GxEPD2_750c_Z08, GD7965 controller), confirming base-board compatibility and driver feasibility. Test results show that content refreshes and displays normally.
The firmware in this repository exercises every on-board peripheral in a fixed sequence (T0…T11). It renders a dedicated screen for every step and lets the operator confirm / reject each step with the USER and BOOT buttons. At the end, a one-page summary screen lists every test as PASS / FAIL / SKIP.
A complete run typically takes ~3 min, dominated by EPD full-refresh time (~10 s per page on a 3-color panel).
┌───────────────────────────┐
power on ───► │ T0 Welcome │ press USER
└───────────────────────────┘
│
▼
┌───────────────────────────┐
│ T1 … T10 │
│ for each test: │
│ show screen │
│ run hardware │
│ USER = PASS / OK │
│ BOOT = FAIL │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
│ T11 Summary │ EPD hibernate → ESP32 deep sleep
└───────────────────────────┘
Buttons are debounced (5 × 10 ms samples) and gated against EPD BUSY so a press during a refresh cannot be consumed as a verdict for the next test.
Prerequisites: PlatformIO Core (command line) or VS Code + PlatformIO extension.
# In the repository root:
$env:IDF_GITHUB_ASSETS = "dl.espressif.cn/github_assets" # optional, China mirror
pio run # build
pio run --target upload --upload-port COM38 # flash
pio device monitor --baud 115200 # serial consoleThe first build downloads the ESP-IDF toolchain (~hundreds of MB) into %USERPROFILE%\.platformio. Subsequent builds take ~25 s.
Typical serial output during a run:
[FACTORY TEST] Board: NM-EPD-420
[FACTORY TEST] FW: v1.4.01
[FACTORY TEST] T0 - System startup OK
…
[FACTORY TEST] T1 START - EPD Display
[T1] Round 1/4 - Filling screen WHITE ...
[T1] BUSY self-check: sawHigh=1 highMs=2873
…
[FACTORY TEST] ===== SUMMARY =====
[FACTORY TEST] T1 EPD Display [PASS]
…
[FACTORY TEST] Overall: FACTORY_TEST=OK
This board is essentially a fully-featured ESP32-S3 carrier. To start your own project:
- Pick one of the already-supported projects from Section 2 and clone the corresponding branch, or create a fresh PlatformIO / Arduino project.
- Copy the pin configuration below into your project's
config.horplatformio.ini. For PlatformIO projects, see thecodesdirectory;nm_epd_420.jsonalready defines the board pin mapping and can be referenced directly. - Initialize the SPI / I²C / I²S buses as needed. Remember: EPD uses FSPI, SD + LoRa share HSPI.
- Drive the corresponding module-enable pin HIGH before using a peripheral, and LOW afterwards to save power.
- Use
esp_deep_sleep_start()or similar APIs for low-power operation.
- E-paper: Use the
zinggjm/GxEPD2library withGxEPD2_420c_GDEY042Z98for the default tri-color NM-EPD-420 andGxEPD2_420_GYE042A87for NM-EPD-420-BW. The four-color panel can useGxEPD2_420c_GDEY0420F51with thenm-epd-420-4cenvironment. CS=46, DC=4, RST=5, BUSY=6; the four-color driver uses active-low BUSY. - AHT20: Use
Adafruit AHTX0; I²C SDA=39, SCL=38; setPIN_TEMP_CTL(40)HIGH before reading. - ES8311 / speaker / microphone: I²C address 0x18, I²S pins as in the table above; drive
PIN_CODEC_EN(44)andPIN_PA_CTRL(41)HIGH before playback. - SD card: Use the
SDlibrary + HSPI (SCK=9, MOSI=10, MISO=11, CS=7). - LoRa: SX126x family on the shared HSPI bus; CS=8, RST=12, BUSY=13, DIO1=14; drive
PIN_LORA_EN(47)HIGH before use. - Battery ADC: Drive
PIN_ADC_EN(43)HIGH, read fromPIN_BATT_ADC(3), divide ratio is 2:1.
If you need to modify or extend the factory test firmware, the source is organized as follows:
src/
├── main.cpp ← Arduino entry; disables task WDT, calls runner.run()
├── test_runner.{h,cpp} ← T0/T11, button helpers, EPD pre-test resync, dispatch
├── config.h ← Pin map + feature switches (first-hand reference for your own development)
├── spi_buses.h ← Shared HSPI bus init for SD + LoRa
├── ui/
│ └── display_helper.h ← EPD wrapper + showWelcome / showTestScreen
└── tests/
├── test_t1_epd.h ← header-only test implementations
├── …
└── test_t10_lora.h
Design notes:
- Single-threaded, blocking. The factory line is human-paced; using
delay()and synchronous SPI/I²S keeps the code linear and easy to debug. - Tests are header-only. Each
test_tN_*.hexposesTestResult runTestTN(Display&, TestRunner&)and is included once fromtest_runner.cpp. No virtual dispatch. - EPD resync between tests.
TestRunner::_preTest()re-runs_epd.init(..., initial_power_on=true, ...)so peripheral tests that touch shared resources (Wi-Fi RF cal, HSPI for SD, …) cannot leave the EPD in a desynced state. - Button gating against EPD BUSY. All button reads are debounced (5 × 10 ms) and rejected while
PIN_EPD_BUSYis HIGH, so a press during the ~10 s full-window refresh cannot leak into the next verdict.
| Layer | Version |
|---|---|
| Build system | PlatformIO |
| Platform | pioarduino/platform-espressif32 54.03.21 |
| Framework | Arduino-ESP32 3.2.x (on top of ESP-IDF 5.4) |
| Language | C++17 |
Library deps (see platformio.ini):
| Library | Version | Used by |
|---|---|---|
zinggjm/GxEPD2 |
1.6.8 | EPD |
adafruit/Adafruit AHTX0 |
2.0.5 | T6 sensor |
adafruit/Adafruit BusIO |
1.17.4 | (dep) |
adafruit/Adafruit Unified Sensor |
1.1.15 | (dep) |
SPI, Wire, WiFi, SD |
3.2.1 | bundled |
Adafruit GFX Library |
1.12.6 | (dep) |
If you want to share your code with the community more quickly, check out the RockBase IoT ESPWebApps project. Develop your application following the existing framework and conventions, and users will be able to flash it online through RockBase IoT Web Flash.
NM-EPD-420/
├── README.md ← this file
├── README_cn.md ← Chinese version
├── platformio.ini ← PlatformIO build configuration
├── docs/ ← Design/debug notes, 3D case files
├── image/ ← Screen captures shown in this README
│ ├── T0.png T2.png T4.png T5.png T6.png T8.png T9.png T11.png
└── src/ ← All firmware sources
The first batch of NM-EPD-420 units went on sale as scheduled in August 2026 and is now available for order. You can purchase through the following channels:
- Added
NM-EPD-420-4Cfour-color E-ink support test; released Inkstone-firmware, supporting web preview and image push for multi-color devices; - Added 7.5-inch tri-color panel validation (GxEPD2_750c_Z08, GD7965 controller);
- Added support for the nm-epd420-bw-demo project, making it easy to test and verify partial refresh and fast refresh on the black-and-white
NM-EPD-420-BW.












