Skip to content

Hub75Driver

Source: Hub75Driver.h

BoardPins

struct BoardPins
src/light/drivers/Hub75Driver.h:360

One board's HUB75 wiring, in ribbon order. -1 means the board leaves that line to the user.

Public Attributes

const char * label

int8_t r1

int8_t g1

int8_t b1

int8_t r2

int8_t g2

int8_t b2

int8_t a

int8_t b

int8_t c

int8_t d

int8_t e

int8_t clk

int8_t lat

int8_t oe

bool(* onThisChip

bool editable : Are these pins the user's to change? False for a published board map: those lines are soldered, so showing fourteen uneditable rows is noise on the card.

Line

struct Line
src/light/drivers/Hub75Driver.h:437

One HUB75 line as wired: the GPIO and the ribbon name it carries.

Public Attributes

int8_t gpio

const char * role

Hub75Driver

class Hub75Driver
src/light/drivers/Hub75Driver.h:50

Inherits: DriverBase

Output driver: HUB75 panels driven directly from the board's own pins, with no receiving card.

The sibling of PanelCardDriver, which drives the same panels the other way: it emits ColorLight frames to a 5A-75B/E receiving card over raw Ethernet. That path is the right one above roughly 16,384 pixels. Below it the card is overhead: a Windows tool to configure, a dedicated Ethernet link, and a board that could have driven the panel from its own GPIO. This driver closes that gap.

A HUB75 panel is scanned, not addressed. Two rows light at once (upper through R1/G1/B1, lower through R2/G2/B2), selected by a row address, and the controller walks every address in turn. Brightness is TIME rather than amplitude: a pixel is a switch, so intensity comes from showing bit plane p for 2^p time units. Both facts live in the encoder (Hub75Slots.h), which is pure data and host-tested with no panel.

Output is continuous. A WS2812 strand latches a frame and holds it; a HUB75 panel holds nothing and is lit only while being clocked. So the platform arms one scan and re-sends the same buffer forever, and this driver writes the next frame into that buffer between scans. There is no per-frame transmit and no wait, which is why tick() looks unlike every other driver's.

Prior art: the HUB75 lineage generally (mrcodetastic/ESP32-HUB75-MatrixPanel-DMA, hzeller/rpi-rgb-led-matrix, ESPHome's hub75 component). The scan and bit-plane structure belongs to the panel rather than to any library; studied, not copied.

Public Attributes

uint8_t boardSel = 0 : Which board's wiring to use.

int8_t r1 = -1

int8_t g1 = -1

int8_t b1 = -1

int8_t r2 = -1

int8_t g2 = -1

int8_t b2 = -1

int8_t addrA = -1

int8_t addrB = -1

int8_t addrC = -1

int8_t addrD = -1

int8_t addrE = -1

int8_t clk = -1

int8_t lat = -1

int8_t oe = -1

uint8_t scanSel = 1 : The panel's own scan rate, which is NOT derivable from its size: two panels of identical dimensions can scan differently, so the user reads it off the panel and says which.

uint8_t bitDepth = 4 : Bit depth, and therefore the refresh tradeoff.

uint8_t peripheralSel_ = 0 : Which silicon block drives the panel, where the chip offers a choice.

Public Methods

virtual inline const char * tags() const override : Curated emoji tags for the module picker's chip filter — extras beyond the role chip (which the UI derives from role() on its own).

virtual inline LedHwBlock hwBlock() const override : The peripheral block this driver holds, for the sibling claim guard in DriverBase: two live drivers on one LCD_CAM corrupt each other, and the guard already arbitrates that for the parallel LED drivers.

virtual inline void setSourceBuffer(Buffer * buf) override

virtual inline void defineDriverControls() override : A driver's own controls (its pins, protocol, IP, window, …), added AFTER the correction block.

virtual inline void onControlChanged(const char * name) override : Picking a board writes its map into the pin controls.

virtual inline bool affectsPrepare(const char * name) const override : A control that changes the wire format or the geometry forces a rebuild; the rest (a pin typo being corrected, the depth) are handled by [prepare()] re-initializing anyway.

virtual inline void release() override : Clear every shared status string on release: fail buffer, config error, and config warning: so a stopped driver leaves nothing behind (frees the owned failBuf_; retracts the warning the same "clear only MY status" way as the error), then chain to MoonModule::release() so any ScratchBuffer the driver holds frees on disable and children recurse.

virtual inline void prepare() override : Tier-3 of the control-change split (see onControlChanged above): the module (re)allocates / recomputes whatever derived state it owns — an effect's heap, a Layer's mapping LUT, the Drivers output buffer.

virtual inline void tick() override

virtual inline void onCorrectionChanged() override : The scratch holds packed RGB, so outChannels does not size it: a wider wiring is compacted to three bytes a light on the way in.

virtual inline void tick1s() override : Publishes the measured refresh.

More info

The board select supplies the pins, defaulting to MoonHub75 and prefilling all fourteen on first definition, because a soldered line must never be guessed from nothing. Each map is taken from that board's own published source, and the maps themselves are on the driver's page (docs/moonmodules/light/drivers.md, "HUB75, details"), where a user wiring a panel can read them beside the rest of the card.

Two naming traps, both worth knowing before wiring. A panel's ribbon numbers its color lines R1/G1/B1 (upper half) and R2/G2/B2 (lower half); some board docs call the same pairs R0/G0/B0 and R1/G1/B1. And WLED's pin array is {R1,G1,B1,R2,G2,B2,A,B,C,D,E,LAT,OE,CLK}, so latch and output-enable come BEFORE the clock: a map transcribed as ...CLK,LAT,OE silently swaps three lines. Every map here is in this driver's own control order (clk, lat, oe), converted already.