1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
//! # 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:
//!
//! ```rust,no_run
//! ```
//!
//! ## Crate Features
//!
//! - `esp32`: Enable support for the ESP32
//! - `esp32s3`: Enable support for the ESP32-S3
//! - `esp32c5`: Enable support for the ESP32-C5
//! - `esp32c6`: Enable support for the ESP32-C6
//! - `defmt`: Enable logging with `defmt`
//! - `log`: Enable logging with the `log` crate
//! - `invert-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 (implies `full-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 both `frame_count()` and the
//! completion signal for [`Hub75Swap::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 the `hub75-framebuffer` crate, enabling an
//! optimization that skips writing black pixels to the framebuffer.
//! - `tail-closes-latch`: Forwards to the `hub75-framebuffer` crate. Appends a
//! tail word at the end of each DMA buffer (`plain` framebuffers) 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 to
//! `hub75-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 to `hub75-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.
use AnyPin;
pub use hub75_framebuffer as framebuffer;
pub use static_cell;
pub
pub use Hub75;
pub use Hub75Swap;
/// The color type used by the HUB75 driver.
pub use Color;
compile_error!;
pub const MAX_DMA_CHUNK_SIZE: usize = CHUNK_SIZE;
/// Computes the number of DMA descriptors required for a given framebuffer
/// configuration.
///
/// Users should prefer the [`hub75_dma_descriptors!`] macro which calls this
/// automatically. This function is public for advanced use cases.
///
/// # Arguments
/// * `bcm_chunk_count` - Number of BCM chunks (from
/// `FBType::bcm_chunk_count()`)
/// * `bcm_chunk_bytes` - Byte size of one BCM chunk (from
/// `FBType::bcm_chunk_bytes()`)
pub const
/// Allocates static DMA descriptors sized for the given framebuffer type.
///
/// This macro computes the required number of DMA descriptors at compile time
/// and allocates them in a static cell. It returns `&'static mut
/// [DmaDescriptor]` suitable for passing to [`Hub75::new`] or
/// [`Hub75::new_async`].
///
/// # Example
/// ```rust,ignore
/// type FBType = DmaFrameBuffer<NROWS, COLS, PLANES>;
/// let tx_descriptors = esp_hub75::hub75_dma_descriptors!(FBType);
/// ```
/// Pin configuration for a HUB75 panel without an external address latch.
///
/// This configuration requires 16 bits of data per pixel transfer, as the row
/// address lines are driven directly along with the color data.
/// Pin configuration for a HUB75 panel with an external address latch.
///
/// This configuration is more memory-efficient, requiring only 8 bits of data
/// per pixel transfer. The row address is set once per row and held by an
/// external latch on the controller board. For an example of a latch circuit,
/// see the [`hub75-framebuffer` crate documentation](https://crates.io/crates/hub75-framebuffer)
/// and its [GitHub repository](https://github.com/liebman/hub75-framebuffer).
/// A trait for applying a set of HUB75 pins onto the specific ESP32 peripheral
///
/// This allows the driver to abstract over the differences in pin
/// configurations between peripherals (I2S, LCD-CAM, PARL_IO) and between
/// direct-drive (16-bit) and latched (8-bit) HUB75 controller boards.
/// A trait for converting a set of HUB75 pins into the required format for a
/// specific ESP32 peripheral.
///
/// This allows the driver to abstract over the differences in pin
/// configurations between peripherals (I2S, LCD-CAM, PARL_IO) and between
/// direct-drive (16-bit) and latched (8-bit) HUB75 controller boards.
///
/// # Type Parameters
/// * `T` - The target pin configuration type for the specific peripheral.
// ---------------------------------------------------------------------------
// GDMA channel number trait (ESP32-S3 / C6 / C5)
// ---------------------------------------------------------------------------
/// Maps a DMA channel singleton to its numeric index so that the driver can
/// configure GDMA interrupts without scanning registers at runtime.
///
/// This trait is implemented automatically for every `DMA_CHn` type exported
/// by `esp-hal`. Users never need to name it directly — it is satisfied
/// implicitly when passing a DMA channel peripheral to [`Hub75::new`].
/// Represents errors that can occur during HUB75 driver operations.
///
/// This enum consolidates errors from the underlying `esp-hal` DMA, peripheral,
/// and buffer management modules into a single type for easier error handling.