Expand description
§ESP-HUB75
A no-std Rust driver for HUB75-style LED matrix panels on ESP32-series
microcontrollers. HUB75 is a standard interface for driving large, bright,
and colorful RGB LED displays, commonly used in digital signage and art
installations.
This library provides a high-performance implementation that uses Direct Memory Access (DMA) to drive the display with minimal CPU overhead. It is designed to work with a variety of ESP32 models, using the most efficient peripheral available on each chip:
- ESP32-S3: Uses the LCD_CAM peripheral
- ESP32-C6: Uses the PARL_IO peripheral
- ESP32-C5: Uses the PARL_IO peripheral (8-bit mode only; requires a
latch circuit and
Hub75Pins8) - ESP32: Uses the I2S peripheral in parallel mode
§Framebuffers
The hub75-framebuffer crate provides bitplane framebuffers in two
variants: a direct-drive variant (16-bit, no external latch) and a latched
variant (8-bit, requires an external address-latch circuit). Both variants
can be sent directly to the peripheral without any extra formatting step.
Bitplane framebuffers (framebuffer::bitplane::plain::DmaFrameBuffer /
framebuffer::bitplane::latched::DmaFrameBuffer) store only one bit per
pixel per plane. The driver uses DMA descriptors to assemble the BCM
(Binary Code Modulation) output on the fly, keeping RAM usage low while
delivering high visual quality.
§Usage
Here is an example of how to initialize the driver for an ESP32-S3:
//! Example rendering a Rustacean PNG image on a HUB75 LED matrix using LCD_CAM
//!
//! The image is pre-converted to raw RGB888 bytes and drawn via
//! `embedded_graphics::image::ImageRaw`.
#![no_std]
#![no_main]
#![allow(clippy::uninlined_format_args)]
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embedded_graphics::geometry::Point;
use embedded_graphics::mono_font::ascii::FONT_5X7;
use embedded_graphics::mono_font::MonoTextStyleBuilder;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics::text::Alignment;
use embedded_graphics::text::Text;
use embedded_graphics::Drawable;
use embedded_sprites::image::Image;
use embedded_sprites::include_image;
use embedded_sprites::sprite::Sprite;
use esp_backtrace as _;
use esp_hal::clock::CpuClock;
use esp_hal::gpio::Pin;
use esp_hal::main;
use esp_hal::time::Rate;
use esp_hub75::framebuffer::bitplane::plain::DmaFrameBuffer;
use esp_hub75::framebuffer::compute_rows;
use esp_hub75::Color;
use esp_hub75::Hub75;
use esp_hub75::Hub75Pins16;
esp_bootloader_esp_idf::esp_app_desc!();
const ROWS: usize = 64;
const COLS: usize = 64;
const NROWS: usize = compute_rows(ROWS);
const PLANES: usize = 7;
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
}};
}
#[include_image]
const GRASS_DATA: Image<hub75_framebuffer::Color> = "./images/rustacean-flat-happy-64x64.png";
#[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.GPIO38.degrade(),
grn1: peripherals.GPIO42.degrade(),
blu1: peripherals.GPIO48.degrade(),
red2: peripherals.GPIO47.degrade(),
grn2: peripherals.GPIO2.degrade(),
blu2: peripherals.GPIO21.degrade(),
addr0: peripherals.GPIO14.degrade(),
addr1: peripherals.GPIO46.degrade(),
addr2: peripherals.GPIO13.degrade(),
addr3: peripherals.GPIO9.degrade(),
addr4: peripherals.GPIO3.degrade(),
blank: peripherals.GPIO11.degrade(),
clock: peripherals.GPIO12.degrade(),
latch: peripherals.GPIO10.degrade(),
};
let fb = mk_static!(FBType, FBType::new());
let rustacean = Sprite::new(Point::new(0, 0), &GRASS_DATA);
rustacean.draw(fb).expect("failed to draw image");
let text_style = MonoTextStyleBuilder::new()
.font(&FONT_5X7)
.text_color(Color::WHITE)
.background_color(Color::BLACK)
.build();
Text::with_alignment(
"Hello, Hub75",
Point::new(31, 55),
text_style,
Alignment::Center,
)
.draw(fb)
.expect("failed to draw text");
let _hub75 = Hub75::new(
peripherals.LCD_CAM,
pins,
peripherals.DMA_CH0,
tx_descriptors,
Rate::from_mhz(20),
&*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. This only applies to 8-bit latched configurations (Hub75Pins8); in 16-bit direct-drive mode the blank signal is always active-low. 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.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-C6 PARL_IO peripheral 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 instant pointer-delta updates with no DMA stop/restart. A frame-boundary ISR is always active in this mode, providing bothframe_count()and the completion signal forHub75Swap::wait()/Hub75Swap::wait_for_done(). Only supported on ESP32 and ESP32-S3 — enabling this on ESP32-C5/C6 is a compile-time error because the PARL_IO peripheral does not support 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 (render / DMA wait functions) in Instruction RAM (IRAM) to avoid flash-cache stalls (for example during Wi-Fi, PSRAM, or SPI-flash activity) that can cause visible flicker. Enabling this feature consumes roughly 5–10 KiB of IRAM.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.
§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
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 - Represents errors that can occur during HUB75 driver operations.
Traits§
- Gdma
Channel Num - Maps a DMA channel singleton to its numeric index so that the driver can configure GDMA interrupts without scanning registers at runtime.
- Hub75
Pins - A trait for converting a set of HUB75 pins into the required format for a specific ESP32 peripheral.
Functions§
- dma_
descriptor_ count - Computes the number of DMA descriptors required for a given framebuffer configuration.
Type Aliases§
- Color
- The color type used by the HUB75 driver. Color type used in the framebuffer