Arrivals is a Kotlin Multiplatform project for real-time public transit info. The most recent exploration was a Raspberry Pi driving an LCD. Now I’m going for character: a 128x32 dot-matrix board built from two chained LED panels, which feels like a miniature of the real thing.

If you’re interested in building this, the project README is comprehensive, so this post will be a quick run through the approach and gotchas.
Kotlin/Python bridge
Python is the standard language for talking to hardware via the Raspberry Pi’s GPIO headers. We need a way to connect the Kotlin app to Python LED display drivers.

Luckily I’d already built a CLI target, so the approach was:
- Add a
--jsonoutput flag to the CLI - Build Kotlin/Native CLI artifacts to run with no JVM overhead on the Raspberry Pi Zero 2’s 512MB of RAM
- Call the CLI from a Python rendering script and parse the JSON results
The Kotlin Multiplatform compiler doesn’t run on the Raspberry Pi’s linuxArm64 architecture, but the CLI binary can be cross-compiled from macOS or Linux x86 via the :cli:linkReleaseExecutableLinuxArm64 Gradle task.
Bitmap fonts
I got something working pretty quickly using a font bundled with the LED display samples. It looked… extremely disappointing. The next step was to reproduce the classic TfL font.

The LED matrix is a tiny 128x32 resolution, so we need a bitmap font where every pixel is fully on or off with no anti-aliasing. I used Claude Code to transcode a scalable TrueType reproduction of the London Underground dot matrix font into BDF, a 38-year-old font format. The resulting bitmap font has a height of 9px, which means we can just about squeeze in 3 rows.
Assembly
The build itself is very simple:
- Raspberry Pi (5 or Zero 2)
- Adafruit RGB Matrix Bonnet
- 2x 64x32 HUB75 LED panels
The challenge was connecting the two LED units to form a single, seamless panel. I couldn’t find technical drawings for the panels, but I was able to use caliper measurements to 3D-print a bracket. The model is included in the project, along with a couple of risers to attach different Raspberry Pi boards to the back.

Flicker
Once everything was up and running, I noticed a subtle flicker whenever data was fetched, due to the Linux scheduler preempting the LED refresh thread. The fix was to reserve a core: adding isolcpus=3 to /boot/firmware/cmdline.txt keeps core 3 off-limits so the LED driver runs there exclusively. On the Pi Zero 2, the rgbmatrix library picks this up automatically; on the Pi 5, I’m running a patched Piomatter branch that pins the blit thread to the isolated core.
Conclusion
This was a great weekend project, and a bit of a rollercoaster: getting the CLI bridge working, almost writing the project off over the font, chasing down the flicker, and finally seeing the finished result. 🤩