1use core::convert::Infallible;
71
72use bitfield::bitfield;
73use embedded_graphics::pixelcolor::RgbColor;
74use embedded_graphics::prelude::{DrawTarget, OriginDimensions, Point, Size};
75
76use crate::Color;
77use crate::FrameBuffer;
78use crate::{FrameBufferOperations, MutableFrameBuffer};
79
80#[cfg(feature = "lead-blank-1")]
81const LEAD_BLANK_DELAY: usize = 1;
82#[cfg(feature = "lead-blank-2")]
83const LEAD_BLANK_DELAY: usize = 2;
84#[cfg(feature = "lead-blank-4")]
85const LEAD_BLANK_DELAY: usize = 4;
86#[cfg(feature = "lead-blank-8")]
87const LEAD_BLANK_DELAY: usize = 8;
88#[cfg(feature = "lead-blank-16")]
89const LEAD_BLANK_DELAY: usize = 16;
90
91#[cfg(not(any(
93 feature = "lead-blank-1",
94 feature = "lead-blank-2",
95 feature = "lead-blank-4",
96 feature = "lead-blank-8",
97 feature = "lead-blank-16"
98)))]
99const LEAD_BLANK_DELAY: usize = 1;
100
101#[cfg(feature = "trail-blank-1")]
102const TRAIL_BLANK_DELAY: usize = 1;
103#[cfg(feature = "trail-blank-2")]
104const TRAIL_BLANK_DELAY: usize = 2;
105#[cfg(feature = "trail-blank-4")]
106const TRAIL_BLANK_DELAY: usize = 4;
107#[cfg(feature = "trail-blank-8")]
108const TRAIL_BLANK_DELAY: usize = 8;
109#[cfg(feature = "trail-blank-16")]
110const TRAIL_BLANK_DELAY: usize = 16;
111
112#[cfg(not(any(
114 feature = "trail-blank-1",
115 feature = "trail-blank-2",
116 feature = "trail-blank-4",
117 feature = "trail-blank-8",
118 feature = "trail-blank-16"
119)))]
120const TRAIL_BLANK_DELAY: usize = 1;
121
122#[cfg(not(feature = "invert-oe"))]
123const OE_ACTIVE: u16 = 0b1_0000_0000;
124#[cfg(not(feature = "invert-oe"))]
125const OE_BLANK: u16 = 0;
126
127#[cfg(feature = "invert-oe")]
128const OE_ACTIVE: u16 = 0;
129#[cfg(feature = "invert-oe")]
130const OE_BLANK: u16 = 0b1_0000_0000;
131
132#[inline]
133const fn map_index(i: usize) -> usize {
134 #[cfg(feature = "esp32-ordering")]
135 {
136 i ^ 1
137 }
138 #[cfg(not(feature = "esp32-ordering"))]
139 {
140 i
141 }
142}
143
144#[inline]
145const fn make_data_template<const COLS: usize>(addr: u8, prev_addr: u8) -> [Entry; COLS] {
146 let mut data = [Entry::new(); COLS];
147 let mut i = 0;
148
149 while i < COLS {
150 let mut entry = Entry::new();
151 entry.0 = prev_addr as u16 | OE_BLANK;
153
154 if i == COLS - 1 {
155 entry.0 |= 0b0010_0000; entry.0 = (entry.0 & !0b0001_1111) | (addr as u16); } else if i >= TRAIL_BLANK_DELAY && i < COLS.saturating_sub(LEAD_BLANK_DELAY + 1) {
159 entry.0 = (entry.0 & !0b1_0000_0000) | OE_ACTIVE;
161 }
162
163 data[map_index(i)] = entry;
164 i += 1;
165 }
166
167 data
168}
169
170bitfield! {
171 #[derive(Clone, Copy, Default, PartialEq)]
172 #[repr(transparent)]
173 pub(crate) struct Entry(u16);
174 pub(crate) dummy2, set_dummy2: 15;
175 pub(crate) blu2, set_blu2: 14;
176 pub(crate) grn2, set_grn2: 13;
177 pub(crate) red2, set_red2: 12;
178 pub(crate) blu1, set_blu1: 11;
179 pub(crate) grn1, set_grn1: 10;
180 pub(crate) red1, set_red1: 9;
181 pub(crate) output_enable, set_output_enable: 8;
182 pub(crate) dummy1, set_dummy1: 7;
183 pub(crate) dummy0, set_dummy0: 6;
184 pub(crate) latch, set_latch: 5;
185 pub(crate) addr, set_addr: 4, 0;
186}
187
188impl core::fmt::Debug for Entry {
189 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190 f.debug_tuple("Entry")
191 .field(&format_args!("{:#x}", self.0))
192 .finish()
193 }
194}
195
196impl Entry {
197 const fn new() -> Self {
198 Self(OE_BLANK)
199 }
200
201 const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; const COLOR1_MASK: u16 = 0b0111_0000_0000_0000; #[inline]
205 fn set_color0_bits(&mut self, bits: u8) {
206 let bits16 = u16::from(bits) << 9;
207 self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
208 }
209
210 #[inline]
211 fn set_color1_bits(&mut self, bits: u8) {
212 let bits16 = u16::from(bits) << 12;
213 self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
214 }
215}
216
217#[derive(Clone, Copy, PartialEq, Debug)]
218#[repr(C)]
219pub struct Row<const COLS: usize> {
224 pub(crate) data: [Entry; COLS],
225}
226
227impl<const COLS: usize> Row<COLS> {
228 #[must_use]
232 pub const fn new() -> Self {
233 Self {
234 data: [Entry::new(); COLS],
235 }
236 }
237
238 #[inline]
243 pub fn format(&mut self, addr: u8, prev_addr: u8) {
244 let template = make_data_template::<COLS>(addr, prev_addr);
245 self.data.copy_from_slice(&template);
246 }
247}
248
249impl<const COLS: usize> Default for Row<COLS> {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255#[derive(Clone, Copy, PartialEq, Debug)]
258#[repr(C)]
259pub struct PlaneData<const NROWS: usize, const COLS: usize> {
260 pub(crate) rows: [Row<COLS>; NROWS],
261 #[cfg(all(feature = "tail-closes-latch", feature = "esp32-ordering"))]
266 pub(crate) padding: Entry,
267 #[cfg(feature = "tail-closes-latch")]
268 pub(crate) tail: Entry,
269}
270
271impl<const NROWS: usize, const COLS: usize> PlaneData<NROWS, COLS> {
272 const fn new() -> Self {
273 Self {
274 rows: [Row::new(); NROWS],
275 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
276 padding: Entry::new(),
277 #[cfg(feature = "tail-closes-latch")]
278 tail: Entry::new(),
279 }
280 }
281}
282
283#[derive(Copy, Clone)]
285#[repr(C)]
286pub struct DmaFrameBuffer<const NROWS: usize, const COLS: usize, const PLANES: usize> {
287 pub(crate) planes: [PlaneData<NROWS, COLS>; PLANES],
288}
289
290impl<const NROWS: usize, const COLS: usize, const PLANES: usize>
291 DmaFrameBuffer<NROWS, COLS, PLANES>
292{
293 #[must_use]
295 pub fn new() -> Self {
296 let mut instance = Self {
297 planes: [PlaneData::new(); PLANES],
298 };
299 instance.format();
300 instance
301 }
302
303 #[must_use]
305 pub const fn bcm_chunk_count() -> usize {
306 PLANES
307 }
308
309 #[must_use]
311 pub const fn bcm_chunk_bytes() -> usize {
312 core::mem::size_of::<PlaneData<NROWS, COLS>>()
313 }
314
315 #[inline]
317 pub fn format(&mut self) {
318 for plane in &mut self.planes {
319 for (row_idx, row) in plane.rows.iter_mut().enumerate() {
320 let prev_addr = if row_idx == 0 {
321 NROWS as u8 - 1
322 } else {
323 row_idx as u8 - 1
324 };
325 row.format(row_idx as u8, prev_addr);
326 }
327 #[cfg(feature = "tail-closes-latch")]
328 {
329 plane.tail.0 = 0x1f | OE_BLANK;
330 }
331 #[cfg(all(feature = "esp32-ordering", feature = "tail-closes-latch"))]
332 {
333 plane.padding.0 = 0x1f | OE_BLANK;
334 }
335 }
336 }
337
338 #[inline]
340 pub fn erase(&mut self) {
341 const MASK: u16 = !0b0111_1110_0000_0000; for plane in &mut self.planes {
343 for row in &mut plane.rows {
344 for entry in &mut row.data {
345 entry.0 &= MASK;
346 }
347 }
348 }
349 }
350
351 #[inline]
353 pub fn set_pixel(&mut self, p: Point, color: Color) {
354 if p.x < 0 || p.y < 0 {
355 return;
356 }
357 self.set_pixel_internal(p.x as usize, p.y as usize, color);
358 }
359
360 #[inline]
361 fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
362 if x >= COLS || y >= NROWS * 2 {
363 return;
364 }
365
366 let row_idx = if y < NROWS { y } else { y - NROWS };
367 let is_top = y < NROWS;
368 let red = color.r();
369 let green = color.g();
370 let blue = color.b();
371
372 for plane_idx in 0..PLANES {
373 let bit = 7_u32.saturating_sub(plane_idx as u32);
374 let bits = ((u8::from(((blue >> bit) & 1) != 0)) << 2)
375 | ((u8::from(((green >> bit) & 1) != 0)) << 1)
376 | u8::from(((red >> bit) & 1) != 0);
377 let col_idx = map_index(x);
378 let entry = &mut self.planes[plane_idx].rows[row_idx].data[col_idx];
379 if is_top {
380 entry.set_color0_bits(bits);
381 } else {
382 entry.set_color1_bits(bits);
383 }
384 }
385 }
386}
387
388impl<const NROWS: usize, const COLS: usize, const PLANES: usize> Default
389 for DmaFrameBuffer<NROWS, COLS, PLANES>
390{
391 fn default() -> Self {
392 Self::new()
393 }
394}
395
396impl<const NROWS: usize, const COLS: usize, const PLANES: usize> core::fmt::Debug
397 for DmaFrameBuffer<NROWS, COLS, PLANES>
398{
399 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
400 f.debug_struct("DmaFrameBuffer")
401 .field("size", &core::mem::size_of_val(&self.planes))
402 .field("plane_count", &self.planes.len())
403 .field("plane_size", &core::mem::size_of_val(&self.planes[0]))
404 .finish()
405 }
406}
407
408#[cfg(feature = "defmt")]
409impl<const NROWS: usize, const COLS: usize, const PLANES: usize> defmt::Format
410 for DmaFrameBuffer<NROWS, COLS, PLANES>
411{
412 fn format(&self, f: defmt::Formatter) {
413 defmt::write!(f, "DmaFrameBuffer<{}, {}, {}>", NROWS, COLS, PLANES);
414 defmt::write!(f, " size: {}", core::mem::size_of_val(&self.planes));
415 defmt::write!(
416 f,
417 " plane_size: {}",
418 core::mem::size_of_val(&self.planes[0])
419 );
420 }
421}
422
423impl<const NROWS: usize, const COLS: usize, const PLANES: usize> FrameBuffer
424 for DmaFrameBuffer<NROWS, COLS, PLANES>
425{
426 type Word = u16;
427
428 fn plane_count(&self) -> usize {
429 PLANES
430 }
431
432 fn plane_ptr_len(&self, plane_idx: usize) -> (*const u8, usize) {
433 assert!(
434 plane_idx < PLANES,
435 "plane_idx {plane_idx} out of range for {PLANES} planes"
436 );
437 let ptr = (&raw const self.planes[plane_idx]).cast::<u8>();
438 let len = core::mem::size_of::<PlaneData<NROWS, COLS>>();
439 (ptr, len)
440 }
441}
442
443impl<const NROWS: usize, const COLS: usize, const PLANES: usize> FrameBufferOperations
444 for DmaFrameBuffer<NROWS, COLS, PLANES>
445{
446 #[inline]
447 fn erase(&mut self) {
448 DmaFrameBuffer::<NROWS, COLS, PLANES>::erase(self);
449 }
450
451 #[inline]
452 fn set_pixel(&mut self, p: Point, color: Color) {
453 DmaFrameBuffer::<NROWS, COLS, PLANES>::set_pixel(self, p, color);
454 }
455}
456
457impl<const NROWS: usize, const COLS: usize, const PLANES: usize> MutableFrameBuffer
458 for DmaFrameBuffer<NROWS, COLS, PLANES>
459{
460}
461
462impl<const NROWS: usize, const COLS: usize, const PLANES: usize> OriginDimensions
463 for DmaFrameBuffer<NROWS, COLS, PLANES>
464{
465 fn size(&self) -> Size {
466 Size::new(COLS as u32, (NROWS * 2) as u32)
467 }
468}
469
470impl<const NROWS: usize, const COLS: usize, const PLANES: usize> DrawTarget
471 for DmaFrameBuffer<NROWS, COLS, PLANES>
472{
473 type Color = Color;
474 type Error = Infallible;
475
476 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
477 where
478 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
479 {
480 for pixel in pixels {
481 self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
482 }
483 Ok(())
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 extern crate std;
490
491 use super::*;
492 use embedded_graphics::prelude::*;
493 use std::format;
494
495 const TEST_COLS: usize = 64;
496 type TestBuffer = DmaFrameBuffer<16, 64, 8>;
497
498 #[test]
499 fn row_format_sets_address_and_control_bits() {
500 let mut row = Row::<TEST_COLS>::new();
501 row.format(5, 4);
502
503 let last_idx = map_index(TEST_COLS - 1);
504 assert_eq!(row.data[last_idx].latch(), true);
505 assert_eq!(row.data[last_idx].addr(), 5);
506
507 let first_idx = map_index(0);
508 assert_eq!(row.data[first_idx].addr(), 4);
509 assert_eq!(row.data[first_idx].latch(), false);
510 }
511
512 #[test]
513 fn format_sets_expected_row_addresses_for_all_rows() {
514 let mut fb = TestBuffer::new();
515 fb.format();
516
517 for plane_idx in 0..8 {
518 for row_idx in 0..16 {
519 let last_col = map_index(63);
520 assert_eq!(
521 fb.planes[plane_idx].rows[row_idx].data[last_col].addr(),
522 row_idx as u16
523 );
524 assert_eq!(
525 fb.planes[plane_idx].rows[row_idx].data[last_col].latch(),
526 true
527 );
528 }
529 }
530 }
531
532 #[test]
533 fn set_pixel_maps_top_half_bits_per_plane() {
534 let mut fb = TestBuffer::new();
535 let color = Color::new(0b1010_0101, 0b0101_1010, 0b1111_0000);
536 fb.set_pixel(Point::new(2, 3), color);
537
538 for plane_idx in 0..8 {
539 let bit = 7 - plane_idx;
540 let entry = fb.planes[plane_idx].rows[3].data[map_index(2)];
541 assert_eq!(entry.red1(), ((color.r() >> bit) & 1) != 0);
542 assert_eq!(entry.grn1(), ((color.g() >> bit) & 1) != 0);
543 assert_eq!(entry.blu1(), ((color.b() >> bit) & 1) != 0);
544 }
545 }
546
547 #[test]
548 fn set_pixel_maps_bottom_half_bits_per_plane() {
549 let mut fb = TestBuffer::new();
550 let color = Color::new(0b1100_0011, 0b0011_1100, 0b1001_0110);
551 fb.set_pixel(Point::new(4, 20), color);
552
553 for plane_idx in 0..8 {
554 let bit = 7 - plane_idx;
555 let entry = fb.planes[plane_idx].rows[4].data[map_index(4)];
556 assert_eq!(entry.red2(), ((color.r() >> bit) & 1) != 0);
557 assert_eq!(entry.grn2(), ((color.g() >> bit) & 1) != 0);
558 assert_eq!(entry.blu2(), ((color.b() >> bit) & 1) != 0);
559 }
560 }
561
562 #[test]
563 fn erase_clears_only_color_bits() {
564 let mut fb = TestBuffer::new();
565 let oe_before = fb.planes[0].rows[0].data[map_index(1)].output_enable();
566 fb.set_pixel(Point::new(0, 0), Color::WHITE);
567 fb.erase();
568
569 for plane in &fb.planes {
570 for row in &plane.rows {
571 for entry in &row.data {
572 assert!(!entry.red1());
573 assert!(!entry.grn1());
574 assert!(!entry.blu1());
575 assert!(!entry.red2());
576 assert!(!entry.grn2());
577 assert!(!entry.blu2());
578 }
579 }
580 }
581
582 assert_eq!(
583 fb.planes[0].rows[0].data[map_index(1)].output_enable(),
584 oe_before
585 );
586 }
587
588 #[test]
589 fn draw_target_iter_sets_pixels() {
590 let mut fb = TestBuffer::new();
591 let pixels = [Pixel(Point::new(1, 1), Color::RED)];
592 let result = fb.draw_iter(pixels);
593 assert!(result.is_ok());
594
595 for plane_idx in 0..8 {
596 let bit = 7 - plane_idx;
597 let entry = fb.planes[plane_idx].rows[1].data[map_index(1)];
598 assert_eq!(entry.red1(), ((Color::RED.r() >> bit) & 1) != 0);
599 assert!(!entry.grn1());
600 assert!(!entry.blu1());
601 }
602 }
603
604 #[test]
605 fn set_pixel_ignores_out_of_bounds_and_negative() {
606 let mut fb = TestBuffer::new();
607 let before = fb.planes;
608 fb.set_pixel(Point::new(-1, 0), Color::WHITE);
609 fb.set_pixel(Point::new(0, -1), Color::WHITE);
610 fb.set_pixel(Point::new(64, 0), Color::WHITE);
611 fb.set_pixel(Point::new(0, 32), Color::WHITE);
612 assert_eq!(fb.planes, before);
613 }
614
615 #[test]
616 fn bcm_chunk_info_for_common_panel() {
617 assert_eq!(TestBuffer::bcm_chunk_count(), 8);
618 assert_eq!(
619 TestBuffer::bcm_chunk_bytes(),
620 core::mem::size_of::<PlaneData<16, 64>>()
621 );
622 }
623
624 #[test]
625 fn frame_buffer_trait_accessors_report_expected_values() {
626 let fb = TestBuffer::new();
627 let as_trait: &dyn FrameBuffer<Word = u16> = &fb;
628 assert_eq!(as_trait.get_word_size(), crate::WordSize::Sixteen);
629 assert_eq!(as_trait.plane_count(), 8);
630
631 let (ptr, len) = as_trait.plane_ptr_len(0);
632 assert_eq!(len, core::mem::size_of::<PlaneData<16, 64>>());
633 assert_eq!(ptr, (&raw const fb.planes[0]).cast::<u8>());
634 }
635
636 #[test]
637 #[should_panic(expected = "out of range")]
638 fn plane_ptr_len_panics_for_invalid_plane() {
639 let fb = TestBuffer::new();
640 let _ = fb.plane_ptr_len(8);
641 }
642
643 #[test]
644 fn origin_dimensions_match_panel_geometry() {
645 let fb = TestBuffer::new();
646 assert_eq!(fb.size(), Size::new(64, 32));
647 }
648
649 #[test]
650 fn debug_impl_includes_shape_information() {
651 let fb = TestBuffer::new();
652 let s = format!("{fb:?}");
653 assert!(s.contains("DmaFrameBuffer"));
654 assert!(s.contains("plane_count"));
655 assert!(s.contains("plane_size"));
656 }
657
658 #[test]
659 fn row_format_sets_expected_blank_and_latch_positions() {
660 let mut row = Row::<TEST_COLS>::new();
661 row.format(5, 4);
662
663 let oe_active = !cfg!(feature = "invert-oe");
664
665 let idx_active = map_index(TRAIL_BLANK_DELAY);
667 assert_eq!(row.data[idx_active].output_enable(), oe_active);
668 assert_eq!(row.data[idx_active].addr(), 4);
669
670 let idx_blank = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
672 assert_eq!(row.data[idx_blank].output_enable(), !oe_active);
673
674 let idx_last = map_index(TEST_COLS - 1);
676 assert!(row.data[idx_last].latch());
677 assert_eq!(row.data[idx_last].addr(), 5);
678 }
679
680 #[test]
681 fn default_constructors_match_new() {
682 let row_default = Row::<TEST_COLS>::default();
683 let row_new = Row::<TEST_COLS>::new();
684 assert_eq!(row_default, row_new);
685
686 let fb_default = TestBuffer::default();
687 let fb_new = TestBuffer::new();
688 assert_eq!(fb_default.planes, fb_new.planes);
689 }
690
691 #[test]
692 fn framebuffer_operations_trait_delegates_correctly() {
693 let mut fb = TestBuffer::new();
694 FrameBufferOperations::set_pixel(&mut fb, Point::new(3, 5), Color::GREEN);
695
696 assert!(fb.planes[0].rows[5].data[map_index(3)].grn1());
697
698 FrameBufferOperations::erase(&mut fb);
699 for plane in &fb.planes {
700 for row in &plane.rows {
701 for entry in &row.data {
702 assert!(!entry.red1());
703 assert!(!entry.grn1());
704 assert!(!entry.blu1());
705 assert!(!entry.red2());
706 assert!(!entry.grn2());
707 assert!(!entry.blu2());
708 }
709 }
710 }
711 }
712
713 #[test]
714 fn entry_debug_shows_hex_value() {
715 let mut entry = Entry::new();
716 entry.set_red1(true);
717 let s = format!("{entry:?}");
718 assert!(s.contains("Entry"));
719 assert!(s.contains("0x"));
720 }
721
722 #[test]
723 fn entry_new_oe_matches_feature() {
724 let entry = Entry::new();
725 if cfg!(feature = "invert-oe") {
726 assert!(entry.output_enable());
727 } else {
728 assert!(!entry.output_enable());
729 }
730 }
731
732 #[test]
733 fn make_data_template_oe_polarity() {
734 let mut row = Row::<TEST_COLS>::new();
735 row.format(5, 4);
736
737 let active_idx = map_index(TRAIL_BLANK_DELAY);
738 let blank_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
739 let latch_idx = map_index(TEST_COLS - 1);
740
741 if cfg!(feature = "invert-oe") {
742 assert!(!row.data[active_idx].output_enable());
743 assert!(row.data[blank_idx].output_enable());
744 assert!(row.data[latch_idx].output_enable());
745 } else {
746 assert!(row.data[active_idx].output_enable());
747 assert!(!row.data[blank_idx].output_enable());
748 assert!(!row.data[latch_idx].output_enable());
749 }
750 }
751}