stuartparmenter/esp-hub75

0.1.0

Latest
uploaded 10 hours ago
High-performance DMA-based driver for HUB75 RGB LED matrix panels on ESP32 platforms

readme

# HUB75 DMA Driver Component

ESP-IDF component for driving HUB75 RGB LED matrix panels via DMA. Supports ESP32, ESP32-S2, ESP32-S3, ESP32-C6, and ESP32-P4.

**Repository:** https://github.com/stuartparmenter/esp-hub75
**Examples & Documentation:** See main repository for working examples and detailed documentation

## Key Features

- Static circular DMA refresh (no CPU intervention after initialization)
- Multi-platform support: GDMA (S3), I2S (ESP32/S2), PARLIO (P4/C6)
- CIE 1931 gamma correction with native bit-depth LUTs (6-12 bit)
- Multi-panel layouts with serpentine and zigzag chaining
- Double buffering for tear-free animation
- Multiple pixel formats: RGB888, RGB888_32, RGB565

## Installation

### Option 1: Component Manager (Recommended)

In your project's `idf_component.yml`:

```yaml
dependencies:
  hub75:
    git: https://github.com/stuartparmenter/esp-hub75
    path: components/hub75  # Important: point to subdirectory!
```

For local development:

```yaml
dependencies:
  hub75:
    path: /path/to/esp-hub75/components/hub75
```

### Option 2: Manual Copy

Copy the component directory to your project:

```bash
cp -r /path/to/esp-hub75/components/hub75 my_project/components/
```

**Important:** Always point to `components/hub75/` subdirectory, not the repository root. The root contains a standalone test project that will conflict if included as a component.

## Quick Start

```cpp
#include "hub75.h"

void app_main() {
    // Configure for your panel
    Hub75Config config{};
    config.panel_width = 64;
    config.panel_height = 64;
    config.scan_pattern = Hub75ScanPattern::SCAN_1_32;
    config.scan_wiring = ScanPattern::STANDARD_TWO_SCAN;  // Most panels
    config.shift_driver = ShiftDriver::FM6126A;            // Or GENERIC

    // Set GPIO pins
    config.pins.r1 = 25;
    config.pins.g1 = 26;
    config.pins.b1 = 27;
    config.pins.r2 = 14;
    config.pins.g2 = 12;
    config.pins.b2 = 13;
    config.pins.a = 23;
    config.pins.b = 19;
    config.pins.c = 5;
    config.pins.d = 17;
    config.pins.e = -1;
    config.pins.lat = 4;
    config.pins.oe = 15;
    config.pins.clk = 16;

    // Create and start driver
    Hub75Driver driver(config);
    driver.begin();  // Starts continuous refresh

    // Draw pixels - changes appear automatically!
    driver.set_pixel(10, 10, 255, 0, 0);  // Red
    driver.set_pixel(20, 20, 0, 255, 0);  // Green
    driver.set_pixel(30, 30, 0, 0, 255);  // Blue

    // Optional: Double buffering for tear-free animation
    if (config.double_buffer) {
        driver.clear();  // Clear back buffer
        // ... draw frame ...
        driver.flip_buffer();  // Atomic swap
    }
}
```

