An AMOLED watch display driver
Part of porting the open-source ZSWatch to a Waveshare ESP32-S3 AMOLED board on upstream Zephyr: the board itself, its production test, and a display driver rebuilt around DMA.
Goal
ZSWatch is an open-source smartwatch built around a Nordic nRF5340. The goal was to run it on a cheaper off-the-shelf ESP32-S3 board with a 410×502 AMOLED panel, on upstream Zephyr, without breaking the original nRF builds.
What I built
Board support, and a rule for the port
Device trees, pin control and configuration for the new board, and one rule that kept the port clean: the nRF sources stay untouched, and the new board gets sibling backends selected through Kconfig and the device tree.
Production test
The factory test flow, brought up check by check: I²C scan, display, power-management chip, battery, IMU, touch, real-time clock, audio codec and microphone, microSD card, Wi-Fi scan and BLE advertising.
The display driver
The CO5300 panel needs a one-line command and address followed by four-line pixel data in a single transaction, which Zephyr's SPI driver could not produce. I went from bit-banged GPIO to the ESP32-S3's hardware QSPI through Espressif's HAL, then fed it with GDMA in 4 KB chunks, and finally waited on the DMA-complete interrupt instead of spinning, so other threads run during transfers.
The rest of the watch
Brightness and vibration backends for the new hardware, a battery backend on the AXP2101 power chip, the Bluetooth and Gadgetbridge layer moved to upstream Zephyr's stack, and UI screens laid out for the 410×502 panel.
Result
Bit-banged, a full screen took 200–400 ms. With hardware QSPI the per-second redraw took 2.2 ms, and with DMA 0.9 ms, at about 14 MB/s and with the CPU free during transfers. All 16 production-test checks pass on the board.
What I learned
- Reading a display controller's datasheet closely enough to get its command protocol exactly right.
- ESP32-S3 peripherals below the RTOS: QSPI, GDMA and the GPIO matrix, through Espressif's HAL alongside Zephyr drivers.
- Porting a large Zephyr application to a new board without forking it.
- Bringing up a board one peripheral at a time, with a production test as the checklist.