Expand description
§ESP-HUB75
A no-std driver for HUB75-style LED matrix panels on ESP32-series
microcontrollers.
The panel is refreshed over DMA with almost no CPU involvement, using whichever peripheral fits each chip best:
- ESP32-S3: Uses the
LCD_CAMperipheral - ESP32-C6: Uses the
PARL_IOperipheral - ESP32-C5: Uses the
PARL_IOperipheral (8-bit mode only; requires a latch circuit andHub75Pins8) - ESP32: Uses the I2S peripheral in parallel mode
§Framebuffers
Use the bitplane framebuffers from the hub75-framebuffer crate.
They come in two variants: direct-drive (16-bit, no external latch) and
latched (8-bit, needs an external address-latch circuit). Both can be
handed to the peripheral as-is; there is no extra formatting step.
Bitplane framebuffers (framebuffer::bitplane::plain::DmaFrameBuffer /
framebuffer::bitplane::latched::DmaFrameBuffer) store only one bit per
pixel per plane. The driver assembles the BCM (Binary Code Modulation)
output on the fly with DMA descriptors, so RAM use stays low without
losing visual quality.
§Usage
Example for ESP32-C6:
#![no_std]
#![no_main]
use embedded_graphics::Drawable;
use embedded_graphics::geometry::Point;
use embedded_graphics::mono_font::MonoTextStyleBuilder;
use embedded_graphics::mono_font::ascii::FONT_5X7;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics::text::Alignment;
use embedded_graphics::text::Text;
use esp_backtrace as _;
use esp_hal::clock::CpuClock;
use esp_hal::gpio::Pin;
use esp_hal::main;
use esp_hub75::Color;
use esp_hub75::Hub75;
use esp_hub75::Hub75Pins16;
use esp_hub75::framebuffer::bitplane::plain::DmaFrameBuffer;
use esp_hub75::framebuffer::compute_rows;
esp_bootloader_esp_idf::esp_app_desc!();
const ROWS: usize = 64;
const COLS: usize = 64;
const NROWS: usize = compute_rows(ROWS);
const PLANES: usize = 4;
type FBType = DmaFrameBuffer<NROWS, COLS, PLANES>;
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write($val);
x
}};
}
#[main]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
let tx_descriptors = esp_hub75::hub75_dma_descriptors!(FBType);
let pins = Hub75Pins16 {
red1: peripherals.GPIO19.degrade(),
grn1: peripherals.GPIO20.degrade(),
blu1: peripherals.GPIO21.degrade(),
red2: peripherals.GPIO22.degrade(),
grn2: peripherals.GPIO23.degrade(),
blu2: peripherals.GPIO15.degrade(),
addr0: peripherals.GPIO10.degrade(),
addr1: peripherals.GPIO8.degrade(),
addr2: peripherals.GPIO1.degrade(),
addr3: peripherals.GPIO0.degrade(),
addr4: peripherals.GPIO11.degrade(),
blank: peripherals.GPIO5.degrade(),
clock: peripherals.GPIO7.degrade(),
latch: peripherals.GPIO6.degrade(),
};
let fb = mk_static!(FBType, FBType::new());
let text_style = MonoTextStyleBuilder::new()
.font(&FONT_5X7)
.text_color(Color::YELLOW)
.background_color(Color::BLACK)
.build();
let point = Point::new(32, 32);
Text::with_alignment("Hello, World!", point, text_style, Alignment::Center)
.draw(fb)
.expect("failed to draw text");
let _hub75 = Hub75::new(
peripherals.PARL_IO,
pins,
peripherals.DMA_CH0,
tx_descriptors,
Hub75Config::new(),
&*fb,
)
.expect("failed to create Hub75");
loop {
core::hint::spin_loop();
}
}§Crate Features
esp32: Enable support for the ESP32esp32s3: Enable support for the ESP32-S3esp32c5: Enable support for the ESP32-C5esp32c6: Enable support for the ESP32-C6defmt: Enable logging withdefmtlog: Enable logging with thelogcrateinvert-blank: Invert the blank signal in hardware by enabling the GPIO output inverter on the blank pin. Applies to both 8-bit latched (Hub75Pins8) and 16-bit direct-drive (Hub75Pins16) configurations. Some latch controller boards include a hardware inverter on the blank line; enable this feature to compensate.invert-clock: Invert the clock signal. By default the driver outputs data that changes on the falling edge of CLK so that it is stable when the panel latches on the rising edge. Enable this feature if your panel requires the opposite polarity.invert-oe: Forwards to thehub75-framebuffercrate, inverting the output-enable (OE) signal in the generated data stream. Whereasinvert-blankinverts the blank pin in hardware, this feature flips the OE polarity at the framebuffer level instead. The two features may seem redundant but are meant to be used together: where the peripheral drives all pins to 0 when a transfer completes,invert-blankturns that idle 0 into a 1 (blanked), andinvert-oecompensates for the now-inverted pin.full-chain-dma: Build the entire BCM repetition chain in a single DMA transfer instead of one plane per interrupt. This reduces interrupt frequency at the cost of more DMA descriptor RAM. Note that the ESP32-C6PARL_IOperipheral has a 65,535-byte per-transfer limit, which constrains the maximum panel size and plane count when this feature is enabled.circular-dma: Circular DMA descriptor chain (impliesfull-chain-dma). The DMA engine starts once and loops forever; buffer swaps are pointer-delta updates applied by the swap-boundary ISR at a pass boundary, so there is no DMA stop/restart and no mid-frame tearing. In steady state no interrupts are enabled: a swap temporarily arms the boundary detector (suc_eofon the last descriptor) and the ISR disarms it again after applying the swap. On ESP32-C5 (PARL_IO) a consumedsuc_eofhalts the DMA channel, so the ISR restarts the transfer after each swap; on ESP32/S3 the chain free-runs uninterrupted. Supported on ESP32 (I2S), ESP32-S3 (LCD_CAM), and ESP32-C5 (PARL_IO); on ESP32-C6 this is a compile-time error becausePARL_IOcannot do circular chains.skip-black-pixels: Forwards to thehub75-framebuffercrate, enabling an optimization that skips writing black pixels to the framebuffer.tail-closes-latch: Forwards to thehub75-framebuffercrate. Appends a tail word at the end of each DMA buffer (plainframebuffers) or at the end of each bit-plane (bitplane::plain) that drives LATCH LOW when the transfer completes. Does not apply to latched framebuffers.iram: Place the driver’s hot path — the refresh ISR, the DMA start/finish/wait path, and the BCM segment and descriptor bookkeeping, including the framebuffer pointer-delta swap — in Instruction RAM (IRAM) to avoid flash-cache stalls (for example during Wi-Fi, PSRAM, or SPI-flash activity) that can cause visible flicker. Drawing (set_pixel) stays in flash. Costs roughly 1–2 KiB of IRAM (about 4 KiB atopt-level = 0).lead-blank-1/2/4/8/16/trail-blank-1/2/4/8/16: Forwards tohub75-framebuffer. Control the number of pixel-clock cycles of blanking (OE HIGH) inserted around row address changes. The lead blank controls blanking before the address change, and the trail blank controls blanking after. Higher values reduce ghosting at the cost of slightly less brightness.inter-row-blank-4/8/16/32: Forwards tohub75-framebuffer. Insert additional dead clock cycles at the end of each row. In plain framebuffers the gap defers the address change to the first pixel of the next row, giving slow panels more time to finish blanking. In latched framebuffers the gap adds extra blanked cycles after the address change.reverse-row-order: Forwards tohub75-framebuffer. Stores the rows of the framebuffer in reverse scan order so that the DMA stream renders the last panel row first and row 0 last.
§Safety
This crate uses unsafe code to interface with hardware peripherals, but it
exposes a safe, high-level API.
Re-exports§
pub use hub75_framebuffer as framebuffer;
Macros§
- hub75_
dma_ descriptors - Allocates static DMA descriptors sized for the given framebuffer type.
Structs§
- Hub75
- HUB75 display controller driven by an interrupt-based BCM refresh loop.
- Hub75
Config - Configuration for creating a
Hub75instance. - Hub75
DmaDescriptors - DMA descriptor storage bound to a specific framebuffer type.
- Hub75
Pins8 - Pin configuration for a HUB75 panel with an external address latch.
- Hub75
Pins16 - Pin configuration for a HUB75 panel without an external address latch.
- Hub75
Swap - A pending framebuffer swap.
Enums§
- Hub75
Error - Errors returned by the HUB75 driver.
Traits§
- Hub75
Pins - Describes the pins used to drive a HUB75 panel.
Functions§
- dma_
descriptor_ count - Computes the number of DMA descriptors this driver needs for a
framebuffer of type
FB. - frame_
clock_ cycles - Number of pixel-clock cycles the DMA streams for one complete panel
refresh of framebuffer type
FB. - refresh_
hz - Theoretical refresh rate (in Hz) for framebuffer type
FBat the given HUB75 pixel-clock frequency.
Type Aliases§
- Color
- The color type used by the HUB75 driver. Color type used in the framebuffer