**Pin Configuration:** See repository [examples/common/pins_example.h](https://github.com/stuartparmenter/esp-hub75/blob/main/examples/common/pins_example.h) for board-specific pre-configured pin layouts.

## API Reference

### Initialization

- `Hub75Driver(config)` - Create driver with configuration
- `bool begin()` - Initialize hardware and start continuous refresh loop
- `void end()` - Stop refresh and cleanup resources

### Drawing

- `void draw_pixels(x, y, w, h, buffer, format, color_order, big_endian)` - Bulk pixel write (most efficient)
  - `format`: `PixelFormat::RGB888` (24-bit packed), `RGB888_32` (32-bit padded), or `RGB565` (16-bit)
  - `color_order`: `ColorOrder::RGB` or `BGR` (for RGB888_32 only)
  - `big_endian`: Byte order control (affects RGB565 and RGB888_32)
- `void set_pixel(x, y, r, g, b)` - Draw single pixel with RGB888 values (0-255)
- `void clear()` - Clear entire display to black

**Note:** For graphics primitives (rectangles, lines, circles), use a graphics library like LVGL or Adafruit_GFX, or implement manually with `set_pixel()` loops.

### Double Buffering

- `void flip_buffer()` - Swap front and back buffers atomically

When double buffering is enabled, drawing operations (`clear()`, `set_pixel()`, `draw_pixels()`) operate on the back buffer. Call `flip_buffer()` to atomically swap buffers and display the new frame.

**Memory Usage:**
- **GDMA/I2S** (internal SRAM): ~57 KB single-buffer, ~114 KB double-buffer (64×64 panel, 8-bit)
- **PARLIO** (PSRAM): ~284 KB single-buffer, ~568 KB double-buffer (64×64 panel, 8-bit)

Double buffering doubles memory usage but enables tear-free animation. PARLIO uses ~5× more memory than GDMA/I2S, but allocates from PSRAM (typically 8-16 MB available) rather than scarce internal SRAM (~500 KB total). Larger panels scale linearly: 128×128 uses ~4× memory (PARLIO: ~1.1 MB, GDMA: ~228 KB).

### Brightness & Color Control

- `void set_brightness(uint8_t brightness)` - Set display brightness (0-255)
- `void set_intensity(float intensity)` - Set intensity multiplier (0.0-1.0) for smooth dimming
- `uint8_t get_brightness()` - Get current brightness value
- `void set_gamma_mode(Hub75GammaMode mode)` - Set gamma correction mode
  - `Hub75GammaMode::NONE` - No gamma correction (linear)
  - `Hub75GammaMode::CIE1931` - CIE 1931 standard (recommended)
  - `Hub75GammaMode::GAMMA_2_2` - Gamma 2.2 correction
- `Hub75GammaMode get_gamma_mode()` - Get current gamma mode

**Dual-Mode Brightness System:**
- **Basis brightness** (0-255): Adjusts hardware OE (output enable) timing in DMA buffers
- **Intensity** (0.0-1.0): Runtime scaling multiplier for smooth dimming without refresh rate changes
- Final brightness = (basis × intensity) >> 8

### Information

- `uint16_t get_width()` - Get display width in pixels (panel_width × layout_cols)
- `uint16_t get_height()` - Get display height in pixels (panel_height × layout_rows)
- `bool is_running()` - Check if refresh loop is active

## Configuration Options

### Hardware Specifications

```cpp
Hub75Config config{};  // Start with defaults

// Single Panel Hardware
config.panel_width = 64;                               // Single panel width in pixels
config.panel_height = 64;                              // Single panel height in pixels
config.scan_pattern = Hub75ScanPattern::SCAN_1_32;     // Hardware scan pattern
config.scan_wiring = ScanPattern::STANDARD_TWO_SCAN;   // Coordinate remapping
config.shift_driver = ShiftDriver::GENERIC;            // LED driver chip type
```

**Scan Pattern Options:**
- `SCAN_1_2` - 2-row pairs (4px high panels)
- `SCAN_1_4` - 4-row pairs (8px high panels)
- `SCAN_1_8` - 8-row pairs (16px high panels)
- `SCAN_1_16` - 16-row pairs (32px high panels)
- `SCAN_1_32` - 32-row pairs (64px high panels)

Must match panel hardware. Formula: `num_rows = height / scan_pattern_value`

**Scan Wiring Options:**
- `STANDARD_TWO_SCAN` - Most panels (default, no coordinate remapping)
- `FOUR_SCAN_16PX_HIGH` - Four-scan 1/4 scan, 16-pixel high panels
- `FOUR_SCAN_32PX_HIGH` - Four-scan 1/8 scan, 32-pixel high panels
- `FOUR_SCAN_64PX_HIGH` - Four-scan 1/8 scan, 64-pixel high panels

For panels with non-standard internal wiring that require coordinate remapping.

**Shift Driver Options:**
- `GENERIC` - Standard panels with no special initialization (default)
- `FM6126A` - Very common in modern panels (also works for ICN2038S)
- `ICN2038S` - Alias for FM6126A (same initialization sequence)
- `FM6124` - FM6124 family panels
- `MBI5124` - MBI5124 panels (requires `clk_phase_inverted = true`)
- `DP3246` - DP3246 panels (special timing requirements)

**Tip:** If panel shows incorrect colors or doesn't light up with `GENERIC`, try `FM6126A` first - it's the most common driver chip in modern panels.

### Multi-Panel Physical Layout

```cpp
// Multi-panel configuration (optional, defaults to single panel)
config.layout_rows = 1;                                // Number of panels vertically
config.layout_cols = 1;                                // Number of panels horizontally
config.layout = PanelLayout::HORIZONTAL;               // Chaining pattern
```

**Layout Options:**
- `HORIZONTAL` - Simple left-to-right chain (single row only)
- `TOP_LEFT_DOWN` - Serpentine, start top-left corner
- `TOP_RIGHT_DOWN` - Serpentine, start top-right corner
- `BOTTOM_LEFT_UP` - Serpentine, start bottom-left corner
- `BOTTOM_RIGHT_UP` - Serpentine, start bottom-right corner
- `TOP_LEFT_DOWN_ZIGZAG` - Zigzag, start top-left (all panels upright)
- `TOP_RIGHT_DOWN_ZIGZAG` - Zigzag, start top-right (all panels upright)
- `BOTTOM_LEFT_UP_ZIGZAG` - Zigzag, start bottom-left (all panels upright)
- `BOTTOM_RIGHT_UP_ZIGZAG` - Zigzag, start bottom-right (all panels upright)

**Serpentine vs Zigzag:**
- **Serpentine**: Alternate rows are physically mounted upside down (saves cable length)
- **Zigzag**: All panels mounted upright, cables route back between rows (longer cables)

**Row-Major Chaining:** Panels chain HORIZONTALLY across rows (not vertically down columns). This matches the ESP32-HUB75-MatrixPanel-DMA reference library.

### Performance & Timing

```cpp
// Performance
config.output_clock_speed = Hub75ClockSpeed::HZ_20M;   // Clock speed
config.bit_depth = 8;                                   // BCM bit depth: 6-12
config.min_refresh_rate = 60;                           // Minimum refresh rate in Hz

// Timing
config.latch_blanking = 1;                              // OE blanking cycles during LAT
config.clk_phase_inverted = false;                      // Invert clock phase (MBI5124)

// Features
config.double_buffer = false;                           // Enable double buffering
config.temporal_dither = false;                         // Enable temporal dithering (NYI)

// Color
config.gamma_mode = Hub75GammaMode::CIE1931;            // Gamma correction mode
config.brightness = 128;                                // Initial brightness (0-255)
```

**Clock Speed Options:**
- `HZ_8M` - 8 MHz (most compatible)
- `HZ_10M` - 10 MHz
- `HZ_16M` - 16 MHz
- `HZ_20M` - 20 MHz (default, works with most panels)

Higher speeds may cause signal integrity issues with long cables or poor-quality panels.

**Bit Depth:**
- 6-bit: Fast refresh, basic color depth
- 8-bit: Good balance (default)
- 10-bit: Better gradients, recommended for smooth animations
- 12-bit: Best quality, slower refresh

Higher bit depth = more descriptors + slower refresh rate.

### Pin Configuration

```cpp
// GPIO pin assignments (example for ESP32-S3)
config.pins.r1 = 25;   // Red data (top half)
config.pins.g1 = 26;   // Green data (top half)
config.pins.b1 = 27;   // Blue data (top half)
config.pins.r2 = 14;   // Red data (bottom half)
config.pins.g2 = 12;   // Green data (bottom half)
config.pins.b2 = 13;   // Blue data (bottom half)
config.pins.a = 23;    // Row address bit A
config.pins.b = 19;    // Row address bit B
config.pins.c = 5;     // Row address bit C
config.pins.d = 17;    // Row address bit D
config.pins.e = -1;    // Row address bit E (-1 if unused)
config.pins.lat = 4;   // Latch
config.pins.oe = 15;   // Output enable
config.pins.clk = 16;  // Clock
```

**GPIO Restrictions:**
- Avoid strapping pins (GPIO0, GPIO46, etc.)
- ESP32-S3: GPIO19/20 unavailable if USB CDC enabled
- Check ESP32 variant datasheets for input-only pins
- Some platforms have restrictions on which peripherals can use which pins

**Pre-configured Examples:** See repository [examples/common/pins_example.h](https://github.com/stuartparmenter/esp-hub75/blob/main/examples/common/pins_example.h) for tested pin configurations for different boards.

## Multi-Panel Layouts

### Layout Examples

**2×2 Serpentine Grid** (128×128, panels alternate upside down):
```cpp
config.panel_width = 64;
config.panel_height = 64;
config.layout_rows = 2;
config.layout_cols = 2;
config.layout = PanelLayout::TOP_LEFT_DOWN;  // Serpentine
```

**4×1 Horizontal Chain** (256×64, all panels upright):
```cpp
config.panel_width = 64;
config.panel_height = 64;
config.layout_rows = 1;
config.layout_cols = 4;
config.layout = PanelLayout::HORIZONTAL;
```

**3×2 Zigzag Grid** (192×128, all panels upright):
```cpp
config.panel_width = 64;
config.panel_height = 64;
config.layout_rows = 2;
config.layout_cols = 3;
config.layout = PanelLayout::TOP_LEFT_DOWN_ZIGZAG;  // All upright
```

### Physical Wiring

**Serpentine Example** (TOP_LEFT_DOWN):
```
Virtual Display:        Physical Chain:         Panel Orientation:
┌────┬────┐             ┌───┬───┬───┬───┐      ┌────┬────┐
│ 0  │ 1  │             │ 0 │ 1 │ 2 │ 3 │      │ 0→ │ ←1 │  (Panel 1 upside down)
├────┼────┤             └───┴───┴───┴───┘      ├────┼────┤
│ 2  │ 3  │                                     │ 2→ │ ←3 │  (Panel 3 upside down)
└────┴────┘                                     └────┴────┘
```

Panel chain is always horizontal in DMA buffer: `[Panel 0][Panel 1][Panel 2][Panel 3]`

**Coordinate Transformation Pipeline:**
Virtual coordinates → Panel layout remap → Scan pattern remap → Physical DMA buffer

## Platform-Specific Notes

### ESP32-S3 (GDMA)

- **Status:** ✅ Working
- **Memory:** Internal SRAM allocation
- **BCM Method:** Descriptor duplication (bit 7 has 32 descriptors → same buffer)
- **Architecture:** Direct LCD_CAM register access, static circular descriptor chain
- **Memory Usage:** ~57 KB single-buffer (64×64, 8-bit)

### ESP32/ESP32-S2 (I2S)

- **Status:** ⏳ Implemented, untested on hardware
- **Memory:** Internal SRAM allocation
- **BCM Method:** Descriptor duplication (same as GDMA)
- **Architecture:** I2S peripheral in LCD mode, static circular descriptor chain
- **Memory Usage:** Similar to GDMA

### ESP32-P4 (PARLIO)

- **Status:** ✅ Tested and working
- **Memory:** PSRAM allocation via EDMA (frees internal SRAM for application use)
- **BCM Method:** Buffer padding (not descriptor duplication)
- **Clock Gating:** MSB (bit 15) controls PCLK on/off for display timing
- **Architecture:** Transaction-based API, single buffer with loop transmission
- **Memory Usage:** ~284 KB single-buffer (64×64, 8-bit) in PSRAM
- **Cache Sync:** Automatic cache flushing for CPU writes to DMA-visible PSRAM

**Why PSRAM?**
- Frees scarce internal SRAM (~500 KB) for application code
- Better scalability for large displays (128×128 = ~1 MB)
- PSRAM typically 8-32 MB on ESP32-P4

### ESP32-C6 (PARLIO)

- **Status:** ⏳ Implemented, untested on hardware
- **Memory:** Same as ESP32-P4 (PSRAM via EDMA)
- **Difference:** No clock gating support (MSB unused, BCM via padding only)
- **Architecture:** Same as ESP32-P4

### Memory Comparison

| Platform | 64×64 8-bit | 128×128 8-bit | Memory Pool | Notes |
|----------|-------------|---------------|-------------|-------|
| GDMA/I2S | ~57 KB      | ~228 KB       | Internal SRAM (~500 KB) | Faster access, descriptor chains |
| PARLIO   | ~284 KB     | ~1.1 MB       | PSRAM (8-32 MB) | Frees SRAM, simpler code |

PARLIO trades memory efficiency for code simplicity and application SRAM availability.

## Troubleshooting

### Black Screen

**Symptom:** Panel doesn't light up at all

**Solutions:**
- Try `shift_driver = ShiftDriver::FM6126A` (most common in modern panels)
- Verify pin mapping matches your board layout
- Check power supply is adequate (64×64 panels can draw 3-4A at full brightness)
- Verify data cable connections are secure

### Incorrect Colors or Garbled Display

**Symptom:** Colors are wrong, swapped, or display shows random patterns

**Solutions:**
- **Wrong shift driver:** Try `FM6126A` (works for most modern panels including ICN2038S)
- **Wrong scan wiring:** Try different `ScanPattern` values if `STANDARD_TWO_SCAN` doesn't work
- **Pin mapping:** Verify R1/G1/B1/R2/G2/B2 are connected to correct GPIOs
- **Scan pattern:** Ensure `scan_pattern` matches panel height (64px = SCAN_1_32)

### Ghosting

**Symptom:** Previous frame content faintly visible, or adjacent rows bleeding into each other

**Solutions:**
- Increase `latch_blanking` parameter (default is 1, try 2-4)
- Lower `output_clock_speed` (signal integrity issue)
- Check for poor-quality data cables

### Flickering

**Symptom:** Display flickers or has visible scanlines

**Solutions:**
- Refresh rate too low - driver will automatically adjust `lsbMsbTransitionBit`
- Increase `min_refresh_rate` parameter
- Reduce `bit_depth` if refresh rate is critically low

### Scrambled/Weird Patterns

**Symptom:** Display shows correct colors but scrambled geometry

**Solutions:**
- **Wrong scan_wiring:** Try `FOUR_SCAN_*` variants for non-standard panels
- **Multi-panel layout:** Verify `layout` setting matches physical wiring
- **Scan pattern:** Double-check `scan_pattern` matches panel specifications

### MBI5124 Panels

**Symptom:** MBI5124 panels don't work correctly

**Solution:**
- Must set `clk_phase_inverted = true` in configuration
- Set `shift_driver = ShiftDriver::MBI5124`

## Advanced Topics

### BCM Timing

The driver uses Binary Code Modulation (BCM) for color depth control. Instead of PWM (which would flicker), BCM varies how long each bit plane is displayed:

- **Pure BCM:** Bit 0 shown 1×, bit 1 shown 2×, bit 2 shown 4×, ..., bit 7 shown 128×
- **Optimized:** Lower bits (≤ lsbMsbTransitionBit) shown only 1×, upper bits get BCM weighting

`lsbMsbTransitionBit` is auto-calculated to achieve target refresh rate. Higher values = faster refresh but slight color depth trade-off on lower bits (perceptually minor due to CIE correction).

**Implementation differs by platform:**
- **GDMA/I2S:** Multiple DMA descriptors pointing to same buffer (descriptor duplication)
- **PARLIO:** Buffer padding with extended display periods (clock gating on ESP32-P4)

### Double Buffering Best Practices

```cpp
// Enable double buffering in config
config.double_buffer = true;
Hub75Driver driver(config);
driver.begin();

// Rendering loop
while (true) {
    // Draw to back buffer
    driver.clear();
    draw_my_frame();

    // Atomic swap
    driver.flip_buffer();

    // Front buffer displays while we draw next frame
    vTaskDelay(pdMS_TO_TICKS(16));  // ~60 FPS
}
```

**Benefits:**
- Eliminates tearing artifacts during animation
- Allows complex frame preparation without visible rendering

**Costs:**
- Doubles memory usage
- Slightly more complex code

### Scan Pattern Details

The `Hub75ScanPattern` enum determines how many row pairs are addressed simultaneously:

```
SCAN_1_32 (64px high panel):
- 32 row pairs (rows 0+32, 1+33, 2+34, ...)
- Address lines A/B/C/D/E select which pair (0-31)

SCAN_1_16 (32px high panel):
- 16 row pairs (rows 0+16, 1+17, 2+18, ...)
- Address lines A/B/C/D select which pair (0-15)
```

Non-standard panels may have shifted registers wired unusually, requiring `scan_wiring` remapping.

## License

MIT License - See repository [LICENSE](https://github.com/stuartparmenter/esp-hub75/blob/main/LICENSE) file for details.

## Support

For issues, examples, and detailed documentation, visit the main repository:
https://github.com/stuartparmenter/esp-hub75

Links

Maintainer

  • Stuart Parmenter <pavlov@pavlov.net>

License: MIT

To add this component to your project, run:

idf.py add-dependency "stuartparmenter/esp-hub75^0.1.0"

download archive

Stats

  • Archive size
    Archive size ~ 74.34 KB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

stuartparmenter/esp-hub75 version: 0.1.0
|