- 2,048
- Pixels
- 120 Hz
- Refresh rate
- 9 ms
- Frame latency
- 24 bit
- Color depth
Helios exists because every HUB75 demo I found stopped at “scroll some text.” The panel itself — 2048 RGB LEDs at $12 — is absurdly capable hardware. What was missing was a decent way to author content for it. So the project is really two things: a driver that gets out of the way, and a studio that makes the panel feel like a canvas.
Driving 2048 pixels without a CPU
HUB75 panels have no memory. You have to scan them continuously — clock out two rows of pixel data, latch, strobe output enable, advance the row address — thousands of times per second, forever. Doing this in software eats the ESP32 alive and any interrupt causes visible flicker.
The fix is the ESP32’s I2S peripheral in parallel LCD mode: a DMA descriptor chain plays the entire scan sequence out of RAM with zero CPU involvement. Color depth comes from binary code modulation — each bit plane’s descriptor is held on the bus for double the previous plane’s time, giving 12-bit-per-channel output at a rock-solid 120 Hz.
Two buffers, no tearing
Early versions wrote incoming frames straight into the live scan buffer. At 30 fps of streamed animation the panel showed horizontal tear lines where the DMA scan crossed a half-written frame. The fix is classic double buffering, with the swap deferred to the DMA end-of-frame interrupt:
void IRAM_ATTR onFrameComplete() {
if (backBufferReady) {
std::swap(frontDesc, backDesc); // repoint the descriptor chain
backBufferReady = false;
}
}
Swapping descriptor pointers instead of copying 24 KB of pixel data keeps the ISR under 2 µs. Tearing gone.
The studio
The browser side is a pixel editor with a frame timeline, plus a small JS-like scripting language for procedural effects — pixel(x, y, hsv(t + x * 4, 255, 255)) gets you a rainbow sweep in one line. Scripts compile to a compact bytecode that either runs in the browser preview or streams frames to the panel.
Frames travel over a raw WebSocket as 4-byte-header binary packets. Gamma correction happens on-device via an 8-to-12-bit lookup table — the first thing I learned is that linear RGB on LEDs looks washed-out and wrong, and a LUT costs nothing.
Development timeline
- 2025-06
First pixels
Bit-banged the HUB75 protocol — 40 Hz and visibly flickering.
- 2025-07
DMA rewrite
Moved scanning to the I2S peripheral; CPU load dropped to near zero.
- 2025-09
Web studio
Pixel editor and effect scripting shipped in the browser.
- 2025-11
v1.0
Binary streaming protocol stabilized and the enclosure printed.