Skip to main content

hub75_framebuffer/
plain.rs

1//! DMA-friendly framebuffer implementation for HUB75 LED panels.
2//!
3//! This module provides a framebuffer implementation with memory
4//! layout optimized for efficient transfer to HUB75 LED panels. The data is
5//! structured for direct signal mapping, making it ideal for DMA transfers but
6//! also suitable for programmatic transfer. It supports RGB color and brightness
7//! control through multiple frames using Binary Code Modulation (BCM).
8//!
9//! # Hardware Requirements
10//! This implementation can be used by any microcontroller that has a peripheral
11//! capable of outputting a clock signal and 16 bits (though only 14 bits are
12//! actually needed) in parallel. The data is structured to directly match the
13//! HUB75 connector signals, making it suitable for various parallel output
14//! peripherals.
15//!
16//! # Features
17//! - Memory layout optimized for efficient data transfer
18//! - Direct signal mapping to HUB75 connector signals
19//! - Support for RGB color with brightness control
20//! - Multiple frame buffers for Binary Code Modulation (BCM)
21//! - Integration with embedded-graphics for easy drawing
22//!
23//! # Brightness Control
24//! Brightness is controlled through Binary Code Modulation (BCM):
25//! - The number of brightness levels is determined by the `BITS` parameter
26//! - Each additional bit doubles the number of brightness levels
27//! - More bits provide better brightness resolution but require more memory
28//! - Memory usage grows exponentially with the number of bits: `(2^BITS)-1`
29//!   frames
30//! - Example: 8 bits = 256 levels, 4 bits = 16 levels
31//!
32//! # Memory Usage
33//! The framebuffer's memory usage is determined by:
34//! - Panel size (ROWS × COLS)
35//! - Number of brightness bits (BITS)
36//! - Memory grows exponentially with bits: `(2^BITS)-1` frames
37//! - 16-bit entries provide direct signal mapping but use more memory
38//!
39//! # Example
40//! ```rust,no_run
41//! use embedded_graphics::pixelcolor::RgbColor;
42//! use embedded_graphics::prelude::*;
43//! use embedded_graphics::primitives::Circle;
44//! use embedded_graphics::primitives::Rectangle;
45//! use hub75_framebuffer::compute_frame_count;
46//! use hub75_framebuffer::compute_rows;
47//! use hub75_framebuffer::Color;
48//! use hub75_framebuffer::plain::DmaFrameBuffer;
49//! use embedded_graphics::primitives::PrimitiveStyle;
50//!
51//! // Create a framebuffer for a 64x32 panel with 3-bit color depth
52//! const ROWS: usize = 32;
53//! const COLS: usize = 64;
54//! const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
55//! const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
56//! const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
57//!
58//! let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
59//!
60//! // Draw a red rectangle
61//! Rectangle::new(Point::new(10, 10), Size::new(20, 20))
62//!     .into_styled(PrimitiveStyle::with_fill(Color::RED))
63//!     .draw(&mut framebuffer)
64//!     .unwrap();
65//!
66//! // Draw a blue circle
67//! Circle::new(Point::new(40, 20), 10)
68//!     .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
69//!     .draw(&mut framebuffer)
70//!     .unwrap();
71//! ```
72//!
73//! # Implementation Details
74//! The framebuffer is organized to directly match the HUB75 connector signals:
75//! - Each 16-bit word maps directly to the HUB75 control signals
76//! - Color data (R, G, B) for two sub-pixels is stored in dedicated bits
77//! - Control signals (output enable, latch, address) are mapped to specific
78//!   bits
79//! - Multiple frames are used to achieve Binary Code Modulation (BCM)
80//! - DMA transfers the data directly to the panel without any transformation
81//!
82//! # HUB75 Signal Bit Mapping
83//! Each 16-bit `Entry` represents the logic levels that will be driven onto the HUB75
84//! bus during a single pixel-clock cycle. Every panel signal—apart from the clock
85//! (`CLK`) itself—occupies a dedicated bit, allowing the word to be streamed via DMA
86//! with zero run-time manipulation.
87//!
88//! ```text
89//! 15 ─ Dummy2   (spare)
90//! 14 ─ B2       Blue  – lower half of the panel
91//! 13 ─ G2       Green – lower half of the panel
92//! 12 ─ R2       Red   – lower half of the panel
93//! 11 ─ B1       Blue  – upper half of the panel
94//! 10 ─ G1       Green – upper half of the panel
95//!  9 ─ R1       Red   – upper half of the panel
96//!  8 ─ OE       Output-Enable / Blank
97//!  7 ─ Dummy1   (spare)
98//!  6 ─ Dummy0   (spare)
99//!  5 ─ LAT      Latch / STB
100//! 4-0 ─ A..E    Row address lines
101//! ```
102//!
103//! The pixel clock is generated by the peripheral that owns the DMA stream and
104//! is therefore **not** part of the 16-bit word stored in the framebuffer.
105//!
106//! # Binary Code Modulation (BCM) Frames
107//! Brightness is achieved with Binary-Code-Modulation as outlined in
108//! <https://www.batsocks.co.uk/readme/art_bcm_1.htm>. For a colour depth of
109//! `BITS`, the driver allocates `FRAME_COUNT = 2^BITS - 1` frames. Frame *n*
110//! (0-based) is displayed for a duration proportional to `2^n`, mirroring the
111//! weight of that bit in a binary number.
112//!
113//! When a pixel is written its 8-bit RGB components are compared against a
114//! per-frame threshold:
115//!
116//! ```text
117//! brightness_step = 256 / 2^BITS
118//! threshold_n     = (n + 1) * brightness_step
119//! ```
120//!
121//! If a channel's value is greater than or equal to `threshold_n` the
122//! corresponding colour bit is set in frame *n*. Streaming the frames from the
123//! least-significant (shortest) to the most-significant (longest) time-slot
124//! produces the correct 8-bit brightness while keeping the refresh routine
125//! trivial.
126//!
127//! # Safety
128//! This implementation uses unsafe code for DMA operations. The framebuffer
129//! must be properly aligned in memory and the DMA configuration must match the
130//! buffer layout.
131
132use core::convert::Infallible;
133
134use crate::{FrameBufferOperations, MutableFrameBuffer};
135use bitfield::bitfield;
136use embedded_dma::ReadBuffer;
137use embedded_graphics::pixelcolor::RgbColor;
138use embedded_graphics::prelude::Point;
139
140use super::Color;
141use super::FrameBuffer;
142use super::WordSize;
143
144const BLANKING_DELAY: usize = 1;
145
146/// Creates a pre-computed data template for a row with the specified addresses.
147/// This template contains all the timing and control signals but no pixel data.
148#[inline]
149const fn make_data_template<const COLS: usize>(addr: u8, prev_addr: u8) -> [Entry; COLS] {
150    let mut data = [Entry::new(); COLS];
151    let mut i = 0;
152
153    while i < COLS {
154        let mut entry = Entry::new();
155        entry.0 = prev_addr as u16;
156
157        // Apply timing control based on position
158        if i == 1 {
159            entry.0 |= 0b1_0000_0000; // set output_enable bit
160        } else if i == COLS - BLANKING_DELAY - 1 {
161            // output_enable already false from initialization
162        } else if i == COLS - 1 {
163            entry.0 |= 0b0010_0000; // set latch bit
164            entry.0 = (entry.0 & !0b0001_1111) | (addr as u16); // set new address
165        } else if i > 1 && i < COLS - BLANKING_DELAY - 1 {
166            entry.0 |= 0b1_0000_0000; // set output_enable bit
167        }
168
169        data[map_index(i)] = entry;
170        i += 1;
171    }
172
173    data
174}
175
176bitfield! {
177    /// A 16-bit word representing the HUB75 control signals for a single pixel.
178    ///
179    /// This structure directly maps to the HUB75 connector signals:
180    /// - RGB color data for two sub-pixels (color0 and color1)
181    /// - Panel control signals (output enable, latch, address)
182    /// - Dummy bits for timing alignment
183    ///
184    /// The bit layout matches the HUB75 connector signals:
185    /// - Bit 15: Dummy bit 2
186    /// - Bit 14: Blue channel for color1
187    /// - Bit 13: Green channel for color1
188    /// - Bit 12: Red channel for color1
189    /// - Bit 11: Blue channel for color0
190    /// - Bit 10: Green channel for color0
191    /// - Bit 9: Red channel for color0
192    /// - Bit 8: Output enable
193    /// - Bit 7: Dummy bit 1
194    /// - Bit 6: Dummy bit 0
195    /// - Bit 5: Latch signal
196    /// - Bits 4-0: Row address
197    #[derive(Clone, Copy, Default, PartialEq)]
198    #[repr(transparent)]
199    struct Entry(u16);
200    dummy2, set_dummy2: 15;
201    blu2, set_blu2: 14;
202    grn2, set_grn2: 13;
203    red2, set_red2: 12;
204    blu1, set_blu1: 11;
205    grn1, set_grn1: 10;
206    red1, set_red1: 9;
207    output_enable, set_output_enable: 8;
208    dummy1, set_dummy1: 7;
209    dummy0, set_dummy0: 6;
210    latch, set_latch: 5;
211    addr, set_addr: 4, 0;
212}
213
214impl core::fmt::Debug for Entry {
215    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
216        f.debug_tuple("Entry")
217            .field(&format_args!("{:#x}", self.0))
218            .finish()
219    }
220}
221
222#[cfg(feature = "defmt")]
223impl defmt::Format for Entry {
224    fn format(&self, f: defmt::Formatter) {
225        defmt::write!(f, "Entry({=u16:#x})", self.0);
226    }
227}
228
229impl Entry {
230    const fn new() -> Self {
231        Self(0)
232    }
233
234    // Optimized color bit manipulation constants and methods
235    const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; // bits 9-11: R1, G1, B1
236    const COLOR1_MASK: u16 = 0b0111_0000_0000_0000; // bits 12-14: R2, G2, B2
237
238    #[inline]
239    fn set_color0_bits(&mut self, bits: u8) {
240        let bits16 = u16::from(bits) << 9;
241        self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
242    }
243
244    #[inline]
245    fn set_color1_bits(&mut self, bits: u8) {
246        let bits16 = u16::from(bits) << 12;
247        self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
248    }
249}
250
251/// Represents a single row of pixels in the framebuffer.
252///
253/// Each row contains a fixed number of columns (`COLS`) and manages the timing
254/// and control signals for the HUB75 panel. The row handles:
255/// - Output enable timing to prevent ghosting
256/// - Latch signal generation for row updates
257/// - Row address management
258/// - Color data for both sub-pixels
259#[derive(Clone, Copy, PartialEq, Debug)]
260#[repr(C)]
261struct Row<const COLS: usize> {
262    data: [Entry; COLS],
263}
264
265const fn map_index(i: usize) -> usize {
266    #[cfg(feature = "esp32-ordering")]
267    {
268        i ^ 1
269    }
270    #[cfg(not(feature = "esp32-ordering"))]
271    {
272        i
273    }
274}
275
276impl<const COLS: usize> Default for Row<COLS> {
277    fn default() -> Self {
278        Self::new()
279    }
280}
281
282impl<const COLS: usize> Row<COLS> {
283    pub const fn new() -> Self {
284        Self {
285            data: [Entry::new(); COLS],
286        }
287    }
288
289    pub fn format(&mut self, addr: u8, prev_addr: u8) {
290        // Use pre-computed template and bulk copy for maximum performance
291        let template = make_data_template::<COLS>(addr, prev_addr);
292        self.data.copy_from_slice(&template);
293    }
294
295    /// Fast clear method that preserves timing/control bits while clearing pixel data.
296    /// Uses bulk memory operations for maximum performance.
297    #[inline]
298    pub fn clear_colors(&mut self) {
299        // Clear color bits while preserving timing and control bits
300        const COLOR_CLEAR_MASK: u16 = !0b0111_1110_0000_0000; // Clear bits 9-14 (R1,G1,B1,R2,G2,B2)
301
302        for entry in &mut self.data {
303            entry.0 &= COLOR_CLEAR_MASK;
304        }
305    }
306
307    #[inline]
308    pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
309        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
310        let col = map_index(col);
311        self.data[col].set_color0_bits(bits);
312    }
313
314    #[inline]
315    pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
316        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
317        let col = map_index(col);
318        self.data[col].set_color1_bits(bits);
319    }
320}
321
322#[derive(Copy, Clone, Debug)]
323#[repr(C)]
324struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
325    rows: [Row<COLS>; NROWS],
326}
327
328impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
329    pub const fn new() -> Self {
330        Self {
331            rows: [Row::new(); NROWS],
332        }
333    }
334
335    pub fn format(&mut self) {
336        for (addr, row) in self.rows.iter_mut().enumerate() {
337            let prev_addr = if addr == 0 {
338                NROWS as u8 - 1
339            } else {
340                addr as u8 - 1
341            };
342            row.format(addr as u8, prev_addr);
343        }
344    }
345
346    /// Fast clear method that preserves timing/control bits while clearing pixel data.
347    #[inline]
348    pub fn clear_colors(&mut self) {
349        for row in &mut self.rows {
350            row.clear_colors();
351        }
352    }
353
354    #[inline]
355    pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
356        let row = &mut self.rows[if y < NROWS { y } else { y - NROWS }];
357        if y < NROWS {
358            row.set_color0(x, red, green, blue);
359        } else {
360            row.set_color1(x, red, green, blue);
361        }
362    }
363}
364
365impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
366    for Frame<ROWS, COLS, NROWS>
367{
368    fn default() -> Self {
369        Self::new()
370    }
371}
372
373/// DMA-compatible framebuffer for HUB75 LED panels.
374///
375/// This is a framebuffer implementation that:
376/// - Manages multiple frames for Binary Code Modulation (BCM)
377/// - Provides DMA-compatible memory layout
378/// - Implements the embedded-graphics `DrawTarget` trait
379///
380/// # Type Parameters
381/// - `ROWS`: Total number of rows in the panel
382/// - `COLS`: Number of columns in the panel
383/// - `NROWS`: Number of rows per scan (typically half of ROWS)
384/// - `BITS`: Color depth (1-8 bits)
385/// - `FRAME_COUNT`: Number of frames used for Binary Code Modulation
386///
387/// # Helper Functions
388/// Use these functions to compute the correct values:
389/// - `esp_hub75::compute_frame_count(BITS)`: Computes the required number of
390///   frames
391/// - `esp_hub75::compute_rows(ROWS)`: Computes the number of rows per scan
392///
393/// # Memory Layout
394/// The buffer is aligned to ensure efficient DMA transfers and contains:
395/// - A 64-bit alignment field
396/// - An array of frames, each containing the full panel data
397#[derive(Copy, Clone)]
398#[repr(C)]
399pub struct DmaFrameBuffer<
400    const ROWS: usize,
401    const COLS: usize,
402    const NROWS: usize,
403    const BITS: u8,
404    const FRAME_COUNT: usize,
405> {
406    _align: u64,
407    frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
408}
409
410impl<
411        const ROWS: usize,
412        const COLS: usize,
413        const NROWS: usize,
414        const BITS: u8,
415        const FRAME_COUNT: usize,
416    > Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
417{
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423impl<
424        const ROWS: usize,
425        const COLS: usize,
426        const NROWS: usize,
427        const BITS: u8,
428        const FRAME_COUNT: usize,
429    > DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
430{
431    /// Create a new, ready-to-use framebuffer.
432    ///
433    /// This creates a new framebuffer and automatically formats it with proper timing signals.
434    /// The framebuffer is immediately ready for pixel operations and DMA transfers.
435    ///
436    /// # Panics
437    ///
438    /// Panics if `BITS` is greater than 8, as only 1-8 bit color depths are supported.
439    ///
440    /// # Example
441    /// ```rust,no_run
442    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
443    ///
444    /// const ROWS: usize = 32;
445    /// const COLS: usize = 64;
446    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
447    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
448    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
449    ///
450    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
451    /// // No need to call format() - framebuffer is ready to use!
452    /// ```
453    #[must_use]
454    pub fn new() -> Self {
455        debug_assert!(BITS <= 8);
456
457        let mut instance = Self {
458            _align: 0,
459            frames: [Frame::new(); FRAME_COUNT],
460        };
461
462        // Pre-format the framebuffer so it's immediately ready for use
463        instance.format();
464        instance
465    }
466
467    /// Returns the number of BCM chunks in this framebuffer (always 1 for
468    /// single-plane framebuffers — the entire buffer is one contiguous chunk).
469    #[must_use]
470    pub const fn bcm_chunk_count() -> usize {
471        1
472    }
473
474    /// Returns the byte size of one BCM chunk (for single-plane framebuffers
475    /// this equals the total DMA buffer size, since BCM weighting is baked in).
476    #[must_use]
477    pub const fn bcm_chunk_bytes() -> usize {
478        core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
479    }
480
481    /// Perform full formatting of the framebuffer with timing and control signals.
482    ///
483    /// This sets up all the timing and control signals needed for proper HUB75 operation.
484    /// This is automatically called by `new()`, so you typically don't need to call this
485    /// unless you want to completely reinitialize the framebuffer.
486    ///
487    /// # Example
488    /// ```rust,no_run
489    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
490    ///
491    /// const ROWS: usize = 32;
492    /// const COLS: usize = 64;
493    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
494    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
495    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
496    ///
497    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
498    /// framebuffer.format(); // Reinitialize if needed
499    /// ```
500    #[inline]
501    pub fn format(&mut self) {
502        for frame in &mut self.frames {
503            frame.format();
504        }
505    }
506
507    /// Fast erase operation that clears all pixel data while preserving timing signals.
508    ///
509    /// This is much faster than `format()` when you just want to clear the display
510    /// since it preserves all the timing and control signals that are already set up.
511    /// Use this for clearing between frames or when you want to start drawing fresh content.
512    ///
513    /// # Example
514    /// ```rust,no_run
515    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
516    ///
517    /// const ROWS: usize = 32;
518    /// const COLS: usize = 64;
519    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
520    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
521    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
522    ///
523    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
524    /// framebuffer.erase();
525    /// ```
526    #[inline]
527    pub fn erase(&mut self) {
528        for frame in &mut self.frames {
529            frame.clear_colors();
530        }
531    }
532
533    /// Set a pixel in the framebuffer.
534    /// # Example
535    /// ```rust,no_run
536    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
537    /// use embedded_graphics::prelude::*;
538    ///
539    /// const ROWS: usize = 32;
540    /// const COLS: usize = 64;
541    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
542    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
543    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
544    ///
545    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
546    /// framebuffer.set_pixel(Point::new(10, 10), Color::RED);
547    /// ```
548    pub fn set_pixel(&mut self, p: Point, color: Color) {
549        if p.x < 0 || p.y < 0 {
550            return;
551        }
552        self.set_pixel_internal(p.x as usize, p.y as usize, color);
553    }
554
555    #[inline]
556    fn frames_on(v: u8) -> usize {
557        // v / brightness_step but the compiler resolves the shift at build-time
558        (v as usize) >> (8 - BITS)
559    }
560
561    #[inline]
562    fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
563        if x >= COLS || y >= ROWS {
564            return;
565        }
566
567        // Early exit for black pixels - common in UI backgrounds
568        // Only enabled when skip-black-pixels feature is active
569        #[cfg(feature = "skip-black-pixels")]
570        if color == Color::BLACK {
571            return;
572        }
573
574        // Pre-compute how many frames each channel should be on
575        let red_frames = Self::frames_on(color.r());
576        let green_frames = Self::frames_on(color.g());
577        let blue_frames = Self::frames_on(color.b());
578
579        // Set the pixel in all frames based on pre-computed frame counts
580        for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
581            frame.set_pixel(
582                y,
583                x,
584                frame_idx < red_frames,
585                frame_idx < green_frames,
586                frame_idx < blue_frames,
587            );
588        }
589    }
590}
591
592impl<
593        const ROWS: usize,
594        const COLS: usize,
595        const NROWS: usize,
596        const BITS: u8,
597        const FRAME_COUNT: usize,
598    > FrameBufferOperations for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
599{
600    #[inline]
601    fn erase(&mut self) {
602        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
603    }
604
605    #[inline]
606    fn set_pixel(&mut self, p: Point, color: Color) {
607        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
608    }
609}
610
611impl<
612        const ROWS: usize,
613        const COLS: usize,
614        const NROWS: usize,
615        const BITS: u8,
616        const FRAME_COUNT: usize,
617    > embedded_graphics::prelude::OriginDimensions
618    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
619{
620    fn size(&self) -> embedded_graphics::prelude::Size {
621        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
622    }
623}
624
625impl<
626        const ROWS: usize,
627        const COLS: usize,
628        const NROWS: usize,
629        const BITS: u8,
630        const FRAME_COUNT: usize,
631    > embedded_graphics::prelude::OriginDimensions
632    for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
633{
634    fn size(&self) -> embedded_graphics::prelude::Size {
635        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
636    }
637}
638
639impl<
640        const ROWS: usize,
641        const COLS: usize,
642        const NROWS: usize,
643        const BITS: u8,
644        const FRAME_COUNT: usize,
645    > embedded_graphics::draw_target::DrawTarget
646    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
647{
648    type Color = Color;
649
650    type Error = Infallible;
651
652    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
653    where
654        I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
655    {
656        for pixel in pixels {
657            self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
658        }
659        Ok(())
660    }
661}
662
663unsafe impl<
664        const ROWS: usize,
665        const COLS: usize,
666        const NROWS: usize,
667        const BITS: u8,
668        const FRAME_COUNT: usize,
669    > ReadBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
670{
671    type Word = u8;
672
673    unsafe fn read_buffer(&self) -> (*const u8, usize) {
674        let ptr = (&raw const self.frames).cast::<u8>();
675        let len = core::mem::size_of_val(&self.frames);
676        (ptr, len)
677    }
678}
679
680unsafe impl<
681        const ROWS: usize,
682        const COLS: usize,
683        const NROWS: usize,
684        const BITS: u8,
685        const FRAME_COUNT: usize,
686    > ReadBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
687{
688    type Word = u8;
689
690    unsafe fn read_buffer(&self) -> (*const u8, usize) {
691        let ptr = (&raw const self.frames).cast::<u8>();
692        let len = core::mem::size_of_val(&self.frames);
693        (ptr, len)
694    }
695}
696
697impl<
698        const ROWS: usize,
699        const COLS: usize,
700        const NROWS: usize,
701        const BITS: u8,
702        const FRAME_COUNT: usize,
703    > core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
704{
705    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
706        let brightness_step = 1 << (8 - BITS);
707        f.debug_struct("DmaFrameBuffer")
708            .field("size", &core::mem::size_of_val(&self.frames))
709            .field("frame_count", &self.frames.len())
710            .field("frame_size", &core::mem::size_of_val(&self.frames[0]))
711            .field("brightness_step", &&brightness_step)
712            .finish_non_exhaustive()
713    }
714}
715
716#[cfg(feature = "defmt")]
717impl<
718        const ROWS: usize,
719        const COLS: usize,
720        const NROWS: usize,
721        const BITS: u8,
722        const FRAME_COUNT: usize,
723    > defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
724{
725    fn format(&self, f: defmt::Formatter) {
726        let brightness_step = 1 << (8 - BITS);
727        defmt::write!(
728            f,
729            "DmaFrameBuffer<{}, {}, {}, {}, {}>",
730            ROWS,
731            COLS,
732            NROWS,
733            BITS,
734            FRAME_COUNT
735        );
736        defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
737        defmt::write!(
738            f,
739            " frame_size: {}",
740            core::mem::size_of_val(&self.frames[0])
741        );
742        defmt::write!(f, " brightness_step: {}", brightness_step);
743    }
744}
745
746impl<
747        const ROWS: usize,
748        const COLS: usize,
749        const NROWS: usize,
750        const BITS: u8,
751        const FRAME_COUNT: usize,
752    > FrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
753{
754    fn get_word_size(&self) -> WordSize {
755        WordSize::Sixteen
756    }
757
758    fn plane_count(&self) -> usize {
759        1
760    }
761
762    fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
763        assert!(plane_idx == 0, "plain DmaFrameBuffer has only 1 plane");
764        let ptr = (&raw const self.frames).cast::<u8>();
765        let len = core::mem::size_of_val(&self.frames);
766        (ptr, len)
767    }
768}
769
770impl<
771        const ROWS: usize,
772        const COLS: usize,
773        const NROWS: usize,
774        const BITS: u8,
775        const FRAME_COUNT: usize,
776    > FrameBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
777{
778    fn get_word_size(&self) -> WordSize {
779        WordSize::Sixteen
780    }
781
782    fn plane_count(&self) -> usize {
783        1
784    }
785
786    fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
787        assert!(plane_idx == 0, "plain DmaFrameBuffer has only 1 plane");
788        let ptr = (&raw const self.frames).cast::<u8>();
789        let len = core::mem::size_of_val(&self.frames);
790        (ptr, len)
791    }
792}
793
794impl<
795        const ROWS: usize,
796        const COLS: usize,
797        const NROWS: usize,
798        const BITS: u8,
799        const FRAME_COUNT: usize,
800    > MutableFrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
801{
802}
803
804#[cfg(test)]
805mod tests {
806    extern crate std;
807
808    use std::format;
809    use std::vec;
810
811    use super::*;
812    use crate::{FrameBuffer, WordSize};
813    use embedded_graphics::pixelcolor::RgbColor;
814    use embedded_graphics::prelude::*;
815    use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
816
817    const TEST_ROWS: usize = 32;
818    const TEST_COLS: usize = 64;
819    const TEST_NROWS: usize = TEST_ROWS / 2;
820    const TEST_BITS: u8 = 3;
821    const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1; // 7 frames for 3-bit depth
822
823    type TestFrameBuffer =
824        DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
825
826    // Helper function to get mapped index for ESP32
827    fn get_mapped_index(index: usize) -> usize {
828        map_index(index)
829    }
830
831    #[test]
832    fn test_entry_construction() {
833        let entry = Entry::new();
834        assert_eq!(entry.0, 0);
835        assert_eq!(entry.dummy2(), false);
836        assert_eq!(entry.blu2(), false);
837        assert_eq!(entry.grn2(), false);
838        assert_eq!(entry.red2(), false);
839        assert_eq!(entry.blu1(), false);
840        assert_eq!(entry.grn1(), false);
841        assert_eq!(entry.red1(), false);
842        assert_eq!(entry.output_enable(), false);
843        assert_eq!(entry.dummy1(), false);
844        assert_eq!(entry.dummy0(), false);
845        assert_eq!(entry.latch(), false);
846        assert_eq!(entry.addr(), 0);
847    }
848
849    #[test]
850    fn test_entry_setters() {
851        let mut entry = Entry::new();
852
853        entry.set_dummy2(true);
854        assert_eq!(entry.dummy2(), true);
855        assert_eq!(entry.0 & 0b1000000000000000, 0b1000000000000000);
856
857        entry.set_blu2(true);
858        assert_eq!(entry.blu2(), true);
859        assert_eq!(entry.0 & 0b0100000000000000, 0b0100000000000000);
860
861        entry.set_grn2(true);
862        assert_eq!(entry.grn2(), true);
863        assert_eq!(entry.0 & 0b0010000000000000, 0b0010000000000000);
864
865        entry.set_red2(true);
866        assert_eq!(entry.red2(), true);
867        assert_eq!(entry.0 & 0b0001000000000000, 0b0001000000000000);
868
869        entry.set_blu1(true);
870        assert_eq!(entry.blu1(), true);
871        assert_eq!(entry.0 & 0b0000100000000000, 0b0000100000000000);
872
873        entry.set_grn1(true);
874        assert_eq!(entry.grn1(), true);
875        assert_eq!(entry.0 & 0b0000010000000000, 0b0000010000000000);
876
877        entry.set_red1(true);
878        assert_eq!(entry.red1(), true);
879        assert_eq!(entry.0 & 0b0000001000000000, 0b0000001000000000);
880
881        entry.set_output_enable(true);
882        assert_eq!(entry.output_enable(), true);
883        assert_eq!(entry.0 & 0b0000000100000000, 0b0000000100000000);
884
885        entry.set_dummy1(true);
886        assert_eq!(entry.dummy1(), true);
887        assert_eq!(entry.0 & 0b0000000010000000, 0b0000000010000000);
888
889        entry.set_dummy0(true);
890        assert_eq!(entry.dummy0(), true);
891        assert_eq!(entry.0 & 0b0000000001000000, 0b0000000001000000);
892
893        entry.set_latch(true);
894        assert_eq!(entry.latch(), true);
895        assert_eq!(entry.0 & 0b0000000000100000, 0b0000000000100000);
896
897        entry.set_addr(0b11111);
898        assert_eq!(entry.addr(), 0b11111);
899        assert_eq!(entry.0 & 0b0000000000011111, 0b0000000000011111);
900    }
901
902    #[test]
903    fn test_entry_bit_isolation() {
904        let mut entry = Entry::new();
905
906        // Test that setting one field doesn't affect others
907        entry.set_addr(0b11111);
908        entry.set_latch(true);
909        assert_eq!(entry.addr(), 0b11111);
910        assert_eq!(entry.latch(), true);
911        assert_eq!(entry.output_enable(), false);
912        assert_eq!(entry.red1(), false);
913
914        entry.set_red1(true);
915        entry.set_grn2(true);
916        assert_eq!(entry.addr(), 0b11111);
917        assert_eq!(entry.latch(), true);
918        assert_eq!(entry.red1(), true);
919        assert_eq!(entry.grn2(), true);
920        assert_eq!(entry.blu1(), false);
921        assert_eq!(entry.red2(), false);
922    }
923
924    #[test]
925    fn test_entry_set_color0() {
926        let mut entry = Entry::new();
927
928        let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); // b=1, g=0, r=1 = 0b101
929        entry.set_color0_bits(bits);
930        assert_eq!(entry.red1(), true);
931        assert_eq!(entry.grn1(), false);
932        assert_eq!(entry.blu1(), true);
933        // Check that only the expected bits are set
934        assert_eq!(entry.0 & 0b0000101000000000, 0b0000101000000000); // Red1 and Blue1 bits
935    }
936
937    #[test]
938    fn test_entry_set_color1() {
939        let mut entry = Entry::new();
940
941        let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); // b=1, g=1, r=0 = 0b110
942        entry.set_color1_bits(bits);
943        assert_eq!(entry.red2(), false);
944        assert_eq!(entry.grn2(), true);
945        assert_eq!(entry.blu2(), true);
946        // Check that only the expected bits are set
947        assert_eq!(entry.0 & 0b0110000000000000, 0b0110000000000000); // Green2 and Blue2 bits
948    }
949
950    #[test]
951    fn test_entry_debug_formatting() {
952        let entry = Entry(0x1234);
953        let debug_str = format!("{:?}", entry);
954        assert_eq!(debug_str, "Entry(0x1234)");
955
956        let entry = Entry(0xabcd);
957        let debug_str = format!("{:?}", entry);
958        assert_eq!(debug_str, "Entry(0xabcd)");
959    }
960
961    #[test]
962    fn test_row_construction() {
963        let row: Row<TEST_COLS> = Row::new();
964        assert_eq!(row.data.len(), TEST_COLS);
965
966        // Check that all entries are initialized to zero
967        for entry in &row.data {
968            assert_eq!(entry.0, 0);
969        }
970    }
971
972    #[test]
973    fn test_row_format() {
974        let mut row: Row<TEST_COLS> = Row::new();
975        let test_addr = 5;
976        let prev_addr = 4;
977
978        row.format(test_addr, prev_addr);
979
980        // Check data entries configuration
981        for (physical_i, entry) in row.data.iter().enumerate() {
982            let logical_i = get_mapped_index(physical_i);
983
984            match logical_i {
985                i if i == TEST_COLS - BLANKING_DELAY - 1 => {
986                    // Second to last pixel should have output_enable false
987                    assert_eq!(entry.output_enable(), false);
988                    assert_eq!(entry.addr(), prev_addr as u16);
989                    assert_eq!(entry.latch(), false);
990                }
991                i if i == TEST_COLS - 1 => {
992                    // Last pixel should have latch true and new address
993                    assert_eq!(entry.latch(), true);
994                    assert_eq!(entry.addr(), test_addr as u16);
995                    assert_eq!(entry.output_enable(), false);
996                }
997                1 => {
998                    // First pixel after start should have output_enable true
999                    assert_eq!(entry.output_enable(), true);
1000                    assert_eq!(entry.addr(), prev_addr as u16);
1001                    assert_eq!(entry.latch(), false);
1002                }
1003                _ => {
1004                    // Other pixels should have the previous address and no latch
1005                    assert_eq!(entry.addr(), prev_addr as u16);
1006                    assert_eq!(entry.latch(), false);
1007                    if logical_i > 1 && logical_i < TEST_COLS - BLANKING_DELAY - 1 {
1008                        assert_eq!(entry.output_enable(), true);
1009                    }
1010                }
1011            }
1012        }
1013    }
1014
1015    #[test]
1016    fn test_row_set_color0() {
1017        let mut row: Row<TEST_COLS> = Row::new();
1018
1019        row.set_color0(0, true, false, true);
1020
1021        let mapped_col_0 = get_mapped_index(0);
1022        assert_eq!(row.data[mapped_col_0].red1(), true);
1023        assert_eq!(row.data[mapped_col_0].grn1(), false);
1024        assert_eq!(row.data[mapped_col_0].blu1(), true);
1025
1026        // Test another column
1027        row.set_color0(1, false, true, false);
1028
1029        let mapped_col_1 = get_mapped_index(1);
1030        assert_eq!(row.data[mapped_col_1].red1(), false);
1031        assert_eq!(row.data[mapped_col_1].grn1(), true);
1032        assert_eq!(row.data[mapped_col_1].blu1(), false);
1033    }
1034
1035    #[test]
1036    fn test_row_set_color1() {
1037        let mut row: Row<TEST_COLS> = Row::new();
1038
1039        row.set_color1(0, true, true, false);
1040
1041        let mapped_col_0 = get_mapped_index(0);
1042        assert_eq!(row.data[mapped_col_0].red2(), true);
1043        assert_eq!(row.data[mapped_col_0].grn2(), true);
1044        assert_eq!(row.data[mapped_col_0].blu2(), false);
1045    }
1046
1047    #[test]
1048    fn test_row_default() {
1049        let row1: Row<TEST_COLS> = Row::new();
1050        let row2: Row<TEST_COLS> = Row::default();
1051
1052        // Both should be equivalent
1053        assert_eq!(row1, row2);
1054        assert_eq!(row1.data.len(), row2.data.len());
1055
1056        // Check that all entries are initialized to zero
1057        for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1058            assert_eq!(entry1.0, entry2.0);
1059            assert_eq!(entry1.0, 0);
1060        }
1061    }
1062
1063    #[test]
1064    fn test_frame_construction() {
1065        let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1066        assert_eq!(frame.rows.len(), TEST_NROWS);
1067    }
1068
1069    #[test]
1070    fn test_frame_format() {
1071        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1072
1073        frame.format();
1074
1075        // Check that each row was formatted with correct address parameters
1076        for addr in 0..TEST_NROWS {
1077            let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1078
1079            // Check some key pixels in each row
1080            let row = &frame.rows[addr];
1081
1082            // Check last pixel has correct new address
1083            let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1084            assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1085            assert_eq!(row.data[last_pixel_idx].latch(), true);
1086
1087            // Check non-last pixels have previous address
1088            let first_pixel_idx = get_mapped_index(0);
1089            assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1090            assert_eq!(row.data[first_pixel_idx].latch(), false);
1091        }
1092    }
1093
1094    #[test]
1095    fn test_frame_set_pixel() {
1096        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1097
1098        // Test setting pixel in upper half (y < NROWS)
1099        frame.set_pixel(5, 10, true, false, true);
1100
1101        let mapped_col_10 = get_mapped_index(10);
1102        assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1103        assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1104        assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1105
1106        // Test setting pixel in lower half (y >= NROWS)
1107        frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
1108
1109        let mapped_col_15 = get_mapped_index(15);
1110        assert_eq!(frame.rows[5].data[mapped_col_15].red2(), false);
1111        assert_eq!(frame.rows[5].data[mapped_col_15].grn2(), true);
1112        assert_eq!(frame.rows[5].data[mapped_col_15].blu2(), false);
1113    }
1114
1115    #[test]
1116    fn test_frame_default() {
1117        let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1118        let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
1119
1120        // Both should be equivalent
1121        assert_eq!(frame1.rows.len(), frame2.rows.len());
1122
1123        // Check that all rows are equivalent
1124        for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
1125            assert_eq!(row1, row2);
1126
1127            // Verify all entries are zero-initialized
1128            for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1129                assert_eq!(entry1.0, entry2.0);
1130                assert_eq!(entry1.0, 0);
1131            }
1132        }
1133    }
1134
1135    #[test]
1136    fn test_dma_framebuffer_construction() {
1137        let fb = TestFrameBuffer::new();
1138        assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
1139        assert_eq!(fb._align, 0);
1140    }
1141
1142    #[test]
1143    fn test_bcm_chunk_info() {
1144        let expected_size =
1145            core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
1146        assert_eq!(TestFrameBuffer::bcm_chunk_bytes(), expected_size);
1147        assert_eq!(TestFrameBuffer::bcm_chunk_count(), 1);
1148    }
1149
1150    #[test]
1151    fn test_dma_framebuffer_erase() {
1152        let fb = TestFrameBuffer::new();
1153
1154        // After erasing, all frames should be formatted
1155        for frame in &fb.frames {
1156            for addr in 0..TEST_NROWS {
1157                let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1158
1159                // Check some key pixels in each row
1160                let row = &frame.rows[addr];
1161
1162                // Check last pixel has correct new address
1163                let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1164                assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1165                assert_eq!(row.data[last_pixel_idx].latch(), true);
1166
1167                // Check non-last pixels have previous address
1168                let first_pixel_idx = get_mapped_index(0);
1169                assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1170                assert_eq!(row.data[first_pixel_idx].latch(), false);
1171            }
1172        }
1173    }
1174
1175    #[test]
1176    fn test_dma_framebuffer_set_pixel_bounds() {
1177        let mut fb = TestFrameBuffer::new();
1178
1179        // Test negative coordinates
1180        fb.set_pixel(Point::new(-1, 5), Color::RED);
1181        fb.set_pixel(Point::new(5, -1), Color::RED);
1182
1183        // Test coordinates out of bounds (should not panic)
1184        fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
1185        fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
1186    }
1187
1188    #[test]
1189    fn test_dma_framebuffer_set_pixel_internal() {
1190        let mut fb = TestFrameBuffer::new();
1191
1192        let red_color = Color::RED;
1193        fb.set_pixel_internal(10, 5, red_color);
1194
1195        // With 3-bit depth, brightness steps are 32 (256/8)
1196        // Frames represent thresholds: 32, 64, 96, 128, 160, 192, 224
1197        // Red value 255 should activate all frames
1198        for frame in &fb.frames {
1199            // Check upper half pixel
1200            let mapped_col_10 = get_mapped_index(10);
1201            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1202            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1203            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), false);
1204        }
1205    }
1206
1207    #[test]
1208    fn test_dma_framebuffer_brightness_modulation() {
1209        let mut fb = TestFrameBuffer::new();
1210
1211        // Test with a medium brightness value
1212        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1213        let test_brightness = brightness_step * 3; // 96
1214        let color = Color::from(embedded_graphics::pixelcolor::Rgb888::new(
1215            test_brightness,
1216            0,
1217            0,
1218        ));
1219
1220        fb.set_pixel_internal(0, 0, color);
1221
1222        // Should activate frames 0, 1, 2 (thresholds 32, 64, 96)
1223        // but not frames 3, 4, 5, 6 (thresholds 128, 160, 192, 224)
1224        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1225            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1226            let should_be_active = test_brightness >= frame_threshold;
1227
1228            let mapped_col_0 = get_mapped_index(0);
1229            assert_eq!(frame.rows[0].data[mapped_col_0].red1(), should_be_active);
1230        }
1231    }
1232
1233    #[test]
1234    fn test_origin_dimensions() {
1235        let fb = TestFrameBuffer::new();
1236        let size = fb.size();
1237        assert_eq!(size.width, TEST_COLS as u32);
1238        assert_eq!(size.height, TEST_ROWS as u32);
1239
1240        // Test reference
1241        let size = (&fb).size();
1242        assert_eq!(size.width, TEST_COLS as u32);
1243        assert_eq!(size.height, TEST_ROWS as u32);
1244
1245        // Test mutable reference
1246        let mut fb = TestFrameBuffer::new();
1247        let fb_ref = &mut fb;
1248        let size = fb_ref.size();
1249        assert_eq!(size.width, TEST_COLS as u32);
1250        assert_eq!(size.height, TEST_ROWS as u32);
1251    }
1252
1253    #[test]
1254    fn test_draw_target() {
1255        let mut fb = TestFrameBuffer::new();
1256
1257        let pixels = vec![
1258            embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
1259            embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
1260            embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
1261        ];
1262
1263        let result = fb.draw_iter(pixels);
1264        assert!(result.is_ok());
1265
1266        // Test mutable reference
1267        let result = (&mut fb).draw_iter(vec![embedded_graphics::Pixel(
1268            Point::new(3, 3),
1269            Color::WHITE,
1270        )]);
1271        assert!(result.is_ok());
1272    }
1273
1274    #[test]
1275    fn test_draw_iter_pixel_verification() {
1276        let mut fb = TestFrameBuffer::new();
1277
1278        // Create test pixels with specific colors and positions
1279        let pixels = vec![
1280            // Upper half pixels (y < NROWS) - should set color0
1281            embedded_graphics::Pixel(Point::new(5, 2), Color::RED), // (5, 2) -> red
1282            embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), // (10, 5) -> green
1283            embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), // (15, 8) -> blue
1284            embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), // (20, 10) -> white
1285            // Lower half pixels (y >= NROWS) - should set color1
1286            embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), // (25, 19) -> red
1287            embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), // (30, 23) -> green
1288            embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), // (35, 28) -> blue
1289            // Edge case: black pixel (should not be visible in first frame)
1290            embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), // (40, 1) -> black
1291        ];
1292
1293        let result = fb.draw_iter(pixels);
1294        assert!(result.is_ok());
1295
1296        // Check the first frame only
1297        let first_frame = &fb.frames[0];
1298        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1299        let first_frame_threshold = brightness_step; // 32
1300
1301        // Test upper half pixels (color0)
1302        // Red pixel at (5, 2) - should be red in first frame
1303        let col_idx = get_mapped_index(5);
1304        assert_eq!(
1305            first_frame.rows[2].data[col_idx].red1(),
1306            Color::RED.r() >= first_frame_threshold
1307        );
1308        assert_eq!(
1309            first_frame.rows[2].data[col_idx].grn1(),
1310            Color::RED.g() >= first_frame_threshold
1311        );
1312        assert_eq!(
1313            first_frame.rows[2].data[col_idx].blu1(),
1314            Color::RED.b() >= first_frame_threshold
1315        );
1316
1317        // Green pixel at (10, 5) - should be green in first frame
1318        let col_idx = get_mapped_index(10);
1319        assert_eq!(
1320            first_frame.rows[5].data[col_idx].red1(),
1321            Color::GREEN.r() >= first_frame_threshold
1322        );
1323        assert_eq!(
1324            first_frame.rows[5].data[col_idx].grn1(),
1325            Color::GREEN.g() >= first_frame_threshold
1326        );
1327        assert_eq!(
1328            first_frame.rows[5].data[col_idx].blu1(),
1329            Color::GREEN.b() >= first_frame_threshold
1330        );
1331
1332        // Blue pixel at (15, 8) - should be blue in first frame
1333        let col_idx = get_mapped_index(15);
1334        assert_eq!(
1335            first_frame.rows[8].data[col_idx].red1(),
1336            Color::BLUE.r() >= first_frame_threshold
1337        );
1338        assert_eq!(
1339            first_frame.rows[8].data[col_idx].grn1(),
1340            Color::BLUE.g() >= first_frame_threshold
1341        );
1342        assert_eq!(
1343            first_frame.rows[8].data[col_idx].blu1(),
1344            Color::BLUE.b() >= first_frame_threshold
1345        );
1346
1347        // White pixel at (20, 10) - should be white in first frame
1348        let col_idx = get_mapped_index(20);
1349        assert_eq!(
1350            first_frame.rows[10].data[col_idx].red1(),
1351            Color::WHITE.r() >= first_frame_threshold
1352        );
1353        assert_eq!(
1354            first_frame.rows[10].data[col_idx].grn1(),
1355            Color::WHITE.g() >= first_frame_threshold
1356        );
1357        assert_eq!(
1358            first_frame.rows[10].data[col_idx].blu1(),
1359            Color::WHITE.b() >= first_frame_threshold
1360        );
1361
1362        // Test lower half pixels (color1)
1363        // Red pixel at (25, TEST_NROWS + 3) -> row 3, color1
1364        let col_idx = get_mapped_index(25);
1365        assert_eq!(
1366            first_frame.rows[3].data[col_idx].red2(),
1367            Color::RED.r() >= first_frame_threshold
1368        );
1369        assert_eq!(
1370            first_frame.rows[3].data[col_idx].grn2(),
1371            Color::RED.g() >= first_frame_threshold
1372        );
1373        assert_eq!(
1374            first_frame.rows[3].data[col_idx].blu2(),
1375            Color::RED.b() >= first_frame_threshold
1376        );
1377
1378        // Green pixel at (30, TEST_NROWS + 7) -> row 7, color1
1379        let col_idx = get_mapped_index(30);
1380        assert_eq!(
1381            first_frame.rows[7].data[col_idx].red2(),
1382            Color::GREEN.r() >= first_frame_threshold
1383        );
1384        assert_eq!(
1385            first_frame.rows[7].data[col_idx].grn2(),
1386            Color::GREEN.g() >= first_frame_threshold
1387        );
1388        assert_eq!(
1389            first_frame.rows[7].data[col_idx].blu2(),
1390            Color::GREEN.b() >= first_frame_threshold
1391        );
1392
1393        // Blue pixel at (35, TEST_NROWS + 12) -> row 12, color1
1394        let col_idx = get_mapped_index(35);
1395        assert_eq!(
1396            first_frame.rows[12].data[col_idx].red2(),
1397            Color::BLUE.r() >= first_frame_threshold
1398        );
1399        assert_eq!(
1400            first_frame.rows[12].data[col_idx].grn2(),
1401            Color::BLUE.g() >= first_frame_threshold
1402        );
1403        assert_eq!(
1404            first_frame.rows[12].data[col_idx].blu2(),
1405            Color::BLUE.b() >= first_frame_threshold
1406        );
1407
1408        // Test black pixel - should not be visible in any frame
1409        let col_idx = get_mapped_index(40);
1410        assert_eq!(first_frame.rows[1].data[col_idx].red1(), false);
1411        assert_eq!(first_frame.rows[1].data[col_idx].grn1(), false);
1412        assert_eq!(first_frame.rows[1].data[col_idx].blu1(), false);
1413    }
1414
1415    #[test]
1416    fn test_embedded_graphics_integration() {
1417        let mut fb = TestFrameBuffer::new();
1418
1419        // Draw a rectangle
1420        let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
1421            .into_styled(PrimitiveStyle::with_fill(Color::RED))
1422            .draw(&mut fb);
1423        assert!(result.is_ok());
1424
1425        // Draw a circle
1426        let result = Circle::new(Point::new(30, 15), 8)
1427            .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
1428            .draw(&mut fb);
1429        assert!(result.is_ok());
1430    }
1431
1432    #[test]
1433    #[cfg(feature = "skip-black-pixels")]
1434    fn test_skip_black_pixels_enabled() {
1435        let mut fb = TestFrameBuffer::new();
1436
1437        // Set a red pixel first
1438        fb.set_pixel_internal(10, 5, Color::RED);
1439
1440        // Verify it's red in the first frame
1441        let mapped_col_10 = get_mapped_index(10);
1442        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1443        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1444        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1445
1446        // Now set it to black - with skip-black-pixels enabled, this should be ignored
1447        fb.set_pixel_internal(10, 5, Color::BLACK);
1448
1449        // The pixel should still be red (black write was skipped)
1450        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1451        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1452        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1453    }
1454
1455    #[test]
1456    #[cfg(not(feature = "skip-black-pixels"))]
1457    fn test_skip_black_pixels_disabled() {
1458        let mut fb = TestFrameBuffer::new();
1459
1460        // Set a red pixel first
1461        fb.set_pixel_internal(10, 5, Color::RED);
1462
1463        // Verify it's red in the first frame
1464        let mapped_col_10 = get_mapped_index(10);
1465        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1466        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1467        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1468
1469        // Now set it to black - with skip-black-pixels disabled, this should overwrite
1470        fb.set_pixel_internal(10, 5, Color::BLACK);
1471
1472        // The pixel should now be black (all bits false)
1473        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1474        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1475        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1476    }
1477
1478    #[test]
1479    fn test_bcm_frame_overwrite() {
1480        let mut fb = TestFrameBuffer::new();
1481
1482        // First write a white pixel (255, 255, 255)
1483        fb.set_pixel_internal(10, 5, Color::WHITE);
1484
1485        let mapped_col_10 = get_mapped_index(10);
1486
1487        // Verify white pixel is lit in all frames (255 >= all thresholds)
1488        for frame in fb.frames.iter() {
1489            // White (255) should be active in all frames since it's >= all thresholds
1490            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1491            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), true);
1492            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1493        }
1494
1495        // Now overwrite with 50% white (128, 128, 128)
1496        let half_white = Color::from(embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128));
1497        fb.set_pixel_internal(10, 5, half_white);
1498
1499        // Verify only the correct frames are lit for 50% white
1500        // With 3-bit depth: thresholds are 32, 64, 96, 128, 160, 192, 224
1501        // 128 should activate frames 0, 1, 2, 3 (thresholds 32, 64, 96, 128)
1502        // but not frames 4, 5, 6 (thresholds 160, 192, 224)
1503        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1504        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1505            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1506            let should_be_active = 128 >= frame_threshold;
1507
1508            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), should_be_active);
1509            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), should_be_active);
1510            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), should_be_active);
1511        }
1512
1513        // Specifically verify the expected pattern for 3-bit depth
1514        // Frames 0-3 should be active (thresholds 32, 64, 96, 128)
1515        for frame_idx in 0..4 {
1516            assert_eq!(
1517                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1518                true
1519            );
1520        }
1521        // Frames 4-6 should be inactive (thresholds 160, 192, 224)
1522        for frame_idx in 4..TEST_FRAME_COUNT {
1523            assert_eq!(
1524                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1525                false
1526            );
1527        }
1528    }
1529
1530    #[test]
1531    fn test_read_buffer_implementation() {
1532        // Test owned implementation - explicitly move the framebuffer to ensure we're testing the owned impl
1533        let fb = TestFrameBuffer::new();
1534        let expected_size = core::mem::size_of_val(&fb.frames);
1535
1536        // Test owned ReadBuffer implementation by calling ReadBuffer::read_buffer explicitly
1537        unsafe {
1538            let (ptr, len) = <TestFrameBuffer as ReadBuffer>::read_buffer(&fb);
1539            assert!(!ptr.is_null());
1540            assert_eq!(len, expected_size);
1541        }
1542
1543        // Test direct method call on owned value
1544        unsafe {
1545            let (ptr, len) = fb.read_buffer();
1546            assert!(!ptr.is_null());
1547            assert_eq!(len, expected_size);
1548        }
1549
1550        // Test reference implementation
1551        let fb = TestFrameBuffer::new();
1552        let fb_ref = &fb;
1553        unsafe {
1554            let (ptr, len) = fb_ref.read_buffer();
1555            assert!(!ptr.is_null());
1556            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1557        }
1558
1559        // Test mutable reference implementation
1560        let mut fb = TestFrameBuffer::new();
1561        let fb_ref = &mut fb;
1562        unsafe {
1563            let (ptr, len) = fb_ref.read_buffer();
1564            assert!(!ptr.is_null());
1565            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1566        }
1567    }
1568
1569    #[test]
1570    fn test_read_buffer_owned_implementation() {
1571        // This test specifically ensures the owned ReadBuffer implementation is tested
1572        // by consuming the framebuffer and testing the pointer validity
1573        fn test_owned_read_buffer(fb: TestFrameBuffer) -> (bool, usize) {
1574            unsafe {
1575                let (ptr, len) = fb.read_buffer();
1576                (!ptr.is_null(), len)
1577            }
1578        }
1579
1580        let fb = TestFrameBuffer::new();
1581        let expected_len = core::mem::size_of_val(&fb.frames);
1582
1583        let (ptr_valid, actual_len) = test_owned_read_buffer(fb);
1584        assert!(ptr_valid);
1585        assert_eq!(actual_len, expected_len);
1586    }
1587
1588    #[test]
1589    fn test_framebuffer_trait() {
1590        let fb = TestFrameBuffer::new();
1591        assert_eq!(fb.get_word_size(), WordSize::Sixteen);
1592
1593        let fb_ref = &fb;
1594        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1595
1596        let mut fb = TestFrameBuffer::new();
1597        let fb_ref = &mut fb;
1598        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1599    }
1600
1601    #[test]
1602    fn test_debug_formatting() {
1603        let fb = TestFrameBuffer::new();
1604        let debug_string = format!("{:?}", fb);
1605        assert!(debug_string.contains("DmaFrameBuffer"));
1606        assert!(debug_string.contains("frame_count"));
1607        assert!(debug_string.contains("frame_size"));
1608        assert!(debug_string.contains("brightness_step"));
1609    }
1610
1611    #[test]
1612    fn test_default_implementation() {
1613        let fb1 = TestFrameBuffer::new();
1614        let fb2 = TestFrameBuffer::default();
1615
1616        // Both should be equivalent in size, but may differ in content since new() calls format()
1617        assert_eq!(fb1.frames.len(), fb2.frames.len());
1618        assert_eq!(fb1._align, fb2._align);
1619    }
1620
1621    #[test]
1622    fn test_memory_alignment() {
1623        let fb = TestFrameBuffer::new();
1624        let ptr = &fb as *const _ as usize;
1625
1626        // Should be aligned to 64-bit boundary due to the _align field
1627        assert_eq!(ptr % 8, 0);
1628    }
1629
1630    #[test]
1631    fn test_color_values() {
1632        let mut fb = TestFrameBuffer::new();
1633
1634        // Test different color values
1635        let colors = [
1636            (Color::RED, (255, 0, 0)),
1637            (Color::GREEN, (0, 255, 0)),
1638            (Color::BLUE, (0, 0, 255)),
1639            (Color::WHITE, (255, 255, 255)),
1640            (Color::BLACK, (0, 0, 0)),
1641        ];
1642
1643        for (i, (color, (r, g, b))) in colors.iter().enumerate() {
1644            fb.set_pixel(Point::new(i as i32, 0), *color);
1645            assert_eq!(color.r(), *r);
1646            assert_eq!(color.g(), *g);
1647            assert_eq!(color.b(), *b);
1648        }
1649    }
1650
1651    #[test]
1652    fn test_blanking_delay() {
1653        let mut row: Row<TEST_COLS> = Row::new();
1654        let test_addr = 5;
1655        let prev_addr = 4;
1656
1657        row.format(test_addr, prev_addr);
1658
1659        // Test that the blanking delay is respected
1660        let blanking_pixel_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 1);
1661        assert_eq!(row.data[blanking_pixel_idx].output_enable(), false);
1662
1663        // Test that pixels before blanking delay have output enabled (if after pixel 1)
1664        let before_blanking_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 2);
1665        assert_eq!(row.data[before_blanking_idx].output_enable(), true);
1666    }
1667
1668    #[test]
1669    fn test_esp32_mapping() {
1670        // Test the ESP32-specific index mapping
1671        #[cfg(feature = "esp32-ordering")]
1672        {
1673            assert_eq!(map_index(0), 1);
1674            assert_eq!(map_index(1), 0);
1675            assert_eq!(map_index(2), 3);
1676            assert_eq!(map_index(3), 2);
1677            assert_eq!(map_index(4), 5);
1678            assert_eq!(map_index(5), 4);
1679        }
1680        #[cfg(not(feature = "esp32-ordering"))]
1681        {
1682            assert_eq!(map_index(0), 0);
1683            assert_eq!(map_index(1), 1);
1684            assert_eq!(map_index(2), 2);
1685            assert_eq!(map_index(3), 3);
1686        }
1687    }
1688
1689    #[test]
1690    fn test_bits_assertion() {
1691        // Test that BITS <= 8 assertion is enforced at compile time
1692        // This test mainly documents the constraint
1693        assert!(TEST_BITS <= 8);
1694    }
1695
1696    #[test]
1697    fn test_fast_clear_method() {
1698        let mut fb = TestFrameBuffer::new();
1699
1700        // Set some pixels
1701        fb.set_pixel_internal(10, 5, Color::RED);
1702        fb.set_pixel_internal(20, 10, Color::GREEN);
1703
1704        // Verify pixels are set
1705        let mapped_col_10 = get_mapped_index(10);
1706        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1707
1708        // Clear using fast method
1709        fb.erase();
1710
1711        // Verify pixels are cleared but timing signals remain
1712        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1713        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1714        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1715
1716        // Verify timing signals are still present (check last pixel has latch)
1717        let last_col = get_mapped_index(TEST_COLS - 1);
1718        assert_eq!(fb.frames[0].rows[5].data[last_col].latch(), true);
1719    }
1720
1721    // Remove the old `test_draw_char_bottom_right` and replace with a helper + combined test.
1722
1723    // Constants for FONT_6X10 glyph size
1724    const CHAR_W: i32 = 6;
1725    const CHAR_H: i32 = 10;
1726
1727    /// Draws glyph 'A' at `origin` and validates framebuffer pixels against a reference.
1728    fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
1729        use embedded_graphics::mock_display::MockDisplay;
1730        use embedded_graphics::mono_font::ascii::FONT_6X10;
1731        use embedded_graphics::mono_font::MonoTextStyle;
1732        use embedded_graphics::text::{Baseline, Text};
1733
1734        // Draw the glyph
1735        let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
1736        Text::with_baseline("A", origin, style, Baseline::Top)
1737            .draw(fb)
1738            .unwrap();
1739
1740        // Reference glyph at (0,0)
1741        let mut reference: MockDisplay<Color> = MockDisplay::new();
1742        Text::with_baseline("A", Point::zero(), style, Baseline::Top)
1743            .draw(&mut reference)
1744            .unwrap();
1745
1746        for dy in 0..CHAR_H {
1747            for dx in 0..CHAR_W {
1748                let expected_on = reference
1749                    .get_pixel(Point::new(dx, dy))
1750                    .unwrap_or(Color::BLACK)
1751                    != Color::BLACK;
1752
1753                let gx = (origin.x + dx) as usize;
1754                let gy = (origin.y + dy) as usize;
1755
1756                // we have computed the origin to be within the panel, so we don't need to check for bounds
1757                // if gx >= TEST_COLS || gy >= TEST_ROWS {
1758                //     continue;
1759                // }
1760
1761                // Fetch Entry from frame 0
1762                let frame0 = &fb.frames[0];
1763                let e = if gy < TEST_NROWS {
1764                    &frame0.rows[gy].data[get_mapped_index(gx)]
1765                } else {
1766                    &frame0.rows[gy - TEST_NROWS].data[get_mapped_index(gx)]
1767                };
1768
1769                let (r, g, b) = if gy >= TEST_NROWS {
1770                    (e.red2(), e.grn2(), e.blu2())
1771                } else {
1772                    (e.red1(), e.grn1(), e.blu1())
1773                };
1774
1775                if expected_on {
1776                    assert!(r && g && b,);
1777                } else {
1778                    assert!(!r && !g && !b);
1779                }
1780            }
1781        }
1782    }
1783
1784    #[test]
1785    fn test_draw_char_corners() {
1786        let upper_left = Point::new(0, 0);
1787        let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
1788
1789        let mut fb = TestFrameBuffer::new();
1790
1791        verify_glyph_at(&mut fb, upper_left);
1792        verify_glyph_at(&mut fb, lower_right);
1793    }
1794
1795    #[test]
1796    fn test_framebuffer_operations_trait_erase() {
1797        let mut fb = TestFrameBuffer::new();
1798
1799        // Set pixels so erase has work to do
1800        fb.set_pixel_internal(10, 5, Color::RED);
1801        fb.set_pixel_internal(20, 10, Color::GREEN);
1802
1803        // Explicitly call trait method to hit the FrameBufferOperations impl
1804        <TestFrameBuffer as FrameBufferOperations>::erase(&mut fb);
1805
1806        // Verify colors cleared on frame 0
1807        let mc10 = get_mapped_index(10);
1808        let mc20 = get_mapped_index(20);
1809        assert_eq!(fb.frames[0].rows[5].data[mc10].red1(), false);
1810        assert_eq!(fb.frames[0].rows[10].data[mc20].grn1(), false);
1811
1812        // Timing signals preserved: last pixel should have latch
1813        let last_col = get_mapped_index(TEST_COLS - 1);
1814        assert!(fb.frames[0].rows[0].data[last_col].latch());
1815    }
1816
1817    #[test]
1818    fn test_framebuffer_operations_trait_set_pixel() {
1819        let mut fb = TestFrameBuffer::new();
1820
1821        // Explicitly call trait method to hit the FrameBufferOperations impl
1822        <TestFrameBuffer as FrameBufferOperations>::set_pixel(
1823            &mut fb,
1824            Point::new(8, 3),
1825            Color::BLUE,
1826        );
1827
1828        let idx = get_mapped_index(8);
1829        assert_eq!(fb.frames[0].rows[3].data[idx].blu1(), true);
1830        assert_eq!(fb.frames[0].rows[3].data[idx].red1(), false);
1831        assert_eq!(fb.frames[0].rows[3].data[idx].grn1(), false);
1832    }
1833}