Hardware: Bigsign reference build
The bigsign is led-ticker’s reference build for tall multi-row
displays: a Raspberry Pi 5 driving eight P3 32x64 RGB matrix panels
arranged in a 2x4 vertical-serpentine layout for a 256x64 canvas. The hi-res font, hi-res emoji, and ScaledCanvas features were all developed against this build, and the dimensions match the bigsign-flavored examples in config/. Different panel counts or layouts will work — the
canvas math comes from pixel_mapper_config, not from a hardcoded shape — but
the BOM, wiring diagram, and Pi-5 tuning knobs below are what we built
and tested against. Drawing logic stays at a 16-tall logical canvas;
ScaledCanvas blows it up by a factor of four to fill the panel.
At a glance
Section titled “At a glance”- Raspberry Pi 5
- 8x P3 32x64 RGB matrix panels in a 2x4 vertical-serpentine layout = 256x64 logical canvas
default_scale = 4— every logical pixel becomes a 4x4 block on the real panel, content vertically centered- Drawing logic stays at “1x scale” with 16-tall content; the wrapper handles the expansion
- Same Docker image as the smallsign; the rgbmatrix library detects the SoC at runtime
Bill of materials
Section titled “Bill of materials”| Component | Quantity | Notes |
|---|---|---|
| Raspberry Pi 5 | 1 | 4 GB is plenty. A microSD card and a USB-C power supply for the Pi itself. |
| P3 32x64 RGB matrix panel | 8 | HUB75 panels with the standard 16-pin IDC connector. Matched panels render most consistently across the 2x4 grid. |
| Adafruit RGB matrix HAT | 1 | Sets hardware_mapping = "adafruit-hat" in the config. The bonnet works equivalently. |
| 5V power supply | 1 | Sized for eight P3 panels at full white — that’s serious current. A 5V / 30A+ supply with thick busbars is the comfortable headroom pick. |
| 16-pin IDC ribbon cables | 7+ | One between each pair of panels in chain order. The serpentine route bends across rows so cables need to reach across the layout. |
| Power pigtails / busbar | 1+ | Whatever your panels expect. Each panel has its own 2-pin power input — a busbar across the back of the frame keeps the wiring tidy. |
| Shroud / frame | 1 | A 2x4 grid of P3 panels is heavy and benefits from a rigid frame. Acrylic diffuser optional. |
Total cost (rough): ~$400–600 USD. The 8 P3 panels at $40–60 each are the dominant line item; the Pi 5 + HAT + a 30–60 A 5 V supply with thick busbars round out the build. Frame and shroud costs vary widely depending on whether you fabricate or buy. Prices change — verify before ordering. These were checked in early 2026.
The rgbmatrix library detects the SoC at runtime, so the same Docker image runs on both the smallsign (Pi 4 / BCM2711) and the bigsign (Pi 5 / RP1). No separate build, no per-Pi flag — the Pi-5-specific knobs in the next section are simply ignored on a Pi 4 and vice versa.
The 2x4 serpentine layout
Section titled “The 2x4 serpentine layout”Eight panels are tiled into a 2x4 grid (two rows of four). The data
chain enters at the bottom-right panel and runs vertically: each
column is wired bottom-to-top, with a diagonal jumper from the top of
each column to the bottom of the next column to its left. There is no
horizontal hop along the bottom row — the chain always climbs a column,
then crosses diagonally to the bottom of the next one. All panels are
installed upright (no 180° rotations). The pixel_mapper_config Remap string
in the config tells the rgbmatrix library how each panel’s chain index
maps to its physical (x, y) on the finished canvas.
col 1 col 2 col 3 col 4
top [8] [6] [4] [2] ↑ ↘ ↑ ↘ ↑ ↘ ↑bot [7] [5] [3] [1] ← data chain enters hereEach [N] is the panel’s position in the chain (1 = first, 8 = last).
The ↑ arrows show each column climbing bottom-to-top; the ↘ arrows
show the diagonal jumper from the top of one column to the bottom of
the next column to its left. If your top-row panels are physically
mounted upside down (some manufacturers ship them that way), swap each
top-row n for s in the Remap string — see the comment in the
config snippet below.
Config snippet
Section titled “Config snippet”The minimal [display] block for the bigsign — straight from
config/config.bigsign.example.toml:
[display]rows = 32cols = 64chain_length = 8parallel = 1# Remap each panel to its physical position. Serpentine chain order, all panels upright.pixel_mapper_config = "Remap:256,64|192,32n|192,0n|128,32n|128,0n|64,32n|64,0n|0,32n|0,0n"brightness = 60gpio_slowdown = 3 # bumped from 2 to pair with RIO mode (default); raise to 4-5 if flickerhardware_mapping = "adafruit-hat"default_scale = 4 # 4× scaling fills the 64-tall canvas# --- Pi 5 performance tuning ---pwm_bits = 8 # 11 (default) → 8 ≈ 8× faster refresh, slightly worse colorshow_refresh_rate = true # log measured refresh Hz to stderrThe 256x64 logical canvas comes from the pixel_mapper_config Remap header
(256,64), not from cols * chain_length directly — chain_length = 8 is just the
panel count for the rgbmatrix library; the mapper rearranges those
panels into the 2x4 grid.
Pi-5 tuning
Section titled “Pi-5 tuning”pwm_bits = 8
Section titled “pwm_bits = 8”PWM bit depth, default 11. Cutting to 8 buys roughly 8× faster refresh at the cost of a small drop in color depth. On a panel this big the refresh boost is the difference between visible scan-line flicker and a clean image, and the color hit is barely noticeable for text and pixel art. Leave it at 8 unless you’re displaying photos and care about smooth gradients.
RP1 backend (RIO vs PIO)
Section titled “RP1 backend (RIO vs PIO)”Pi-5 only. The RP1 SoC offers two GPIO drive modes:
- RIO mode (the library default —
rp1_pionot set) — faster refresh, higher CPU. - PIO mode (
rp1_pio = 1) — lower CPU, slower refresh.
The bigsign uses RIO — the default, so nothing to set. The Pi 5 has
cores to spare and the bigger panel wants every Hz it can get. Note
that RIO mode requires bumping gpio_slowdown (next section). Before
June 2026 the knob was called rp1_rio and PIO was the default; an
old rp1_rio = 1 line in a config is now ignored (with a startup
warning) and RIO applies anyway.
gpio_slowdown = 3
Section titled “gpio_slowdown = 3”GPIO timing slowdown, must pair with RIO mode (the default). The Pi 4 default of
2 is too aggressive when paired with RIO mode — start at 3, and
raise to 4 or 5 if you still see flicker. Higher values trade
refresh rate for stability, same tradeoff as on the smallsign just at
a higher floor.
Sharp edges
Section titled “Sharp edges”content_height cannot exceed 16 at scale = 4
Section titled “content_height cannot exceed 16 at scale = 4”The hard ceiling is content_height × scale ≤ panel_h_real. For the bigsign at scale = 4 and a 64-tall panel that’s content_height ≤ 16. Pushing above the ceiling makes the wrapper’s vertical offset go negative — the logical canvas is taller than the real panel — and the top + bottom rows of your content silently clip. BDF text is forgiving because cells sit near the vertical center, but hi-res emoji and large hi-res fonts surface the clip immediately (e.g. a hi-res :instagram: icon loses about 4 real px off the bottom). For per-row breathing room use text_y_offset on the widget, not a higher content_height.
The pixel_mapper_config Remap string is sensitive to chain order
Section titled “The pixel_mapper_config Remap string is sensitive to chain order”Every entry in the string must correspond to a specific panel position in your data chain — get the order wrong (or rotate one panel 180° to match the rest of the row) and you’ll see content split across panels or rendered upside down. The string in config/config.bigsign.example.toml matches a 2x4 serpentine starting at the bottom-right with all panels upright; if your panels are mounted differently, you’ll need to edit each entry’s orientation flag (n, s, e, w). Use panel-map to derive and verify the Remap string from your physical layout instead of hand-editing it.
Hi-res emoji only render at scale ≥ 2
Section titled “Hi-res emoji only render at scale ≥ 2”Slugs like :moon: and :instagram: have a 32x32 hi-res sprite in HIRES_REGISTRY that paints directly to the unwrapped real canvas, bypassing the wrapper’s 4x4 block expansion for sharper detail. On a smallsign at default_scale = 1 the renderer falls back to the 8x8 lo-res sprite automatically — same icon footprint, less detail. Don’t expect hi-res emoji output on the smallsign.
pixel_mapper_config for non-bigsign layouts
Section titled “pixel_mapper_config for non-bigsign layouts”The Remap string follows the format Remap:WIDTH,HEIGHT|x,yORIENT|... with one entry per panel in chain order. Orientations: n = normal, s = 180°, e = 270°, w = 90°, x = discard. The bigsign’s 2×4 vertical-serpentine chain is documented above. For simpler layouts:
- 2×2 grid (chain runs along the bottom row first, then top row right-to-left), all panels upright:
Remap:128,64|0,32n|64,32n|64,0n|0,0n - Single row of 4 panels, all upright, chain enters left:
Remap:256,32|0,0n|64,0n|128,0n|192,0n
If your physical layout doesn’t match the chain order from the data cable, every panel in the wrong position needs an entry in the Remap string. See the upstream hzeller/rpi-rgb-led-matrix README — the base for the jamesawesome/rpi-rgb-led-matrix fork led-ticker ships — for the full Remap reference.
Wiring or driver problem? Run panel-test first
Section titled “Wiring or driver problem? Run panel-test first”If your widgets render with the wrong colors, a garbled bottom half, only one panel lit, or visible flicker, the issue is most likely in the hardware layer (led_rgb_sequence, panel_type, chain_length, gpio_slowdown) — not in your config. The panel-test diagnostic isolates the hardware layer by painting flat R/G/B/W/B colors, so you can verify wiring and driver init before debugging widgets.
Reference config
Section titled “Reference config”A complete working config for this build, including the pixel_mapper_config string
and the Pi 5 RP1 tuning this hardware needs. Drop this into config/config.toml
and adjust the per-widget content.
Complete config.bigsign.example.toml (256×64 bigsign)
# led-ticker bigsign configuration — 256×64 canvas## 8× P3 32×64 panels in a 2×4 vertical-serpentine layout = 256×64 logical.# Driven by a Raspberry Pi 5 through an Adafruit RGB Matrix HAT.## Copy this to config.toml on the bigsign Pi and adjust the playlist for your# content. Full config-options reference:# https://docs.ledticker.dev/reference/config-options/
[display]rows = 32cols = 64chain_length = 8parallel = 1
# pixel_mapper_config tells the rgbmatrix library how each panel's position in the# data chain maps to its physical (x, y) on the finished canvas. The bigsign# chain enters at the bottom-right panel, climbs each column bottom-to-top,# then jumps diagonally to the bottom of the next column to its left. All# panels are installed upright (no 180° rotations). See the chain diagram and# instructions for deriving your own mapper string at:# https://docs.ledticker.dev/hardware/bigsign/## Format: Remap:WIDTH,HEIGHT|x,yORIENT|... (one entry per panel in chain order)# Orientations: n=normal, s=180°, e=270°, w=90°, x=discard.# If your top-row panels are physically flipped 180°, swap each top-row 'n'# for 's' (e.g. "192,0n" → "192,0s").pixel_mapper_config = "Remap:256,64|192,32n|192,0n|128,32n|128,0n|64,32n|64,0n|0,32n|0,0n"
brightness = 60
# default_scale = 4 means every widget draws at the standard 16-tall logical# canvas, and the ScaledCanvas wrapper expands every pixel to a 4×4 real block# to fill the 64-tall panel. Widget code never sees the 256×64 panel directly# — all drawing is in logical-pixel coordinates.default_scale = 4
hardware_mapping = "adafruit-hat"
# Pi 5 RP1 GPIO tuning.## The library's default Pi 5 backend is RIO (Registered IO) — faster# refresh, more CPU than PIO. Set rp1_pio = 1 only to trade refresh# speed for lower CPU.## gpio_slowdown = 3 pairs with RIO mode. The Pi 4 default of 2 is too# aggressive with RIO; raise to 4–5 if you still see flicker.## pwm_bits = 8 drops from the 11-bit default, buying roughly 8× faster# refresh at a small color-depth cost. Barely noticeable for text and pixel# art; only relevant if you're displaying photos with smooth gradients.gpio_slowdown = 3pwm_bits = 8show_refresh_rate = true # log measured refresh Hz to stderr; remove once stable
[title]delay = 5
# Global transition defaults. Full transition catalogue and per-family tuning# lives at https://docs.ledticker.dev/transitions/.[transitions]default = "push_left"duration = 0.5easing = "ease_out"between_sections = "dissolve"
# --- Playlist ---# Each [[playlist.section]] displays in order, then the whole playlist loops.# Sections, modes, and timing knobs: https://docs.ledticker.dev/concepts/sections-and-modes/
[[playlist.section]]mode = "ticker"loop_count = 1# scale inherits from display.default_scale (4)
[playlist.section.title]type = "message"text = "#DevOps News"font_color = "random"
[[playlist.section.widget]]type = "message"text = "May the uptime be with you!"
[[playlist.section.widget]]type = "message"text = "Always be shipping!"
# --- Countdowns at scale=2 ---# Dropping to scale=2 gives 32px-tall content centered with 16px black bands# top and bottom — a visual change of pace between sections.
[[playlist.section]]mode = "ticker"loop_count = 2scale = 2
[playlist.section.title]type = "message"text = "Count Downs"font_color = "random"
[[playlist.section.widget]]type = "countdown"text = "Days Until Summer"countdown_date = 2026-06-20
[[playlist.section.widget]]type = "countdown"text = "Days Until Fall"countdown_date = 2026-09-22
# --- GIF / image playback ---# The gif widget plays an animated GIF or static image at native physical# resolution (256×64), using the same fit/text-overlay knobs as the image# widget. Full reference: https://docs.ledticker.dev/widgets/gif/## `path` resolves relative to this config file's directory. Drop assets# under `<config_dir>/assets/` and reference by relative path.## [[playlist.section]]# mode = "slideshow"# loop_count = 2## [[playlist.section.widget]]# type = "gif"# path = "assets/phoenix.gif"# fit = "pillarbox"