Skip to content

Change Request / Roadmap Proposal - Native Hub75 Support #102

Description

@wompslab

Currently

Product supports Hub75 panels via a PanelCard driver module that makes use of 5A-75b/e cards, this is highly performant when driven by a MM host with sufficient resources - (e.g. a Rockchip based NanoPi R28S will run complex power fx at around 15-20fps on a 256x256 panel, with 8x virtual layer) - similar performance can be achieved via desktop, however performance on other devices, (ESP32-S31 being the reference device) is around 10-20x lower. In addition the capability to allow an ESP32-P4 device to make use of an additional USB GB Ethernet interface is non-trivial, which is a significant blocker as it will prevent it working as a network receiver, which was a key use case - this case is now met (in terms of parity of cost, and exceptional performance of small, cheap rockchip devices).

The colorlight panel cards require configuration via LEDvision and are only really beneficial when running panels greater 16384 pixels, which leaves a gap in the features that is currently met by WLED (both main and Moon Modules.

Native Support for Hub75 Phase 1 - Parity with WLED-MM / MoonModules

  • New driver for 'Direct Panel Driver' (name tbc)
  • Driver will allow users select the pin mapping with the UI
  • 1x Hub75 output port capable of directly driving a range of 'Normal' panels (i.e. not PWM, but inclusive of sPWM panels -e.g those referenced below)
  • User will need UI features to set panel dimensions and arrangement (WLED-main does this in sensible manner)

Important

The ESP32-P4-Nano should be a great candidate for driving panels directly given it's extensive GPIO and PPA functions (see Espressif reference solution below)

Other considerations:

  • It would be beneficial if a reasonable range of common ESP32 build environments (as per WLED/WLED-MM) could be pre-configured - this would help with adoption and wider acceptance. Suggested board configs: Adafruit S3 Portal, Waveshare RGB Matrix, Lilygo ESP32-S3 (Moonhub75 pin config), ESP32-S3-16MB-PSRAM (all currently supported by WLED).

Summary:

The main delta here is that ProjectMM will need to learn how to drive hub75n panels rather than generate colorlight packets - this will require user input to provide more detail about panel type, layout and configuration - FPP is another pre-extant product that could be used as inspiration.

The goal is to make use of the sigificant performance gains that we have seen ProjectMM give to older, well established MCUs when driving non-Hub75 layouts with large quantities of pixels and apply that to smaller panel count/pixel layous w

Reference and Inspiration:

https://github.com/wled/WLED/releases/tag/v16.0.1 (v 16.0.0 onwards has native Hub75 support)

https://github.com/esphome-libs/esp-hub75/tree/main/components/hub75/src (ESPHome hub75 compent)

MoonModules/WLED-MM@778ae55#diff-bd8d5165aa932055149d4e0db7571029b97f2a860f275ddd079bc591da35bf19 (change to support sPWM panel)

https://github.com/espressif/esp-idf/tree/c712a0dde385d659a1470a136251980d31a70bc1/examples/peripherals/parlio/parlio_tx/advanced_rgb_led_matrix (Espressif reference for ESP32-P4 PARLIO Hub75 implementation)

Native Support for Hub75 Phase 2 - Support for a wider range of PWM panels

Full support (as much as is possible) for PWM panels

  • This type of panel can provide increased performance for lower cost and are increasingly available
  • Traditionally support for this panels has not been available without using commercial equipment, however there are some libraries that offer support to an extent:

Reference and Inspiration:

https://github.com/hzeller/rpi-rgb-led-matrix (most mature and highly performant, is based on driving panels via extensive Raspberrr pi style GPIO.

https://github.com/mrcodetastic/ESP32-PWM-MatrixPanel-DMA (extension / branch of Codetastic library to drive PWM panels.

Activity

  1. MoonModules commented on Sep 15, 2026

    @MoonModules
    Owner

    Consider it done ;-)

    Image

    predefined pin config on known pcbs:

    Image
  2. ewowi commented on Sep 15, 2026

    @ewowi
    Collaborator

    Phase 1 is implemented and on the next-iteration branch (b90e265). Not yet merged, and not yet run on a physical panel — details on that below.

    What's built

    A Hub75Driver that drives a panel directly from the board's GPIO, sitting alongside the existing PanelCard driver rather than replacing it. Panel geometry comes from the Layout (PanelLayout for one panel, PanelsLayout for a tiling), so there are no geometry controls on the driver — that separation already existed and HUB75 didn't need to re-litigate it.

    The card carries a board dropdown that prefills the wiring: MoonHub75, Adafruit MatrixPortal S3, Waveshare RGB Matrix, plus per-chip generic sets for someone on a bare module. Pins are hidden for the published maps (those lines are soldered) and editable for the generic sets and Custom.

    Two findings that may be useful regardless of this implementation

    PARLIO caps out at ~4 panels. The Espressif reference linked in the issue is the right shape, but PER_FRAME is 65,535 bytes on every PARLIO-capable target, and a HUB75 frame is (height/2) x width x bitDepth:

    Panels 6-bit 8-bit PARLIO
    1 (64x64) 12,480 B 16,640 B fits
    4 (128x128) 49,344 B 65,792 B 6-bit only — 8-bit misses by 257 bytes
    16 (256x256) 196,800 B 262,400 B no

    So the P4 is a great Phase 1 candidate for small walls, and i80/LCD_CAM (whose DMA reaches PSRAM) is the route that scales. Both backends are implemented; the user picks, because a P4 has one of each peripheral and someone driving WS2812 strips from PARLIO needs the panel on LCD_CAM.

    The classic ESP32 is out on pins, before memory is even a question. One HUB75 port needs 12-14 GPIOs and the classic has 13 usable outputs, leaving nothing for a strand, a button or a mic. Verified that it adds zero bytes to a classic build.

    What is not done

    Phase 2 (PWM panels) is deliberately out of scope — different drive model, needs per-panel calibration, and bundling it would have made Phase 1 unshippable.

    The board presets exist in the driver but not yet as installer catalog entries, so you cannot pick "MatrixPortal S3" at flash time.

    And the honest part: this has never lit a panel

    The bit-plane encoder is pinned by host tests (8 cases, 55 assertions, covering the row-address-per-column-byte rule and the four-rows-per-scan-step case), and it compiles for S3, P4 and S31. That is not the same as light on a wall.

    The driver reports its measured refresh in its status line for exactly this reason. If you are able to try it, a report of geometry + bit depth + that number is what would make it useful — far more than "it flickers". Predicted ceilings are in the docs to compare against.

    The Waveshare pin map came from WLED's WAVESHARE_S3_PINOUT; MoonHub75's from the MoonModules hardware repo; MatrixPortal's from Adafruit's own board definition. One trap worth flagging for anyone transcribing these: WLED's array is {R1,G1,B1,R2,G2,B2,A,B,C,D,E,LAT,OE,CLK} — latch and OE come before the clock.

  3. ewowi commented on Sep 17, 2026

    @ewowi
    Collaborator

    Phase 1 is on main as of 934c93c4, and the driver has had its first real-hardware report.

    What changed since the last update

    Two defects that each stopped a panel from looking right, both found the same way: someone ran it on a wall.

    The encoder sent one byte per pixel clock, on a sixteen-bit bus. The row address, latch and output-enable lines all sit above bit 7, so none of them reached the ribbon. The driver as first committed could not light a panel at all. It writes the whole word now.

    The frame did not end dark, so row 0 lit twice. The peripheral loops the frame buffer continuously; there is no per-frame transmit. Every row is blanked by the word that closes it, but the last row of the last plane has no next row inside the frame, so the wrap landed on row 0's first column and gave that row a second lit window every frame. On a wall that reads as one row brighter than all the others. The frame now ends with a word holding output-enable high and no address bits, and every row gets exactly its own window.

    That one is worth flagging beyond this project: it is a property of any continuously-looping HUB75 DMA buffer, not of this implementation, and it is invisible in a simulator or a host test unless you specifically assert on the wrap point.

    Corrected numbers

    The dark tail word adds one slot per frame, so the byte counts in my earlier comment are two bytes low:

    Panels 6-bit 8-bit PARLIO
    1 (64x64) 12,480 B 16,642 B fits
    4 (128x128) 49,344 B 65,794 B 6-bit only, 8-bit misses by 259 bytes
    16 (256x256) 196,800 B 262,402 B no

    The conclusion is unchanged: PARLIO for small walls, i80/LCD_CAM for anything that scales, and the user picks because a P4 has one of each block.

    Still open, and where help is most useful

    Brightness is not yet weighted. A HUB75 pixel is a switch, so intensity has to come from time: bit plane p should be displayed for 2^p time units through the output-enable window. That is not built, which is why bitDepth is capped at 4. Every plane currently shows for the same duration, so bit 3 lights as long as bit 0 and a fifth plane would buy nothing visible. The mechanism is backlogged by name (repeat DMA descriptors against one stored plane, rather than 2^p copies of it, which for one 64x64 panel at 8-bit would be 1,060,800 bytes against 33,280).

    So if you try this: the panel will look flat in the upper bits, and that is known rather than a fault to report.

    What is worth reporting, from the driver's own status line and read-only refresh control: geometry, scan rate, bit depth, the peripheral you selected, and the measured Hz. A number there is far more actionable than "it flickers". Predicted ceilings are in the driver docs to compare against.

    Phase 2 (PWM panels) remains deliberately out of scope: different drive model, needs per-panel calibration.

  4. ewowi commented on Oct 5, 2026

    @ewowi
    Collaborator

    Update: one more fix from a real panel since the last report. The row address on each data word now names the row held in the output latch, the previous one; naming the current row lit every row with its neighbour's data, which showed as one bright line and the picture one row out of place. There is also a clockEdge control now, for panel chips that sample the shift clock on the falling edge.

    Phase 1 is on main and in latest (6.1.0-dev.24). What would make it proven is a 128×128 wall at 8-bit depth, flicker-free, reported by a tester. The driver's status line shows the geometry, the bit depth and the refresh it achieves in Hz, which is what such a report needs. Could you try it on your panels? Keeping this open until then.

  5. ewowi commented on Oct 6, 2026

    @ewowi
    Collaborator

    Update: Phase 1 now runs on a panel on our own bench, a MoonHub75 (LILYGO T7-S3) driving a 64x64 1/32-scan panel, compared side by side with WLED-MM on the same board. That bench run found and fixed three things, two of them matching earlier reports:

    • The last column was dark, whatever the effect or clock edge, with the picture one column to the left. Every word on the bus is a clock, and a separate latch word shifted a 65th value into a 64-pixel row. The latch now rides the last column's own word. This is the "final column dark" a tester reported.
    • A ghost of the last row on the middle row, and flashing. The peripheral idles its data lines low between scans, and the panel's output enable is active low, so the idle bus lit a row. The output-enable line is now inverted at the pin, so an idle bus is dark.
    • Brightness was coarse and went black at low settings. The bit planes are now weighted (each lit twice as long as the one below) and brightness shortens the lit time instead of scaling the colors. That gives 64 ordered levels per channel at the new default of 6-bit, a smooth slider, and effect details that were invisible before. Measured: 1,508 Hz refresh at 6-bit.

    Settings apply live: changing the depth or the clock edge re-initializes the panel without a reboot, which WLED-MM cannot do on an S3.

    The board catalog gains MoonHub75 and LILYGO T7-S3 entries, so a MoonHub75 is set up from the installer's board picker. The driver's board select already carries the MoonHub75, Adafruit MatrixPortal S3 and Waveshare RGB Matrix pin maps.

    This lands in main and the latest release in the coming days (PR #129).

    With Phase 1 done and verified on hardware, I'm closing this issue. Phase 2 (PWM panels) is a separate project, and the remaining refinements are in the backlog: an init sequence for FM6124-class panels, four-scan panels, and repeating the upper bit planes for full top brightness and 8-bit depth. Please reopen, or open a new issue, when you run it on your walls and something needs attention: the driver's refresh plus your geometry is what makes a report useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions