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