1use core::convert::Infallible;
146
147use crate::{
148 map_row_index, slot_addresses, BcmLenReps, FrameBuffer, FrameBufferOperations,
149 MutableFrameBuffer, BCM_SEQUENCE_CAPACITY,
150};
151use bitfield::bitfield;
152use embedded_graphics::pixelcolor::RgbColor;
153use embedded_graphics::prelude::Point;
154
155use super::Color;
156#[cfg(feature = "tail-closes-latch")]
157use crate::pinmap::ADDR_MASK;
158use crate::pinmap::PINMAP;
159use crate::pinmap::{
160 ADDR_SHIFT, COLOR0_MASK, COLOR0_SHIFT, COLOR1_MASK, COLOR1_SHIFT, COLOR_MASK, LATCH_BIT, OE_BIT,
161};
162use crate::{INTER_ROW_BLANK, LEAD_BLANK_DELAY, TRAIL_BLANK_DELAY};
163
164#[cfg(not(feature = "invert-oe"))]
168const OE_ACTIVE: u16 = OE_BIT;
169#[cfg(not(feature = "invert-oe"))]
170const OE_BLANK: u16 = 0;
171
172#[cfg(feature = "invert-oe")]
173const OE_ACTIVE: u16 = 0;
174#[cfg(feature = "invert-oe")]
175const OE_BLANK: u16 = OE_BIT;
176
177#[allow(clippy::absurd_extreme_comparisons)]
182#[inline]
183const fn make_data_template<const COLS: usize>(addr: u8) -> [Entry; COLS] {
184 let mut data = [Entry::new(); COLS];
185 let mut i = 0;
186
187 while i < COLS {
188 let mut entry = Entry::new();
189 entry.0 = ((addr as u16) << ADDR_SHIFT) | OE_BLANK;
190
191 if i == COLS - 1 {
192 entry.0 |= LATCH_BIT;
193 } else if i >= TRAIL_BLANK_DELAY && i < COLS.saturating_sub(LEAD_BLANK_DELAY + 1) {
194 entry.0 = (entry.0 & !OE_BIT) | OE_ACTIVE;
195 }
196
197 data[map_index(i)] = entry;
198 i += 1;
199 }
200
201 data
202}
203
204bitfield! {
205 #[derive(Clone, Copy, Default, PartialEq)]
216 #[repr(transparent)]
217 struct Entry(u16);
218 dummy2, set_dummy2: PINMAP.spare[2];
219 blu2, set_blu2: PINMAP.blu1;
220 grn2, set_grn2: PINMAP.grn1;
221 red2, set_red2: PINMAP.red1;
222 blu1, set_blu1: PINMAP.blu0;
223 grn1, set_grn1: PINMAP.grn0;
224 red1, set_red1: PINMAP.red0;
225 output_enable, set_output_enable: PINMAP.oe;
226 dummy1, set_dummy1: PINMAP.spare[1];
227 dummy0, set_dummy0: PINMAP.spare[0];
228 latch, set_latch: PINMAP.latch;
229 addr, set_addr: PINMAP.addr_msb(), PINMAP.addr_lsb;
230}
231
232impl core::fmt::Debug for Entry {
233 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
234 f.debug_tuple("Entry")
235 .field(&format_args!("{:#x}", self.0))
236 .finish()
237 }
238}
239
240#[cfg(feature = "defmt")]
241impl defmt::Format for Entry {
242 fn format(&self, f: defmt::Formatter) {
243 defmt::write!(f, "Entry({=u16:#x})", self.0);
244 }
245}
246
247impl Entry {
248 const fn new() -> Self {
249 Self(OE_BLANK)
250 }
251
252 #[inline]
255 fn set_color0_bits(&mut self, bits: u8) {
256 let bits16 = u16::from(bits) << COLOR0_SHIFT;
257 self.0 = (self.0 & !COLOR0_MASK) | (bits16 & COLOR0_MASK);
258 }
259
260 #[inline]
261 fn set_color1_bits(&mut self, bits: u8) {
262 let bits16 = u16::from(bits) << COLOR1_SHIFT;
263 self.0 = (self.0 & !COLOR1_MASK) | (bits16 & COLOR1_MASK);
264 }
265}
266
267#[derive(Clone, Copy, PartialEq, Debug)]
276#[repr(C)]
277struct Row<const COLS: usize> {
278 data: [Entry; COLS],
279 gap: [Entry; INTER_ROW_BLANK],
280}
281
282const fn map_index(i: usize) -> usize {
283 #[cfg(feature = "esp32-ordering")]
284 {
285 i ^ 1
286 }
287 #[cfg(not(feature = "esp32-ordering"))]
288 {
289 i
290 }
291}
292
293impl<const COLS: usize> Default for Row<COLS> {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299impl<const COLS: usize> Row<COLS> {
300 pub const fn new() -> Self {
301 Self {
302 data: [Entry::new(); COLS],
303 gap: [Entry::new(); INTER_ROW_BLANK],
304 }
305 }
306
307 pub const fn format(&mut self, prev_addr: u8) {
308 let template = make_data_template::<COLS>(prev_addr);
309 let mut i = 0;
310 while i < COLS {
311 self.data[i] = template[i];
312 i += 1;
313 }
314
315 let gap_val = ((prev_addr as u16) << ADDR_SHIFT) | OE_BLANK;
316 let mut i = 0;
317 #[allow(clippy::absurd_extreme_comparisons)]
319 while i < INTER_ROW_BLANK {
320 self.gap[i].0 = gap_val;
321 i += 1;
322 }
323 }
324
325 #[inline]
328 pub fn clear_colors(&mut self) {
329 const COLOR_CLEAR_MASK: u16 = !COLOR_MASK;
331
332 for entry in &mut self.data {
333 entry.0 &= COLOR_CLEAR_MASK;
334 }
335 }
336
337 #[inline]
338 pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
339 let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
340 let col = map_index(col);
341 self.data[col].set_color0_bits(bits);
342 }
343
344 #[inline]
345 pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
346 let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
347 let col = map_index(col);
348 self.data[col].set_color1_bits(bits);
349 }
350}
351
352#[derive(Copy, Clone, Debug)]
353#[repr(C)]
354struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
355 rows: [Row<COLS>; NROWS],
356}
357
358impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
359 pub const fn new() -> Self {
360 Self {
361 rows: [Row::new(); NROWS],
362 }
363 }
364
365 pub const fn format(&mut self) {
366 let mut slot = 0;
367 while slot < NROWS {
368 let (prev_addr, _) = slot_addresses::<NROWS>(slot);
369 self.rows[slot].format(prev_addr);
370 slot += 1;
371 }
372 }
373
374 #[inline]
376 pub fn clear_colors(&mut self) {
377 for row in &mut self.rows {
378 row.clear_colors();
379 }
380 }
381
382 #[inline]
383 pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
384 let row_idx = map_row_index::<NROWS>(if y < NROWS { y } else { y - NROWS });
385 let row = &mut self.rows[row_idx];
386 if y < NROWS {
387 row.set_color0(x, red, green, blue);
388 } else {
389 row.set_color1(x, red, green, blue);
390 }
391 }
392}
393
394impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
395 for Frame<ROWS, COLS, NROWS>
396{
397 fn default() -> Self {
398 Self::new()
399 }
400}
401
402#[derive(Copy, Clone, Debug)]
405#[repr(C)]
406struct FrameData<const ROWS: usize, const COLS: usize, const NROWS: usize, const FRAME_COUNT: usize>
407{
408 frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
409 #[cfg(all(feature = "tail-closes-latch", feature = "esp32-ordering"))]
410 padding: Entry,
411 #[cfg(feature = "tail-closes-latch")]
412 tail: Entry,
413}
414
415impl<const ROWS: usize, const COLS: usize, const NROWS: usize, const FRAME_COUNT: usize>
416 FrameData<ROWS, COLS, NROWS, FRAME_COUNT>
417{
418 const fn new() -> Self {
419 Self {
420 frames: [Frame::new(); FRAME_COUNT],
421 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
422 padding: Entry::new(),
423 #[cfg(feature = "tail-closes-latch")]
424 tail: Entry::new(),
425 }
426 }
427}
428
429#[derive(Copy, Clone)]
454#[repr(C)]
455pub struct DmaFrameBuffer<
456 const ROWS: usize,
457 const COLS: usize,
458 const NROWS: usize,
459 const BITS: u8,
460 const FRAME_COUNT: usize,
461> {
462 _align: u64,
463 data: FrameData<ROWS, COLS, NROWS, FRAME_COUNT>,
464}
465
466impl<
467 const ROWS: usize,
468 const COLS: usize,
469 const NROWS: usize,
470 const BITS: u8,
471 const FRAME_COUNT: usize,
472 > Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
473{
474 fn default() -> Self {
475 Self::new()
476 }
477}
478
479impl<
480 const ROWS: usize,
481 const COLS: usize,
482 const NROWS: usize,
483 const BITS: u8,
484 const FRAME_COUNT: usize,
485 > DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
486{
487 #[must_use]
525 pub const fn new() -> Self {
526 assert!(BITS <= 8);
527 #[cfg(feature = "esp32-ordering")]
528 assert!(
529 COLS % 2 == 0,
530 "esp32-ordering feature requires COLS to be even"
531 );
532
533 let mut instance = Self {
534 _align: 0,
535 data: FrameData::new(),
536 };
537
538 instance.format();
539 instance
540 }
541
542 #[must_use]
545 #[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
546 pub const fn bcm_chunk_count() -> usize {
547 1
548 }
549
550 #[must_use]
554 #[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
555 pub const fn bcm_chunk_bytes() -> usize {
556 core::mem::size_of::<FrameData<ROWS, COLS, NROWS, FRAME_COUNT>>()
557 }
558
559 #[inline]
579 pub const fn format(&mut self) {
580 let mut i = 0;
581 while i < FRAME_COUNT {
582 self.data.frames[i].format();
583 i += 1;
584 }
585 #[cfg(feature = "tail-closes-latch")]
586 {
587 self.data.tail.0 = ADDR_MASK | OE_BLANK;
588 }
589 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
590 {
591 self.data.padding.0 = ADDR_MASK | OE_BLANK;
592 }
593 }
594
595 #[inline]
615 pub fn erase(&mut self) {
616 for frame in &mut self.data.frames {
617 frame.clear_colors();
618 }
619 }
620
621 pub fn set_pixel(&mut self, p: Point, color: Color) {
637 if p.x < 0 || p.y < 0 {
638 return;
639 }
640 self.set_pixel_internal(p.x as usize, p.y as usize, color);
641 }
642
643 #[inline]
644 fn frames_on(v: u8) -> usize {
645 (v as usize) >> (8 - BITS)
647 }
648
649 #[inline]
650 fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
651 if x >= COLS || y >= ROWS {
652 return;
653 }
654
655 #[cfg(feature = "skip-black-pixels")]
658 if color == Color::BLACK {
659 return;
660 }
661
662 let red_frames = Self::frames_on(color.r());
664 let green_frames = Self::frames_on(color.g());
665 let blue_frames = Self::frames_on(color.b());
666
667 for (frame_idx, frame) in self.data.frames.iter_mut().enumerate() {
669 frame.set_pixel(
670 y,
671 x,
672 frame_idx < red_frames,
673 frame_idx < green_frames,
674 frame_idx < blue_frames,
675 );
676 }
677 }
678}
679
680impl<
681 const ROWS: usize,
682 const COLS: usize,
683 const NROWS: usize,
684 const BITS: u8,
685 const FRAME_COUNT: usize,
686 > FrameBufferOperations for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
687{
688 #[inline]
689 fn erase(&mut self) {
690 DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
691 }
692
693 #[inline]
694 fn set_pixel(&mut self, p: Point, color: Color) {
695 DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
696 }
697}
698
699impl<
700 const ROWS: usize,
701 const COLS: usize,
702 const NROWS: usize,
703 const BITS: u8,
704 const FRAME_COUNT: usize,
705 > embedded_graphics::prelude::OriginDimensions
706 for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
707{
708 fn size(&self) -> embedded_graphics::prelude::Size {
709 embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
710 }
711}
712
713impl<
714 const ROWS: usize,
715 const COLS: usize,
716 const NROWS: usize,
717 const BITS: u8,
718 const FRAME_COUNT: usize,
719 > embedded_graphics::prelude::OriginDimensions
720 for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
721{
722 fn size(&self) -> embedded_graphics::prelude::Size {
723 embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
724 }
725}
726
727impl<
728 const ROWS: usize,
729 const COLS: usize,
730 const NROWS: usize,
731 const BITS: u8,
732 const FRAME_COUNT: usize,
733 > embedded_graphics::draw_target::DrawTarget
734 for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
735{
736 type Color = Color;
737
738 type Error = Infallible;
739
740 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
741 where
742 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
743 {
744 for pixel in pixels {
745 if pixel.0.x < 0 || pixel.0.y < 0 {
746 continue;
747 }
748 self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
749 }
750 Ok(())
751 }
752}
753
754impl<
755 const ROWS: usize,
756 const COLS: usize,
757 const NROWS: usize,
758 const BITS: u8,
759 const FRAME_COUNT: usize,
760 > core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
761{
762 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
763 let brightness_step = 1 << (8 - BITS);
764 let size = core::mem::size_of::<FrameData<ROWS, COLS, NROWS, FRAME_COUNT>>();
765 f.debug_struct("DmaFrameBuffer")
766 .field("size", &size)
767 .field("frame_count", &self.data.frames.len())
768 .field("frame_size", &core::mem::size_of_val(&self.data.frames[0]))
769 .field("brightness_step", &&brightness_step)
770 .finish_non_exhaustive()
771 }
772}
773
774#[cfg(feature = "defmt")]
775impl<
776 const ROWS: usize,
777 const COLS: usize,
778 const NROWS: usize,
779 const BITS: u8,
780 const FRAME_COUNT: usize,
781 > defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
782{
783 fn format(&self, f: defmt::Formatter) {
784 let brightness_step = 1 << (8 - BITS);
785 defmt::write!(
786 f,
787 "DmaFrameBuffer<{}, {}, {}, {}, {}>",
788 ROWS,
789 COLS,
790 NROWS,
791 BITS,
792 FRAME_COUNT
793 );
794 defmt::write!(
795 f,
796 " size: {}",
797 core::mem::size_of::<FrameData<ROWS, COLS, NROWS, FRAME_COUNT>>()
798 );
799 defmt::write!(
800 f,
801 " frame_size: {}",
802 core::mem::size_of_val(&self.data.frames[0])
803 );
804 defmt::write!(f, " brightness_step: {}", brightness_step);
805 }
806}
807
808impl<
809 const ROWS: usize,
810 const COLS: usize,
811 const NROWS: usize,
812 const BITS: u8,
813 const FRAME_COUNT: usize,
814 > FrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
815{
816 type Word = u16;
817
818 const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = {
819 let mut seq = [BcmLenReps::ZERO; BCM_SEQUENCE_CAPACITY];
820 seq[0] = BcmLenReps {
821 len: core::mem::size_of::<FrameData<ROWS, COLS, NROWS, FRAME_COUNT>>(),
822 reps: 1,
823 };
824 seq
825 };
826
827 const BCM_SEQUENCE_LEN: usize = 1;
828
829 const BCM_SEQUENCE_COUNT: usize = 1;
830
831 fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
832 assert!(index == 0, "threshold DmaFrameBuffer has only 1 segment");
833 (&raw const self.data).cast::<u8>()
834 }
835}
836
837impl<
838 const ROWS: usize,
839 const COLS: usize,
840 const NROWS: usize,
841 const BITS: u8,
842 const FRAME_COUNT: usize,
843 > MutableFrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
844{
845}
846
847#[cfg(test)]
848mod tests {
849 extern crate std;
850
851 use std::format;
852 use std::vec;
853
854 use super::*;
855 use embedded_graphics::pixelcolor::RgbColor;
856 use embedded_graphics::prelude::*;
857 use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
858
859 const TEST_ROWS: usize = 32;
860 const TEST_COLS: usize = if LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 2 > 64 {
861 128
862 } else {
863 64
864 };
865 const TEST_NROWS: usize = TEST_ROWS / 2;
866 const TEST_BITS: u8 = 3;
867 const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1; type TestFrameBuffer =
870 DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
871
872 fn get_mapped_index(index: usize) -> usize {
874 map_index(index)
875 }
876
877 fn get_mapped_row(row_idx: usize) -> usize {
880 map_row_index::<TEST_NROWS>(row_idx)
881 }
882
883 #[test]
884 fn test_entry_construction() {
885 let entry = Entry::new();
886 let expect_oe = cfg!(feature = "invert-oe");
887 assert_eq!(entry.dummy2(), false);
888 assert_eq!(entry.blu2(), false);
889 assert_eq!(entry.grn2(), false);
890 assert_eq!(entry.red2(), false);
891 assert_eq!(entry.blu1(), false);
892 assert_eq!(entry.grn1(), false);
893 assert_eq!(entry.red1(), false);
894 assert_eq!(entry.output_enable(), expect_oe);
895 assert_eq!(entry.dummy1(), false);
896 assert_eq!(entry.dummy0(), false);
897 assert_eq!(entry.latch(), false);
898 assert_eq!(entry.addr(), 0);
899 }
900
901 #[test]
902 fn test_entry_setters() {
903 let mut entry = Entry::new();
904
905 entry.set_dummy2(true);
906 assert_eq!(entry.dummy2(), true);
907
908 entry.set_blu2(true);
909 assert_eq!(entry.blu2(), true);
910
911 entry.set_grn2(true);
912 assert_eq!(entry.grn2(), true);
913
914 entry.set_red2(true);
915 assert_eq!(entry.red2(), true);
916
917 entry.set_blu1(true);
918 assert_eq!(entry.blu1(), true);
919
920 entry.set_grn1(true);
921 assert_eq!(entry.grn1(), true);
922
923 entry.set_red1(true);
924 assert_eq!(entry.red1(), true);
925 assert_eq!(entry.0 & COLOR0_MASK, COLOR0_MASK);
926
927 entry.set_output_enable(true);
928 assert_eq!(entry.output_enable(), true);
929 assert_eq!(entry.0 & OE_BIT, OE_BIT);
930
931 entry.set_dummy1(true);
932 assert_eq!(entry.dummy1(), true);
933
934 entry.set_dummy0(true);
935 assert_eq!(entry.dummy0(), true);
936
937 entry.set_latch(true);
938 assert_eq!(entry.latch(), true);
939 assert_eq!(entry.0 & LATCH_BIT, LATCH_BIT);
940
941 entry.set_addr(0b11111);
942 assert_eq!(entry.addr(), 0b11111);
943 assert_eq!(entry.0 & (0x1f_u16 << ADDR_SHIFT), 0x1f_u16 << ADDR_SHIFT);
944 }
945
946 #[test]
947 fn test_entry_bit_isolation() {
948 let mut entry = Entry::new();
949 let expect_oe = cfg!(feature = "invert-oe");
950
951 entry.set_addr(0b11111);
953 entry.set_latch(true);
954 assert_eq!(entry.addr(), 0b11111);
955 assert_eq!(entry.latch(), true);
956 assert_eq!(entry.output_enable(), expect_oe);
957 assert_eq!(entry.red1(), false);
958
959 entry.set_red1(true);
960 entry.set_grn2(true);
961 assert_eq!(entry.addr(), 0b11111);
962 assert_eq!(entry.latch(), true);
963 assert_eq!(entry.red1(), true);
964 assert_eq!(entry.grn2(), true);
965 assert_eq!(entry.blu1(), false);
966 assert_eq!(entry.red2(), false);
967 }
968
969 #[test]
970 fn test_entry_set_color0() {
971 let mut entry = Entry::new();
972
973 let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); entry.set_color0_bits(bits);
975 assert_eq!(entry.red1(), true);
976 assert_eq!(entry.grn1(), false);
977 assert_eq!(entry.blu1(), true);
978 assert_eq!(entry.0 & COLOR0_MASK, u16::from(bits) << COLOR0_SHIFT);
980 }
981
982 #[test]
983 fn test_entry_set_color1() {
984 let mut entry = Entry::new();
985
986 let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); entry.set_color1_bits(bits);
988 assert_eq!(entry.red2(), false);
989 assert_eq!(entry.grn2(), true);
990 assert_eq!(entry.blu2(), true);
991 assert_eq!(entry.0 & COLOR1_MASK, u16::from(bits) << COLOR1_SHIFT);
993 }
994
995 #[cfg(feature = "interstate75")]
999 #[test]
1000 fn interstate75_word_layout_matches_pinout() {
1001 let blanked = Entry::new();
1004 assert_eq!(blanked.0, OE_BLANK);
1005
1006 let mut e = Entry::new();
1007 e.set_red1(true); e.set_blu1(true); e.set_grn2(true); e.set_addr(0b1_0101); e.set_latch(true); let expected = 0b1u16 | (0b1 << 2) | (0b1 << 4) | (0b1_0101 << 6) | (1 << 12) | OE_BLANK;
1014 assert_eq!(e.0, expected);
1015 assert_eq!(e.0 & (1 << 11), 0, "unused bit 11 must stay 0");
1016 assert_eq!(e.0 & (0b11 << 14), 0, "bits 14/15 must stay 0");
1017 }
1018
1019 #[test]
1020 fn test_entry_debug_formatting() {
1021 let entry = Entry(0x1234);
1022 let debug_str = format!("{:?}", entry);
1023 assert_eq!(debug_str, "Entry(0x1234)");
1024
1025 let entry = Entry(0xabcd);
1026 let debug_str = format!("{:?}", entry);
1027 assert_eq!(debug_str, "Entry(0xabcd)");
1028 }
1029
1030 #[test]
1031 fn test_row_construction() {
1032 let row: Row<TEST_COLS> = Row::new();
1033 assert_eq!(row.data.len(), TEST_COLS);
1034
1035 let expected = Entry::new().0;
1036 for entry in &row.data {
1037 assert_eq!(entry.0, expected);
1038 }
1039 }
1040
1041 #[test]
1042 fn test_row_format() {
1043 let mut row: Row<TEST_COLS> = Row::new();
1044 let prev_addr = 4;
1045
1046 row.format(prev_addr);
1047
1048 let oe_active = !cfg!(feature = "invert-oe");
1049
1050 for (physical_i, entry) in row.data.iter().enumerate() {
1051 let logical_i = get_mapped_index(physical_i);
1052
1053 assert_eq!(entry.addr(), u16::from(prev_addr));
1054
1055 if logical_i == TEST_COLS - 1 {
1056 assert_eq!(entry.latch(), true);
1057 assert_eq!(entry.output_enable(), !oe_active);
1058 } else {
1059 assert_eq!(entry.latch(), false);
1060 if logical_i >= TRAIL_BLANK_DELAY && logical_i < TEST_COLS - LEAD_BLANK_DELAY - 1 {
1061 assert_eq!(entry.output_enable(), oe_active);
1062 } else {
1063 assert_eq!(entry.output_enable(), !oe_active);
1064 }
1065 }
1066 }
1067 }
1068
1069 #[test]
1070 fn test_row_set_color0() {
1071 let mut row: Row<TEST_COLS> = Row::new();
1072
1073 row.set_color0(0, true, false, true);
1074
1075 let mapped_col_0 = get_mapped_index(0);
1076 assert_eq!(row.data[mapped_col_0].red1(), true);
1077 assert_eq!(row.data[mapped_col_0].grn1(), false);
1078 assert_eq!(row.data[mapped_col_0].blu1(), true);
1079
1080 row.set_color0(1, false, true, false);
1082
1083 let mapped_col_1 = get_mapped_index(1);
1084 assert_eq!(row.data[mapped_col_1].red1(), false);
1085 assert_eq!(row.data[mapped_col_1].grn1(), true);
1086 assert_eq!(row.data[mapped_col_1].blu1(), false);
1087 }
1088
1089 #[test]
1090 fn test_row_set_color1() {
1091 let mut row: Row<TEST_COLS> = Row::new();
1092
1093 row.set_color1(0, true, true, false);
1094
1095 let mapped_col_0 = get_mapped_index(0);
1096 assert_eq!(row.data[mapped_col_0].red2(), true);
1097 assert_eq!(row.data[mapped_col_0].grn2(), true);
1098 assert_eq!(row.data[mapped_col_0].blu2(), false);
1099 }
1100
1101 #[test]
1102 fn test_row_default() {
1103 let row1: Row<TEST_COLS> = Row::new();
1104 let row2: Row<TEST_COLS> = Row::default();
1105
1106 assert_eq!(row1, row2);
1108 assert_eq!(row1.data.len(), row2.data.len());
1109
1110 let expected = Entry::new().0;
1111 for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1112 assert_eq!(entry1.0, entry2.0);
1113 assert_eq!(entry1.0, expected);
1114 }
1115 }
1116
1117 #[test]
1118 fn test_frame_construction() {
1119 let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1120 assert_eq!(frame.rows.len(), TEST_NROWS);
1121 }
1122
1123 #[test]
1124 fn test_frame_format() {
1125 let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1126
1127 frame.format();
1128
1129 for slot in 0..TEST_NROWS {
1130 let (prev_addr, _) = slot_addresses::<TEST_NROWS>(slot);
1131 let row = &frame.rows[slot];
1132
1133 let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1134 assert_eq!(row.data[last_pixel_idx].addr(), u16::from(prev_addr));
1135 assert_eq!(row.data[last_pixel_idx].latch(), true);
1136
1137 let first_pixel_idx = get_mapped_index(0);
1138 assert_eq!(row.data[first_pixel_idx].addr(), u16::from(prev_addr));
1139 assert_eq!(row.data[first_pixel_idx].latch(), false);
1140 }
1141 }
1142
1143 #[test]
1144 fn test_frame_set_pixel() {
1145 let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1146
1147 frame.set_pixel(5, 10, true, false, true);
1149
1150 let mapped_col_10 = get_mapped_index(10);
1151 assert_eq!(
1152 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1153 true
1154 );
1155 assert_eq!(
1156 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1157 false
1158 );
1159 assert_eq!(
1160 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1161 true
1162 );
1163
1164 frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
1166
1167 let mapped_col_15 = get_mapped_index(15);
1168 assert_eq!(
1169 frame.rows[get_mapped_row(5)].data[mapped_col_15].red2(),
1170 false
1171 );
1172 assert_eq!(
1173 frame.rows[get_mapped_row(5)].data[mapped_col_15].grn2(),
1174 true
1175 );
1176 assert_eq!(
1177 frame.rows[get_mapped_row(5)].data[mapped_col_15].blu2(),
1178 false
1179 );
1180 }
1181
1182 #[test]
1183 #[cfg(feature = "reverse-row-order")]
1184 fn test_reverse_row_order() {
1185 let mut fb = TestFrameBuffer::new();
1186
1187 let first = &fb.data.frames[0].rows[0];
1191 assert_eq!(first.data[get_mapped_index(0)].addr(), 0);
1192 let last = &fb.data.frames[0].rows[TEST_NROWS - 1];
1194 assert_eq!(last.data[get_mapped_index(0)].addr(), 1);
1195
1196 fb.set_pixel(Point::new(3, 0), Color::RED);
1198 let col3 = get_mapped_index(3);
1199 assert!(fb.data.frames[0].rows[TEST_NROWS - 1].data[col3].red1());
1200 assert!(!fb.data.frames[0].rows[0].data[col3].red1());
1201 }
1202
1203 #[test]
1204 fn test_frame_default() {
1205 let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1206 let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
1207
1208 assert_eq!(frame1.rows.len(), frame2.rows.len());
1210
1211 let expected = Entry::new().0;
1212 for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
1213 assert_eq!(row1, row2);
1214
1215 for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1216 assert_eq!(entry1.0, entry2.0);
1217 assert_eq!(entry1.0, expected);
1218 }
1219 }
1220 }
1221
1222 #[test]
1223 fn test_dma_framebuffer_construction() {
1224 let fb = TestFrameBuffer::new();
1225 assert_eq!(fb.data.frames.len(), TEST_FRAME_COUNT);
1226 assert_eq!(fb._align, 0);
1227 }
1228
1229 #[test]
1230 #[allow(deprecated)]
1231 fn test_bcm_chunk_info() {
1232 let expected_size =
1233 core::mem::size_of::<FrameData<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_FRAME_COUNT>>();
1234 assert_eq!(TestFrameBuffer::bcm_chunk_bytes(), expected_size);
1235 assert_eq!(TestFrameBuffer::bcm_chunk_count(), 1);
1236 }
1237
1238 #[test]
1239 fn test_dma_framebuffer_erase() {
1240 let fb = TestFrameBuffer::new();
1241
1242 for frame in &fb.data.frames {
1243 for slot in 0..TEST_NROWS {
1244 let (prev_addr, _) = slot_addresses::<TEST_NROWS>(slot);
1245 let row = &frame.rows[slot];
1246
1247 let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1248 assert_eq!(row.data[last_pixel_idx].addr(), u16::from(prev_addr));
1249 assert_eq!(row.data[last_pixel_idx].latch(), true);
1250
1251 let first_pixel_idx = get_mapped_index(0);
1252 assert_eq!(row.data[first_pixel_idx].addr(), u16::from(prev_addr));
1253 assert_eq!(row.data[first_pixel_idx].latch(), false);
1254 }
1255 }
1256 }
1257
1258 #[test]
1259 fn test_dma_framebuffer_set_pixel_bounds() {
1260 let mut fb = TestFrameBuffer::new();
1261
1262 fb.set_pixel(Point::new(-1, 5), Color::RED);
1264 fb.set_pixel(Point::new(5, -1), Color::RED);
1265
1266 fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
1268 fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
1269 }
1270
1271 #[test]
1272 fn test_dma_framebuffer_set_pixel_internal() {
1273 let mut fb = TestFrameBuffer::new();
1274
1275 let red_color = Color::RED;
1276 fb.set_pixel_internal(10, 5, red_color);
1277
1278 for frame in &fb.data.frames {
1282 let mapped_col_10 = get_mapped_index(10);
1284 assert_eq!(
1285 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1286 true
1287 );
1288 assert_eq!(
1289 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1290 false
1291 );
1292 assert_eq!(
1293 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1294 false
1295 );
1296 }
1297 }
1298
1299 #[test]
1300 fn test_dma_framebuffer_brightness_modulation() {
1301 let mut fb = TestFrameBuffer::new();
1302
1303 let brightness_step = 1 << (8 - TEST_BITS); let test_brightness = brightness_step * 3; let color = Color::from(embedded_graphics::pixelcolor::Rgb888::new(
1307 test_brightness,
1308 0,
1309 0,
1310 ));
1311
1312 fb.set_pixel_internal(0, 0, color);
1313
1314 for (frame_idx, frame) in fb.data.frames.iter().enumerate() {
1317 let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1318 let should_be_active = test_brightness >= frame_threshold;
1319
1320 let mapped_col_0 = get_mapped_index(0);
1321 assert_eq!(
1322 frame.rows[get_mapped_row(0)].data[mapped_col_0].red1(),
1323 should_be_active
1324 );
1325 }
1326 }
1327
1328 #[test]
1329 fn test_origin_dimensions() {
1330 let fb = TestFrameBuffer::new();
1331 let size = fb.size();
1332 assert_eq!(size.width, TEST_COLS as u32);
1333 assert_eq!(size.height, TEST_ROWS as u32);
1334
1335 let size = (&fb).size();
1337 assert_eq!(size.width, TEST_COLS as u32);
1338 assert_eq!(size.height, TEST_ROWS as u32);
1339
1340 let mut fb = TestFrameBuffer::new();
1342 let fb_ref = &mut fb;
1343 let size = fb_ref.size();
1344 assert_eq!(size.width, TEST_COLS as u32);
1345 assert_eq!(size.height, TEST_ROWS as u32);
1346 }
1347
1348 #[test]
1349 fn test_draw_target() {
1350 let mut fb = TestFrameBuffer::new();
1351
1352 let pixels = vec![
1353 embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
1354 embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
1355 embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
1356 ];
1357
1358 let result = fb.draw_iter(pixels);
1359 assert!(result.is_ok());
1360
1361 let result = (&mut fb).draw_iter(vec![embedded_graphics::Pixel(
1363 Point::new(3, 3),
1364 Color::WHITE,
1365 )]);
1366 assert!(result.is_ok());
1367 }
1368
1369 #[test]
1370 fn test_draw_iter_pixel_verification() {
1371 let mut fb = TestFrameBuffer::new();
1372
1373 let pixels = vec![
1375 embedded_graphics::Pixel(Point::new(5, 2), Color::RED), embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), ];
1387
1388 let result = fb.draw_iter(pixels);
1389 assert!(result.is_ok());
1390
1391 let first_frame = &fb.data.frames[0];
1393 let brightness_step = 1 << (8 - TEST_BITS); let first_frame_threshold = brightness_step; let col_idx = get_mapped_index(5);
1399 assert_eq!(
1400 first_frame.rows[get_mapped_row(2)].data[col_idx].red1(),
1401 Color::RED.r() >= first_frame_threshold
1402 );
1403 assert_eq!(
1404 first_frame.rows[get_mapped_row(2)].data[col_idx].grn1(),
1405 Color::RED.g() >= first_frame_threshold
1406 );
1407 assert_eq!(
1408 first_frame.rows[get_mapped_row(2)].data[col_idx].blu1(),
1409 Color::RED.b() >= first_frame_threshold
1410 );
1411
1412 let col_idx = get_mapped_index(10);
1414 assert_eq!(
1415 first_frame.rows[get_mapped_row(5)].data[col_idx].red1(),
1416 Color::GREEN.r() >= first_frame_threshold
1417 );
1418 assert_eq!(
1419 first_frame.rows[get_mapped_row(5)].data[col_idx].grn1(),
1420 Color::GREEN.g() >= first_frame_threshold
1421 );
1422 assert_eq!(
1423 first_frame.rows[get_mapped_row(5)].data[col_idx].blu1(),
1424 Color::GREEN.b() >= first_frame_threshold
1425 );
1426
1427 let col_idx = get_mapped_index(15);
1429 assert_eq!(
1430 first_frame.rows[get_mapped_row(8)].data[col_idx].red1(),
1431 Color::BLUE.r() >= first_frame_threshold
1432 );
1433 assert_eq!(
1434 first_frame.rows[get_mapped_row(8)].data[col_idx].grn1(),
1435 Color::BLUE.g() >= first_frame_threshold
1436 );
1437 assert_eq!(
1438 first_frame.rows[get_mapped_row(8)].data[col_idx].blu1(),
1439 Color::BLUE.b() >= first_frame_threshold
1440 );
1441
1442 let col_idx = get_mapped_index(20);
1444 assert_eq!(
1445 first_frame.rows[get_mapped_row(10)].data[col_idx].red1(),
1446 Color::WHITE.r() >= first_frame_threshold
1447 );
1448 assert_eq!(
1449 first_frame.rows[get_mapped_row(10)].data[col_idx].grn1(),
1450 Color::WHITE.g() >= first_frame_threshold
1451 );
1452 assert_eq!(
1453 first_frame.rows[get_mapped_row(10)].data[col_idx].blu1(),
1454 Color::WHITE.b() >= first_frame_threshold
1455 );
1456
1457 let col_idx = get_mapped_index(25);
1460 assert_eq!(
1461 first_frame.rows[get_mapped_row(3)].data[col_idx].red2(),
1462 Color::RED.r() >= first_frame_threshold
1463 );
1464 assert_eq!(
1465 first_frame.rows[get_mapped_row(3)].data[col_idx].grn2(),
1466 Color::RED.g() >= first_frame_threshold
1467 );
1468 assert_eq!(
1469 first_frame.rows[get_mapped_row(3)].data[col_idx].blu2(),
1470 Color::RED.b() >= first_frame_threshold
1471 );
1472
1473 let col_idx = get_mapped_index(30);
1475 assert_eq!(
1476 first_frame.rows[get_mapped_row(7)].data[col_idx].red2(),
1477 Color::GREEN.r() >= first_frame_threshold
1478 );
1479 assert_eq!(
1480 first_frame.rows[get_mapped_row(7)].data[col_idx].grn2(),
1481 Color::GREEN.g() >= first_frame_threshold
1482 );
1483 assert_eq!(
1484 first_frame.rows[get_mapped_row(7)].data[col_idx].blu2(),
1485 Color::GREEN.b() >= first_frame_threshold
1486 );
1487
1488 let col_idx = get_mapped_index(35);
1490 assert_eq!(
1491 first_frame.rows[get_mapped_row(12)].data[col_idx].red2(),
1492 Color::BLUE.r() >= first_frame_threshold
1493 );
1494 assert_eq!(
1495 first_frame.rows[get_mapped_row(12)].data[col_idx].grn2(),
1496 Color::BLUE.g() >= first_frame_threshold
1497 );
1498 assert_eq!(
1499 first_frame.rows[get_mapped_row(12)].data[col_idx].blu2(),
1500 Color::BLUE.b() >= first_frame_threshold
1501 );
1502
1503 let col_idx = get_mapped_index(40);
1505 assert_eq!(
1506 first_frame.rows[get_mapped_row(1)].data[col_idx].red1(),
1507 false
1508 );
1509 assert_eq!(
1510 first_frame.rows[get_mapped_row(1)].data[col_idx].grn1(),
1511 false
1512 );
1513 assert_eq!(
1514 first_frame.rows[get_mapped_row(1)].data[col_idx].blu1(),
1515 false
1516 );
1517 }
1518
1519 #[test]
1520 fn test_embedded_graphics_integration() {
1521 let mut fb = TestFrameBuffer::new();
1522
1523 let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
1525 .into_styled(PrimitiveStyle::with_fill(Color::RED))
1526 .draw(&mut fb);
1527 assert!(result.is_ok());
1528
1529 let result = Circle::new(Point::new(30, 15), 8)
1531 .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
1532 .draw(&mut fb);
1533 assert!(result.is_ok());
1534 }
1535
1536 #[test]
1537 #[cfg(feature = "skip-black-pixels")]
1538 fn test_skip_black_pixels_enabled() {
1539 let mut fb = TestFrameBuffer::new();
1540
1541 fb.set_pixel_internal(10, 5, Color::RED);
1543
1544 let mapped_col_10 = get_mapped_index(10);
1546 assert_eq!(
1547 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1548 true
1549 );
1550 assert_eq!(
1551 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1552 false
1553 );
1554 assert_eq!(
1555 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1556 false
1557 );
1558
1559 fb.set_pixel_internal(10, 5, Color::BLACK);
1561
1562 assert_eq!(
1564 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1565 true
1566 );
1567 assert_eq!(
1568 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1569 false
1570 );
1571 assert_eq!(
1572 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1573 false
1574 );
1575 }
1576
1577 #[test]
1578 #[cfg(not(feature = "skip-black-pixels"))]
1579 fn test_skip_black_pixels_disabled() {
1580 let mut fb = TestFrameBuffer::new();
1581
1582 fb.set_pixel_internal(10, 5, Color::RED);
1584
1585 let mapped_col_10 = get_mapped_index(10);
1587 assert_eq!(
1588 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1589 true
1590 );
1591 assert_eq!(
1592 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1593 false
1594 );
1595 assert_eq!(
1596 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1597 false
1598 );
1599
1600 fb.set_pixel_internal(10, 5, Color::BLACK);
1602
1603 assert_eq!(
1605 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1606 false
1607 );
1608 assert_eq!(
1609 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1610 false
1611 );
1612 assert_eq!(
1613 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1614 false
1615 );
1616 }
1617
1618 #[test]
1619 fn test_bcm_frame_overwrite() {
1620 let mut fb = TestFrameBuffer::new();
1621
1622 fb.set_pixel_internal(10, 5, Color::WHITE);
1624
1625 let mapped_col_10 = get_mapped_index(10);
1626
1627 for frame in fb.data.frames.iter() {
1629 assert_eq!(
1631 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1632 true
1633 );
1634 assert_eq!(
1635 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1636 true
1637 );
1638 assert_eq!(
1639 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1640 true
1641 );
1642 }
1643
1644 let half_white = Color::from(embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128));
1646 fb.set_pixel_internal(10, 5, half_white);
1647
1648 let brightness_step = 1 << (8 - TEST_BITS); for (frame_idx, frame) in fb.data.frames.iter().enumerate() {
1654 let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1655 let should_be_active = 128 >= frame_threshold;
1656
1657 assert_eq!(
1658 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1659 should_be_active
1660 );
1661 assert_eq!(
1662 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1663 should_be_active
1664 );
1665 assert_eq!(
1666 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1667 should_be_active
1668 );
1669 }
1670
1671 for frame_idx in 0..4 {
1674 assert_eq!(
1675 fb.data.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1676 true
1677 );
1678 }
1679 for frame_idx in 4..TEST_FRAME_COUNT {
1681 assert_eq!(
1682 fb.data.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1683 false
1684 );
1685 }
1686 }
1687
1688 #[test]
1689 fn test_debug_formatting() {
1690 let fb = TestFrameBuffer::new();
1691 let debug_string = format!("{:?}", fb);
1692 assert!(debug_string.contains("DmaFrameBuffer"));
1693 assert!(debug_string.contains("frame_count"));
1694 assert!(debug_string.contains("frame_size"));
1695 assert!(debug_string.contains("brightness_step"));
1696 }
1697
1698 #[test]
1699 fn test_default_implementation() {
1700 let fb1 = TestFrameBuffer::new();
1701 let fb2 = TestFrameBuffer::default();
1702
1703 assert_eq!(fb1.data.frames.len(), fb2.data.frames.len());
1705 assert_eq!(fb1._align, fb2._align);
1706 }
1707
1708 #[test]
1709 fn test_memory_alignment() {
1710 let fb = TestFrameBuffer::new();
1711 let ptr = &fb as *const _ as usize;
1712
1713 assert_eq!(ptr % 8, 0);
1715 }
1716
1717 #[test]
1718 fn test_color_values() {
1719 let mut fb = TestFrameBuffer::new();
1720
1721 let colors = [
1723 (Color::RED, (255, 0, 0)),
1724 (Color::GREEN, (0, 255, 0)),
1725 (Color::BLUE, (0, 0, 255)),
1726 (Color::WHITE, (255, 255, 255)),
1727 (Color::BLACK, (0, 0, 0)),
1728 ];
1729
1730 for (i, (color, (r, g, b))) in colors.iter().enumerate() {
1731 fb.set_pixel(Point::new(i as i32, 0), *color);
1732 assert_eq!(color.r(), *r);
1733 assert_eq!(color.g(), *g);
1734 assert_eq!(color.b(), *b);
1735 }
1736 }
1737
1738 #[test]
1739 fn test_blanking_delay() {
1740 let mut row: Row<TEST_COLS> = Row::new();
1741 let prev_addr = 4;
1742
1743 row.format(prev_addr);
1744
1745 let oe_active = !cfg!(feature = "invert-oe");
1746
1747 if TRAIL_BLANK_DELAY > 0 {
1749 let trail_blank_idx = get_mapped_index(TRAIL_BLANK_DELAY - 1);
1750 assert_eq!(row.data[trail_blank_idx].output_enable(), !oe_active);
1751 }
1752
1753 let after_trail_idx = get_mapped_index(TRAIL_BLANK_DELAY);
1754 assert_eq!(row.data[after_trail_idx].output_enable(), oe_active);
1755
1756 let before_lead_idx = get_mapped_index(TEST_COLS - LEAD_BLANK_DELAY - 2);
1758 assert_eq!(row.data[before_lead_idx].output_enable(), oe_active);
1759
1760 let lead_blank_idx = get_mapped_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
1761 assert_eq!(row.data[lead_blank_idx].output_enable(), !oe_active);
1762 }
1763
1764 #[test]
1765 fn test_esp32_mapping() {
1766 #[cfg(feature = "esp32-ordering")]
1768 {
1769 assert_eq!(map_index(0), 1);
1770 assert_eq!(map_index(1), 0);
1771 assert_eq!(map_index(2), 3);
1772 assert_eq!(map_index(3), 2);
1773 assert_eq!(map_index(4), 5);
1774 assert_eq!(map_index(5), 4);
1775 }
1776 #[cfg(not(feature = "esp32-ordering"))]
1777 {
1778 assert_eq!(map_index(0), 0);
1779 assert_eq!(map_index(1), 1);
1780 assert_eq!(map_index(2), 2);
1781 assert_eq!(map_index(3), 3);
1782 }
1783 }
1784
1785 #[test]
1786 fn test_bits_assertion() {
1787 assert!(TEST_BITS <= 8);
1790 }
1791
1792 #[test]
1793 fn test_fast_clear_method() {
1794 let mut fb = TestFrameBuffer::new();
1795
1796 fb.set_pixel_internal(10, 5, Color::RED);
1798 fb.set_pixel_internal(20, 10, Color::GREEN);
1799
1800 let mapped_col_10 = get_mapped_index(10);
1802 assert_eq!(
1803 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1804 true
1805 );
1806
1807 fb.erase();
1809
1810 assert_eq!(
1812 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1813 false
1814 );
1815 assert_eq!(
1816 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1817 false
1818 );
1819 assert_eq!(
1820 fb.data.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1821 false
1822 );
1823
1824 let last_col = get_mapped_index(TEST_COLS - 1);
1826 assert_eq!(
1827 fb.data.frames[0].rows[get_mapped_row(5)].data[last_col].latch(),
1828 true
1829 );
1830 }
1831
1832 const CHAR_W: i32 = 6;
1836 const CHAR_H: i32 = 10;
1837
1838 fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
1840 use embedded_graphics::mock_display::MockDisplay;
1841 use embedded_graphics::mono_font::ascii::FONT_6X10;
1842 use embedded_graphics::mono_font::MonoTextStyle;
1843 use embedded_graphics::text::{Baseline, Text};
1844
1845 let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
1847 Text::with_baseline("A", origin, style, Baseline::Top)
1848 .draw(fb)
1849 .unwrap();
1850
1851 let mut reference: MockDisplay<Color> = MockDisplay::new();
1853 Text::with_baseline("A", Point::zero(), style, Baseline::Top)
1854 .draw(&mut reference)
1855 .unwrap();
1856
1857 for dy in 0..CHAR_H {
1858 for dx in 0..CHAR_W {
1859 let expected_on = reference
1860 .get_pixel(Point::new(dx, dy))
1861 .unwrap_or(Color::BLACK)
1862 != Color::BLACK;
1863
1864 let gx = (origin.x + dx) as usize;
1865 let gy = (origin.y + dy) as usize;
1866
1867 let frame0 = &fb.data.frames[0];
1874 let e = if gy < TEST_NROWS {
1875 &frame0.rows[get_mapped_row(gy)].data[get_mapped_index(gx)]
1876 } else {
1877 &frame0.rows[get_mapped_row(gy - TEST_NROWS)].data[get_mapped_index(gx)]
1878 };
1879
1880 let (r, g, b) = if gy >= TEST_NROWS {
1881 (e.red2(), e.grn2(), e.blu2())
1882 } else {
1883 (e.red1(), e.grn1(), e.blu1())
1884 };
1885
1886 if expected_on {
1887 assert!(r && g && b,);
1888 } else {
1889 assert!(!r && !g && !b);
1890 }
1891 }
1892 }
1893 }
1894
1895 #[test]
1896 fn test_draw_char_corners() {
1897 let upper_left = Point::new(0, 0);
1898 let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
1899
1900 let mut fb = TestFrameBuffer::new();
1901
1902 verify_glyph_at(&mut fb, upper_left);
1903 verify_glyph_at(&mut fb, lower_right);
1904 }
1905
1906 #[test]
1907 fn test_framebuffer_operations_trait_erase() {
1908 let mut fb = TestFrameBuffer::new();
1909
1910 fb.set_pixel_internal(10, 5, Color::RED);
1912 fb.set_pixel_internal(20, 10, Color::GREEN);
1913
1914 <TestFrameBuffer as FrameBufferOperations>::erase(&mut fb);
1916
1917 let mc10 = get_mapped_index(10);
1919 let mc20 = get_mapped_index(20);
1920 assert_eq!(
1921 fb.data.frames[0].rows[get_mapped_row(5)].data[mc10].red1(),
1922 false
1923 );
1924 assert_eq!(
1925 fb.data.frames[0].rows[get_mapped_row(10)].data[mc20].grn1(),
1926 false
1927 );
1928
1929 let last_col = get_mapped_index(TEST_COLS - 1);
1931 assert!(fb.data.frames[0].rows[get_mapped_row(0)].data[last_col].latch());
1932 }
1933
1934 #[test]
1935 fn test_framebuffer_operations_trait_set_pixel() {
1936 let mut fb = TestFrameBuffer::new();
1937
1938 <TestFrameBuffer as FrameBufferOperations>::set_pixel(
1940 &mut fb,
1941 Point::new(8, 3),
1942 Color::BLUE,
1943 );
1944
1945 let idx = get_mapped_index(8);
1946 assert_eq!(
1947 fb.data.frames[0].rows[get_mapped_row(3)].data[idx].blu1(),
1948 true
1949 );
1950 assert_eq!(
1951 fb.data.frames[0].rows[get_mapped_row(3)].data[idx].red1(),
1952 false
1953 );
1954 assert_eq!(
1955 fb.data.frames[0].rows[get_mapped_row(3)].data[idx].grn1(),
1956 false
1957 );
1958 }
1959
1960 #[test]
1961 fn test_entry_new_oe_matches_feature() {
1962 let entry = Entry::new();
1963 if cfg!(feature = "invert-oe") {
1964 assert!(entry.output_enable());
1965 } else {
1966 assert!(!entry.output_enable());
1967 }
1968 }
1969
1970 #[test]
1971 fn test_make_data_template_oe_polarity() {
1972 let mut row = Row::<TEST_COLS>::new();
1973 row.format(4);
1974
1975 let active_idx = get_mapped_index(TRAIL_BLANK_DELAY);
1976 let blank_idx = get_mapped_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
1977 let latch_idx = get_mapped_index(TEST_COLS - 1);
1978
1979 if cfg!(feature = "invert-oe") {
1980 assert!(!row.data[active_idx].output_enable());
1981 assert!(row.data[blank_idx].output_enable());
1982 assert!(row.data[latch_idx].output_enable());
1983 } else {
1984 assert!(row.data[active_idx].output_enable());
1985 assert!(!row.data[blank_idx].output_enable());
1986 assert!(!row.data[latch_idx].output_enable());
1987 }
1988 }
1989
1990 static STATIC_FB: TestFrameBuffer = TestFrameBuffer::new();
1991
1992 #[test]
1993 fn test_static_construction_is_formatted() {
1994 let runtime_fb = TestFrameBuffer::new();
1995
1996 for (fi, frame) in STATIC_FB.data.frames.iter().enumerate() {
1997 for (ri, row) in frame.rows.iter().enumerate() {
1998 assert_eq!(
1999 row.data, runtime_fb.data.frames[fi].rows[ri].data,
2000 "static vs runtime mismatch at frame {fi}, row {ri}"
2001 );
2002 assert_eq!(
2003 row.gap, runtime_fb.data.frames[fi].rows[ri].gap,
2004 "static vs runtime gap mismatch at frame {fi}, row {ri}"
2005 );
2006 }
2007 }
2008 }
2009
2010 #[test]
2011 fn test_format_reinitializes_at_runtime() {
2012 let mut fb = TestFrameBuffer::new();
2013 fb.erase();
2014 fb.format();
2015
2016 for (fi, frame) in fb.data.frames.iter().enumerate() {
2017 for (ri, row) in frame.rows.iter().enumerate() {
2018 assert_eq!(
2019 row.data, STATIC_FB.data.frames[fi].rows[ri].data,
2020 "re-formatted vs static mismatch at frame {fi}, row {ri}"
2021 );
2022 }
2023 }
2024 }
2025
2026 #[test]
2027 fn bcm_segment_count_is_one() {
2028 use crate::FrameBuffer;
2029 let fb = TestFrameBuffer::new();
2030 assert_eq!(fb.bcm_segment_count(), 1);
2031 }
2032
2033 #[test]
2034 fn bcm_segment_covers_entire_buffer() {
2035 use crate::FrameBuffer;
2036 let fb = TestFrameBuffer::new();
2037 let seg = fb.bcm_segment(0);
2038 let expected_ptr = (&raw const fb.data).cast::<u8>();
2039 let expected_len =
2040 core::mem::size_of::<FrameData<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_FRAME_COUNT>>();
2041 assert_eq!(seg.ptr, expected_ptr);
2042 assert_eq!(seg.len, expected_len);
2043 assert_eq!(seg.reps, 1);
2044 }
2045
2046 #[test]
2047 #[should_panic(expected = "out of range")]
2048 fn bcm_segment_panics_for_invalid_index() {
2049 use crate::FrameBuffer;
2050 let fb = TestFrameBuffer::new();
2051 let _ = fb.bcm_segment(1);
2052 }
2053
2054 #[test]
2055 fn bcm_sequence_matches_runtime_segments() {
2056 use crate::FrameBuffer;
2057 let fb = TestFrameBuffer::new();
2058 assert_eq!(
2059 TestFrameBuffer::BCM_SEGMENT_COUNT,
2060 TestFrameBuffer::BCM_SEQUENCE_LEN * TestFrameBuffer::BCM_SEQUENCE_COUNT
2061 );
2062 assert_eq!(fb.bcm_segment_count(), TestFrameBuffer::BCM_SEGMENT_COUNT);
2063 for i in 0..TestFrameBuffer::BCM_SEGMENT_COUNT {
2064 let entry = TestFrameBuffer::BCM_SEQUENCE[i % TestFrameBuffer::BCM_SEQUENCE_LEN];
2065 let seg = fb.bcm_segment(i);
2066 assert_eq!((seg.len, seg.reps), (entry.len, entry.reps), "segment {i}");
2067 assert!(!seg.ptr.is_null(), "segment {i} has null pointer");
2068 }
2069 for entry in &TestFrameBuffer::BCM_SEQUENCE[TestFrameBuffer::BCM_SEQUENCE_LEN..] {
2070 assert_eq!(*entry, crate::BcmLenReps::ZERO, "padding must be zero");
2071 }
2072 }
2073}