Expand description
DMA-friendly framebuffer implementation for HUB75 LED panels with external latch circuit support.
This module provides a framebuffer implementation with memory layout optimized for efficient transfer to HUB75 LED panels. The data is structured for direct signal mapping, making it ideal for DMA transfers but also suitable for programmatic transfer. It supports RGB color and brightness control through multiple frames using Binary Code Modulation (BCM).
§Hardware Requirements
This implementation can be used by any microcontroller that has a peripheral capable of outputting a clock signal and 8 bits in parallel. A latch circuit similar to the one shown below can be used to hold the row address. The clock is gated so it does not reach the HUB75 interface when the latch is open. Since there is typically 4 2 input nand gates on a chip the 4th is used to allow PWM to gate the output enable providing much finer grained overall brightness control.
§Key Differences from Plain Implementation
- Uses an external latch circuit to hold the row address and gate the pixel clock, reducing memory usage
- 8-bit entries instead of 16-bit, halving memory requirements
- Separate address and data words for better control
- Requires an external latch circuit; not compatible with plain HUB75 wiring
§Features
- Support for RGB color with brightness control
- Multiple frame buffers for Binary Code Modulation (BCM)
- Integration with embedded-graphics for easy drawing
- Memory-efficient 8-bit format
§Brightness Control
Brightness is controlled through Binary Code Modulation (BCM):
- The number of brightness levels is determined by the
BITSparameter - Each additional bit doubles the number of brightness levels
- More bits provide better brightness resolution but require more memory
- Memory usage grows exponentially with the number of bits:
(2^BITS)-1frames - Example: 8 bits = 256 levels, 4 bits = 16 levels
§Memory Usage
The framebuffer’s memory usage is determined by:
- Panel size (ROWS × COLS)
- Number of brightness bits (BITS)
- Memory grows exponentially with bits:
(2^BITS)-1frames - 8-bit entries reduce memory usage compared to 16-bit implementations
§Example
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::Circle;
use embedded_graphics::primitives::Rectangle;
use embedded_graphics::primitives::PrimitiveStyle;
use hub75_framebuffer::compute_frame_count;
use hub75_framebuffer::compute_rows;
use hub75_framebuffer::Color;
use hub75_framebuffer::latched::DmaFrameBuffer;
// Create a framebuffer for a 64x32 panel with 3-bit color depth
const ROWS: usize = 32;
const COLS: usize = 64;
const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
// Draw a red rectangle
Rectangle::new(Point::new(10, 10), Size::new(20, 20))
.into_styled(PrimitiveStyle::with_fill(Color::RED))
.draw(&mut framebuffer)
.unwrap();
// Draw a blue circle
Circle::new(Point::new(40, 20), 10)
.into_styled(PrimitiveStyle::with_fill(Color::BLUE))
.draw(&mut framebuffer)
.unwrap();§Implementation Details
The framebuffer is organized to efficiently use memory while maintaining HUB75 compatibility:
- Each row contains both data and address words
- 8-bit entries store RGB data for two sub-pixels
- Separate address words control row selection and timing
- Multiple frames are used to achieve Binary Code Modulation (BCM)
- DMA transfers the data directly to the panel without transformation
§HUB75 Signal Bit Mapping (8-bit words)
Two distinct 8-bit words are streamed to the panel:
- Address / Timing (
Address) – row-select and latch control. - Pixel Data (
Entry) – RGB bits for two sub-pixels plus OE/LAT shadow bits.
The bit layouts intentionally overlap so that the very same GPIO lines can transmit either word without any run-time bit twiddling:
Address word (row select & timing)
┌──7─┬──6──┬─5─┬─4─┬─3─┬─2─┬─1─┬─0─┐
│ OE │ LAT │ │ E │ D │ C │ B │ A │
└────┴─────┴───┴───┴───┴───┴───┴───┘
^ ^
| └── Row-address lines (LSB = A)
└── Latch pulse – when HIGH the current address is latched and
external glue logic gates the pixel clock (`CLK`).Entry word (pixel data for two sub-pixels)
┌──7─┬──6──┬─5──┬─4──┬─3──┬─2──┬─1──┬─0──┐
│ OE │ LAT │ B2 │ G2 │ R2 │ B1 │ G1 │ R1 │
└────┴─────┴────┴────┴────┴────┴────┴────┘Bits 7–6 (OE/LAT) mirror those in the Address word so the control lines
remain valid throughout the entire DMA stream.
§External Latch Timing Sequence
- Pixel data for row N is clocked out while
OEis LOW. OEis raised HIGH – LEDs blank.- An
Addressword with the new row index is transmitted whileLATis HIGH; the CPLD/logic also blocksCLKduring this period. LATreturns LOW andOEis driven LOW again.
This keeps visual artefacts to a minimum while allowing the framebuffer to use just 8 data bits.
§Binary Code Modulation (BCM) Frames
Brightness is realised with Binary-Code-Modulation just like the plain
implementation—see https://www.batsocks.co.uk/readme/art_bcm_1.htm.
With a colour depth of BITS the driver allocates
FRAME_COUNT = 2^BITS − 1 frames. Frame n (0-based) is displayed for a
time slice proportional to 2^n.
For each channel the driver compares the 8-bit colour value against a per-frame threshold:
brightness_step = 256 / 2^BITS
threshold_n = (n + 1) * brightness_stepThe channel bit is set in frame n iff value >= threshold_n. Streaming the
frames from LSB to MSB therefore reproduces the intended 8-bit intensity
without extra processing.
§Memory Layout
Each row consists of:
- 4 address words (8 bits each) for row selection and timing
- COLS data words (8 bits each) for pixel data
§Safety
This implementation uses unsafe code for DMA operations. The framebuffer must be properly aligned in memory and the DMA configuration must match the buffer layout.
Structs§
- DmaFrame
Buffer - DMA-compatible framebuffer for HUB75 LED panels with external latch circuit support.