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