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