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