Driving the display on an STM32MP157F-DK2 with Elixir and Scenic

Getting a real, touchable user interface onto the DK2's 4-inch MIPI DSI panel from Elixir on Nerves, with no GPU — the display stack, host previewing, a drift-free timer, and the Scenic gotchas that compile cleanly and fail on the device.

The STM32MP157F-DK2 running the Pomodoro demo on its 4-inch DSI touchscreen
The STM32MP157F-DK2 running the Pomodoro demo on its 4-inch DSI touchscreen

The ST STM32MP157F-DK2 ships with a 4-inch 480×800 MIPI DSI panel and a Goodix capacitive touch controller bolted to the front of it. On my board they sat there doing nothing for four releases of my Nerves system — the kernel drivers were enabled, and nothing ever drew a pixel.

This series is about fixing that, from Elixir, with Scenic, and — as it turns out — without the Vivante GPU at all.

The demo is a Pomodoro timer: start, pause, reset, plus or minus a minute, a big countdown and a progress ring. Small enough to finish, real enough to prove the stack — touch in, timed redraw out, visible state change.

Board ST STM32MP157F-DK2 (the STM32MP157D-DK1 is the same idea with one core variant down)
CPU Dual Cortex-A7 @ 800 MHz, plus a Cortex-M4 @ 209 MHz
RAM 512 MB DDR3L
Display 4″ 480×800, MIPI DSI, Orisetech OTM8009A, backlight over DCS
Touch Goodix capacitive, on I²C, on the panel
GPU Vivante GC NanoUltra — unused, as it turns out

It picks up where the first STM32MP1 series left off. That one was about building the Nerves system: the boot chain, device trees, TF-A, U-Boot and twenty-five build errors. This one is about the thing left switched off at the end of it.

Part 1
9 min

The STM32MP157F-DK2 display stack, and the Vivante GPU I did not need

Between an Elixir process and a lit pixel on the DK2 there is an LTDC, a MIPI DSI host, an OTM8009A panel and a Goodix touch controller. I spent a day preparing to enable the Vivante GC NanoUltra, then read scenic_driver_local's source and found it renders in software to /dev/fb0.

Part 2
6 min

Previewing a Scenic UI on the host, with no screen and no board

An hour spent trying to open a window that was never going to be drawn, then the realisation that a Scenic graph is just data. Walking it and rendering to SVG found two layout bugs in a minute that had survived 49 tests and a review.

Part 3
5 min

A drift-free countdown on an STM32MP157F-DK2, tested in milliseconds

Counting down by subtracting the tick interval drifts on an embedded board. Deriving the remaining time from a monotonic deadline instead makes it drift-free by construction — and injecting the clock turns a 25-minute test into a map update. It also caught a real race.

Part 4
7 min

Scenic on Nerves: scenes, graphs, drivers, and four things that bit me

Scenes, graphs, components and drivers on an STM32MP157F-DK2 — then the four gotchas that compile cleanly and fail on the device: the required assets module, the child spec that is a plain list, the missing input: style that made every button inert, and text_align: :center not working.

Part 5
7 min

nerves_system_br: what it actually is, and where your OTP version is decided

An afternoon spent finding where a Nerves target's Erlang version is really decided — and discovering it is encoded in the filename of a patch inside a dependency I had never opened. Plus the Kconfig default that will silently move you to OTP 29 the moment you bump.