Story & Motivation

SpectrumAnalyzer was created because, ever since childhood, I loved watching the "jumping bars" move in sync with music on my Technics SC-EH550 stereo system. Unfortunately, modern audio equipment with built-in hardware spectrum analyzers is practically non-existent.

I tested various display solutions—OLEDs, LCDs, etc.—and came to the conclusion that VFD (Vacuum Fluorescent Display) has a unique charm that no other technology can match. Unfortunately, acquiring a high-quality VFD display these days is not easy and requires a fairly substantial financial investment.

The choice fell on the Noritake GU256X64D-3900B VFD so I could recreate a visual shape similar to the classic Technics display. I specifically wanted the pixels not to be too small, which is why I didn't pick the 256x128 version—smaller pixels would have sacrificed that authentic retro vibe.








Technical Architecture & Controller Selection


The display features two interfaces: UART and Parallel. Unfortunately, UART would be too slow to achieve full frame display speeds, so I decided to use the parallel interface.

When selecting a control board, I initially considered the STM32F722, which theoretically should be sufficient to drive the display. However, it quickly became apparent that the biggest bottleneck for computing an 8k-sample FFT is the 256 kB RAM limit. While technically feasible, this was meant to be a hobby project, not a trial by ordeal! :D

Many people's first thought would be a Raspberry Pi. However, writing a driver to control the parallel port on a Pi would be problematic. Standard GPIO bit-banging is far too slow (on a Raspberry Pi 4B using `libgpiod`, I achieved around 3 µs per access). Even if you manage to achieve much lower latency, thread preemption in Linux remains a major issue. Resolving this would require writing a custom kernel driver that utilizes a hardware block to drive the parallel interface—ideally with DMA—which, given the limited hardware documentation, would be a challenge. Another alternative could have been using an external microcontroller to drive the VFD and receiving data over SPI.

Ultimately, I decided to test the STM32MP257, which has no memory constraints and presents a very interesting multi-core architecture: Linux runs on the Cortex-A35 cores, while a 400 MHz Cortex-M33 microcontroller is available on the exact same chip.

Software Implementation & Dual-Core Pipeline

Since I already had an open-source C++ application that computes FFT (https://github.com/sylwekkominek/SpectrumAnalyzer), I decided to reuse it.

I modified the application so that the rendering/drafter thread, instead of drawing to a computer screen via OpenGL, opens `/dev/rpmsg0` and sends the computed frequency-domain data directly to the Cortex-M33 core. The M33 core runs the VFD display driver and controls the parallel port bare-metal, completely without an operating system.

This approach solves two critical problems:

1. Deterministic Display Timing: Without an OS, the bare-metal M33 driver has plenty of time for fully deterministic display control. As a result, the display operates buttery smooth without any stutters or delays that would otherwise occur due to Linux thread preemption if I were toggling GPIOs directly from Linux without a hardware DMA controller.

2. Offloaded Computation: All heavy lifting (FFT calculations) is performed on Linux. Audio capture is handled by listening to the system loopback audio. In my setup, audio is streamed from my phone to the device via Bluetooth, and sound output goes to an external USB sound card. This way, I don't have to worry about complex audio processing logic.

Inter-Core Communication (RPMsg / OpenAMP)

Communication between the Cortex-A35 and Cortex-M33 cores is relatively simple to set up using ST's example code.

On the Linux side, you open a virtual UART / RPMsg character device (`/dev/ttyRPMSG`) and send data in packets, which land on the M33 side and trigger an interrupt from...

Read more »