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