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