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
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
//! Framebuffer implementation for HUB75 LED matrix displays.
//!
//! ## How HUB75 LED Displays Work
//!
//! HUB75 RGB LED matrix panels are scanned, time-multiplexed displays that behave like a long
//! daisy-chained shift register rather than a random-access framebuffer.
//!
//! ### Signal names
//! - **R1 G1 B1 / R2 G2 B2** – Serial colour data for the upper and lower halves of the active scan line
//! - **CLK** – Shift-register clock; every rising edge pushes the six colour bits one pixel to the right
//! - **LAT / STB** – Latch; copies the shift-register contents to the LED drivers for the row currently selected by the address lines
//! - **OE** – Output-Enable (active LOW): LEDs are lit while OE is LOW and blanked when it is HIGH
//! - **A B C D (E)** – Row-address select lines (choose which pair of rows is lit)
//! - **VCC & GND** – 5 V power for panel logic and LED drivers
//!
//! ### Row-pair scanning workflow (e.g., 1/16-scan panel)
//! 1. While the panel is still displaying row pair N − 1, the controller shifts the six-bit colour data for row pair N into the chain (OE remains LOW so row N − 1 stays visible).
//! 2. After the last pixel is clocked in, the controller raises OE HIGH to blank the LEDs.
//! 3. With the panel blanked, it first changes the address lines to select row pair N, lets them settle for a few nanoseconds, and **then** pulses LAT to latch the freshly shifted data into the output drivers for that newly selected row.
//! 4. OE is immediately driven LOW again, lighting row pair N.
//! 5. Steps 1–4 repeat for every row pair fast enough (hundreds of Hz) that the human eye sees a steady image.
//! - If the first row pair is being shifted, the panel continues showing the last row pair of the previous frame until the first blank-address-latch sequence occurs.
//!
//! ### Brightness and colour depth (Binary Code Modulation)
//! - Full colour is typically achieved using **Binary Code Modulation (BCM)**, also known as *Bit-Angle Modulation (BAM)*. Each bit-plane is displayed for a period proportional to its binary weight (1, 2, 4, 8 …), yielding 2ⁿ intensity levels per channel. See [Batsocks – LED dimming using Binary Code Modulation](https://www.batsocks.co.uk/readme/art_bcm_1.htm) for a deeper explanation.
//! - Because each LED is on for only a fraction of the total frame time, the driver can use relatively high peak currents without overheating while average brightness is preserved.
//!
//! ### Implications for software / hardware drivers
//! - You don't simply "write a pixel" once; you must continuously stream the complete refresh data at MHz-range clock rates.
//! - Precise timing of CLK, OE, address lines, and LAT is critical—especially the order: blank (OE HIGH) → set address → latch → un-blank (OE LOW).
//! - Microcontrollers typically employ DMA, PIO, or parallel GPIO tricks, and FPGAs use dedicated logic, to sustain the data throughput while leaving processing resources free.
//!
//! In short: a HUB75 panel is a high-speed shift-register chain that relies on rapid row-pair scanning and **Binary Code Modulation (BCM)** to create a bright, full-colour image. Keeping OE LOW almost all the time—blanking only long enough to change the address and pulse LAT—maximises brightness without visible artefacts.
//!
//! ## Framebuffer Implementations
//!
//! Four framebuffer layouts are provided, covering two hardware variants and
//! two BCM strategies:
//!
//! | Module | Word size | External latch? | BCM strategy |
//! |--------|-----------|-----------------|--------------|
//! | [`plain`] | 16-bit | No | Threshold frames |
//! | [`latched`] | 8-bit | Yes | Threshold frames |
//! | [`bitplane::plain`] | 16-bit | No | True bitplane |
//! | [`bitplane::latched`] | 8-bit | Yes | True bitplane |
//!
//! ### Plain vs. Latched
//! - **Plain** packs all HUB75 signals (address, latch, OE, colour) into each
//! 16-bit word. No extra hardware beyond a parallel output peripheral.
//! - **Latched** uses 8-bit words with a separate external latch circuit to
//! hold the row address and gate the pixel clock, halving per-entry memory.
//!
//! ### Threshold Frames vs. True Bitplane
//! The two BCM strategies differ in how they store colour data and how the DMA
//! chain must be configured to render it.
//!
//! **Threshold frames** (`plain`, `latched`) -- the driver compares each
//! channel's 8-bit value against per-frame thresholds and stores the resulting
//! on/off bits. For a colour depth of `BITS`, this produces
//! `2^BITS - 1` frames. Frame *n* is displayed for a duration proportional to
//! `2^n`. Memory grows exponentially with colour depth.
//!
//! **True bitplane** (`bitplane::plain`, `bitplane::latched`) -- each of
//! `PLANES` planes (typically 8) stores one bit of every colour channel
//! directly. To render, configure the DMA descriptor chain so that each
//! plane's data is output `2^(7 - plane_index)` times (plane 0 = MSB is
//! scanned 128 times, plane 7 = LSB is scanned once). Memory scales linearly
//! with the number of planes.
//!
//! All four variants have configurable row and column dimensions, support
//! `embedded-graphics` via the `DrawTarget` trait, and expose per-plane
//! pointers for DMA setup through the [`FrameBuffer`] trait.
//!
//! ## Multiple Panels
//! Use [`tiling::TiledFrameBuffer`] to drive several HUB75 panels as one large
//! virtual display. Combine it with a pixel-remapping policy such as
//! [`tiling::ChainTopRightDown`] and any of the framebuffer flavours above.
//! The wrapper exposes a single `embedded-graphics` canvas, so for example a
//! 3 × 3 stack of 64 × 32 panels simply looks like a 192 × 96 screen while
//! all coordinate translation happens transparently.
//!
//! ## Available Feature Flags
//!
//! ### `skip-black-pixels` Feature (disabled by default)
//! When enabled, calls to `set_pixel()` with `Color::BLACK` return early without
//! writing to the framebuffer. This provides a significant performance boost for
//! UI applications that frequently draw black pixels (backgrounds, clearing, etc.)
//! by assuming the framebuffer was already cleared.
//!
//! **Important**: This optimization assumes that black pixels represent "no change"
//! rather than "explicitly set to black". By default, black pixels are written
//! normally to ensure correct overwrite behavior. To enable the optimization:
//!
//! ```toml
//! [dependencies]
//! hub75-framebuffer = { version = "0.10.0", features = ["skip-black-pixels"] }
//! ```
//!
//! ### `esp32-ordering` Feature (required for original ESP32 only)
//! **Required** when targeting the original ESP32 chip (not ESP32-S3 or other variants).
//! This feature adjusts byte ordering to accommodate the quirky requirements of the
//! ESP32's I²S peripheral in 8-bit and 16-bit modes. The original ESP32 has different
//! byte ordering requirements compared to other ESP32 variants (S2, S3, C3, etc.),
//! which do **not** need this feature.
//!
//! ```toml
//! [dependencies]
//! hub75-framebuffer = { version = "0.10.0", features = ["esp32-ordering"] }
//! ```
//!
//! ### `tail-closes-latch` Feature (plain framebuffers only)
//! Appends a single extra "tail" word at the end of the DMA buffer that drives the
//! LATCH signal LOW (de-asserted) on the final clock edge. Without this feature the
//! last word in each row asserts LATCH HIGH to latch shifted data into the LED
//! drivers, and the GPIO pins remain in that state after the DMA transfer completes.
//! Some hardware configurations (e.g. free-running DMA loops or peripherals that
//! continue clocking after the descriptor chain ends) can re-latch stale data or
//! glitch if LATCH is left asserted.
//!
//! Enabling `tail-closes-latch` adds one 16-bit `Entry` (for `plain`) or one entry
//! per bit-plane (for `bitplane::plain`) that parks the bus with LATCH=0 and
//! OE=BLANK, cleanly terminating the transfer. The cost is a single extra word per
//! DMA chunk, which is negligible compared to the frame data.
//!
//! ```toml
//! [dependencies]
//! hub75-framebuffer = { version = "0.10.0", features = ["tail-closes-latch"] }
//! ```
//!
//! ### Blanking delay features (`lead-blank-*` / `trail-blank-*`)
//!
//! Control the number of pixel-clock cycles of blanking (`OE` HIGH) inserted
//! around row-address changes. The lead blank controls how many cycles the
//! output is blanked *before* the row address is changed, and the trail blank
//! controls blanking *after* the row address is changed. Together they give
//! the address lines time to settle and prevent ghosting or "bleeding"
//! artifacts caused by the panel briefly displaying data on the wrong row
//! during the transition.
//!
//! | Feature | Blanking cycles | Position |
//! |------------------|-----------------|-----------------------|
//! | *(none)* | 1 (default) | before & after change |
//! | `lead-blank-1` | 1 | before address change |
//! | `lead-blank-2` | 2 | before address change |
//! | `lead-blank-4` | 4 | before address change |
//! | `lead-blank-8` | 8 | before address change |
//! | `lead-blank-16` | 16 | before address change |
//! | `trail-blank-1` | 1 | after address change |
//! | `trail-blank-2` | 2 | after address change |
//! | `trail-blank-4` | 4 | after address change |
//! | `trail-blank-8` | 8 | after address change |
//! | `trail-blank-16` | 16 | after address change |
//!
//! Higher values reduce ghosting at the cost of slightly less brightness (the
//! LEDs are on for less time per scan line). Start with the default and increase
//! only if you observe row-transition artifacts on your particular panel
//! hardware.
//!
//! ```toml
//! [dependencies]
//! hub75-framebuffer = { version = "0.10.0", features = ["lead-blank-4", "trail-blank-2"] }
//! ```
//!
//! **Note:** At most one `lead-blank-*` and one `trail-blank-*` feature may be
//! enabled at a time. If multiple are enabled for the same edge, compile-time cfg
//! conflicts will result.
//!
//! ### `defmt` Feature
//! Implements `defmt::Format` for framebuffer types so they can be emitted with
//! the `defmt` logging framework. No functional changes; purely adds a trait impl.
//!
//! ### `doc-images` Feature
//! Embeds documentation images when building docs on docs.rs. Not needed for
//! normal usage.
use DrawTarget;
use Rgb888;
use Point;
/// Color type used in the framebuffer
pub type Color = Rgb888;
/// Word size configuration for the framebuffer
/// Computes the NROWS value from ROWS for `DmaFrameBuffer`
///
/// # Arguments
///
/// * `rows` - Total number of rows in the display
///
/// # Returns
///
/// Number of rows needed internally for `DmaFrameBuffer`
pub const
/// Computes the number of frames needed for a given bit depth
///
/// This is used to determine how many frames are needed to achieve
/// the desired color depth through Binary Code Modulation (BCM).
///
/// # Arguments
///
/// * `bits` - Number of bits per color channel
///
/// # Returns
///
/// Number of frames required for the given bit depth
pub const
/// Trait for read-only framebuffers.
/// Trait for mutable framebuffers
///
/// This trait extends `FrameBuffer` with the ability to draw to the framebuffer
/// using the `embedded_graphics` drawing primitives.
/// Trait for all operations a user may want to call on a framebuffer.