1use embedded_hal::{delay::DelayNs, spi::SpiDevice};
4
5use crate::{
6 Error, MAX_DISPLAYS, Max7219, Register, Result,
7 led_matrix::{
8 buffer::MatrixBuffer,
9 fonts::{self, LedFont},
10 scroll::{ScrollConfig, ScrollingText},
11 },
12};
13
14pub type SingleMatrix<SPI> = LedMatrix<SPI, 64, 1>;
16
17pub type Matrix4<SPI> = LedMatrix<SPI, 256, 4>;
19
20pub type Matrix8<SPI> = LedMatrix<SPI, 512, 8>;
22
23pub struct LedMatrix<SPI, const BUFFER_LENGTH: usize = 64, const DEVICE_COUNT: usize = 1> {
25 driver: Max7219<SPI>,
26 framebuffer: [u8; BUFFER_LENGTH],
36}
37
38impl<SPI, const BUFFER_LENGTH: usize, const DEVICE_COUNT: usize>
39 LedMatrix<SPI, BUFFER_LENGTH, DEVICE_COUNT>
40where
41 SPI: SpiDevice,
42{
43 pub fn from_spi(spi: SPI) -> Result<Self> {
64 let mut driver = Max7219::new(spi).with_device_count(DEVICE_COUNT)?;
65 driver.init()?;
66 Ok(Self {
67 driver,
68 framebuffer: [0; BUFFER_LENGTH],
69 })
70 }
71
72 pub fn from_driver(driver: Max7219<SPI>) -> Result<Self> {
103 if driver.device_count() != DEVICE_COUNT {
104 return Err(Error::InvalidDeviceCount);
105 }
106 Ok(Self {
107 driver,
108 framebuffer: [0; BUFFER_LENGTH],
109 })
110 }
111
112 pub fn driver(&mut self) -> &mut Max7219<SPI> {
116 &mut self.driver
117 }
118
119 pub fn clear(&mut self, device_index: usize) -> Result<()> {
121 self.driver.clear_display(device_index)
122 }
123
124 pub fn clear_all(&mut self) -> Result<()> {
126 self.driver.clear_all()
127 }
128
129 pub fn write_buffer(&mut self, device_index: usize, buffer: &MatrixBuffer) -> Result<()> {
131 for (row, &data) in buffer.data().iter().enumerate() {
132 self.driver.write_raw_digit(device_index, row as u8, data)?;
133 }
134 Ok(())
135 }
136
137 pub fn draw_char(&mut self, device_index: usize, ch: char) -> Result<()> {
152 self.draw_char_with_font(device_index, ch, &fonts::STANDARD_LED_FONT)
153 }
154
155 pub fn draw_char_with_font(
167 &mut self,
168 device_index: usize,
169 ch: char,
170 font: &LedFont,
171 ) -> Result<()> {
172 let bitmap = font.get_char(ch);
173 for (row, value) in bitmap.iter().enumerate() {
175 self.driver
176 .write_raw_digit(device_index, row as u8, *value)?;
177 }
178 Ok(())
179 }
180
181 pub fn draw_text(&mut self, text: &str) -> Result<()> {
185 self.draw_text_with_font(text, &fonts::STANDARD_LED_FONT)
186 }
187
188 pub fn draw_text_with_font(&mut self, text: &str, font: &LedFont) -> Result<()> {
192 let device_count = self.driver.device_count();
193
194 let mut row_data = [[0u8; MAX_DISPLAYS]; 8];
195
196 for (i, ch) in text.chars().take(device_count).enumerate() {
197 let device_index = device_count - 1 - i;
198 let bitmap = font.get_char(ch);
199 for (row, &value) in bitmap.iter().enumerate() {
200 row_data[row][device_index] = value;
201 }
202 }
203
204 for (row_index, digit_register) in Register::digits().enumerate() {
212 let ops_row = row_data[row_index];
213 let mut ops = [(Register::NoOp, 0); MAX_DISPLAYS];
214
215 for (device_index, op) in ops.iter_mut().take(device_count).enumerate() {
216 *op = (digit_register, ops_row[device_index]);
217 }
218
219 self.driver.write_all_registers(&ops[..device_count])?;
220 }
221
222 Ok(())
223 }
224
225 pub fn scroll_text<D: DelayNs>(
243 &mut self,
244 delay: &mut D,
245 text: &str,
246 config: ScrollConfig,
247 ) -> Result<()> {
248 let mut scroller = ScrollingText::new(text, &fonts::STANDARD_LED_FONT, config);
249 scroller.reset();
250
251 let device_count = self.driver().device_count();
252
253 loop {
254 let base_offset = scroller.current_offset;
256
257 for device_index in 0..device_count {
259 scroller.current_offset = base_offset + (device_index as i32 * 8);
262
263 let frame = scroller.get_frame()?; self.write_buffer(device_index, &frame)?;
265 }
266
267 scroller.current_offset = base_offset;
269
270 if !scroller.step() {
271 break; }
273
274 delay.delay_ns(config.step_delay_ns);
275 }
276
277 Ok(())
278 }
279
280 pub fn flush(&mut self) -> Result<()> {
299 for (row, digit_register) in Register::digits().enumerate() {
300 let mut ops = [(Register::NoOp, 0); MAX_DISPLAYS];
301
302 for device_index in 0..DEVICE_COUNT {
303 let buffer_start = device_index * 64 + row * 8;
304 let mut packed_byte = 0;
305 for col in 0..8 {
306 let pixel_index = buffer_start + col;
307 if pixel_index < self.framebuffer.len() && self.framebuffer[pixel_index] != 0 {
308 packed_byte |= 1 << (7 - col);
310 }
311 }
312
313 let ops_index = DEVICE_COUNT - 1 - device_index;
315 ops[ops_index] = (digit_register, packed_byte);
316 }
317
318 self.driver.write_all_registers(&ops[..DEVICE_COUNT])?;
319 }
320 Ok(())
321 }
322
323 pub fn clear_buffer(&mut self) {
325 self.framebuffer.fill(0);
326 }
327
328 pub fn clear_screen(&mut self) -> Result<()> {
330 self.clear_buffer();
331 self.flush()
332 }
333}
334
335#[cfg(feature = "graphics")]
336mod eg_imports {
337 pub use embedded_graphics_core::Pixel;
338
339 pub use embedded_graphics_core::pixelcolor::BinaryColor;
340 pub use embedded_graphics_core::prelude::{DrawTarget, OriginDimensions, Size};
341}
342
343#[cfg(feature = "graphics")]
344use eg_imports::*;
345#[cfg(feature = "graphics")]
346use embedded_graphics_core::geometry::Dimensions;
347
348#[cfg(feature = "graphics")]
350impl<SPI, const BUFFER_LENGTH: usize, const DEVICE_COUNT: usize> DrawTarget
351 for LedMatrix<SPI, BUFFER_LENGTH, DEVICE_COUNT>
352where
353 SPI: SpiDevice,
354{
355 type Color = BinaryColor;
356 type Error = core::convert::Infallible;
357
358 fn draw_iter<I>(&mut self, pixels: I) -> core::result::Result<(), Self::Error>
359 where
360 I: IntoIterator<Item = Pixel<Self::Color>>,
361 {
362 let bb = self.bounding_box();
363 for Pixel(pos, color) in pixels.into_iter() {
364 if bb.contains(pos) {
365 let device = (pos.x as usize) / 8;
366 let col = (pos.x as usize) % 8;
367 let row = pos.y as usize;
368
369 if device < DEVICE_COUNT && row < 8 && col < 8 {
370 let index = device * 64 + row * 8 + col;
371 if index < self.framebuffer.len() {
372 self.framebuffer[index] = color.is_on() as u8;
373 }
374 }
375 }
376 }
377 Ok(())
379 }
380}
381
382#[cfg(feature = "graphics")]
383impl<SPI, const BUFFER_LENGTH: usize, const DEVICE_COUNT: usize> OriginDimensions
384 for LedMatrix<SPI, BUFFER_LENGTH, DEVICE_COUNT>
385{
386 fn size(&self) -> Size {
387 Size::new(DEVICE_COUNT as u32 * 8, 8)
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use crate::Error;
394 use crate::led_matrix::display::{Matrix4, SingleMatrix};
395 use crate::led_matrix::fonts::STANDARD_LED_FONT;
396 use crate::led_matrix::{LedMatrix, buffer::MatrixBuffer, fonts::LedFont};
397 use crate::registers::Register;
398 use crate::{Max7219, NUM_DIGITS};
399 use embedded_hal_mock::eh1::{spi::Mock as SpiMock, spi::Transaction};
400
401 fn write_reg(addr: u8, value: u8) -> Vec<Transaction<u8>> {
402 vec![
403 Transaction::transaction_start(),
404 Transaction::write_vec(vec![addr, value]),
405 Transaction::transaction_end(),
406 ]
407 }
408
409 #[test]
410 fn test_new() {
411 let mut spi = SpiMock::new(&[]);
412 let driver = Max7219::new(&mut spi);
413 let matrix: LedMatrix<_, 64, 1> = LedMatrix::from_driver(driver).unwrap();
414 assert_eq!(matrix.framebuffer, [0u8; 64]);
415 spi.done();
416 }
417
418 #[test]
419 fn test_from_spi() {
420 let mut expected_transactions: Vec<Transaction<u8>> = vec![];
421
422 expected_transactions.extend(write_reg(Register::Shutdown.addr(), 0x01));
424
425 expected_transactions.extend(write_reg(Register::DisplayTest.addr(), 0x00));
427
428 expected_transactions.extend(write_reg(Register::ScanLimit.addr(), NUM_DIGITS - 1));
430
431 expected_transactions.extend(write_reg(
433 Register::DecodeMode.addr(),
434 crate::registers::DecodeMode::NoDecode as u8,
435 ));
436
437 for digit in Register::digits() {
439 expected_transactions.extend(write_reg(digit.addr(), 0x00));
440 }
441
442 let mut spi = SpiMock::new(&expected_transactions);
443 let result: crate::Result<LedMatrix<_, 64, 1>> = LedMatrix::from_spi(&mut spi);
444
445 assert!(result.is_ok());
446 spi.done();
447 }
448
449 #[test]
450 fn test_from_spi_invalid_count() {
451 let mut spi = SpiMock::new(&[]);
452 let driver = Max7219::new(&mut spi);
453 let result = LedMatrix::<_, 1024, 256>::from_driver(driver);
455 assert!(matches!(result, Err(Error::InvalidDeviceCount)));
456
457 let driver = Max7219::new(&mut spi);
459 let result = Matrix4::from_driver(driver);
460 assert!(matches!(result, Err(Error::InvalidDeviceCount)));
461
462 spi.done();
463 }
464
465 #[test]
466 fn test_clear() {
467 let mut expected_transactions = vec![];
468 for digit_register in Register::digits() {
469 expected_transactions.extend(write_reg(digit_register.addr(), 0x00));
470 }
471
472 let mut spi = SpiMock::new(&expected_transactions);
473 let driver = Max7219::new(&mut spi);
474 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
475
476 let result = matrix.clear(0);
477 assert!(result.is_ok());
478 spi.done();
479 }
480
481 #[test]
482 fn test_clear_all() {
483 let mut expected_transactions = vec![];
485 let device_count = 4;
486
487 for digit_register in Register::digits() {
488 expected_transactions.push(Transaction::transaction_start());
489 let batched_data = (0..device_count)
490 .flat_map(|_| vec![digit_register.addr(), 0x00])
491 .collect();
492 expected_transactions.push(Transaction::write_vec(batched_data));
493 expected_transactions.push(Transaction::transaction_end());
494 }
495
496 let mut spi = SpiMock::new(&expected_transactions);
497 let driver = Max7219::new(&mut spi).with_device_count(4).unwrap();
498 let mut matrix = Matrix4::from_driver(driver).expect("valid initialization");
499
500 let result = matrix.clear_all();
501 assert!(result.is_ok());
502 spi.done();
503 }
504
505 #[test]
506 fn test_write_buffer() {
507 let device_index = 0;
508 let mut buffer = MatrixBuffer::new();
509 for i in 0..8 {
511 buffer.set_row(i, 0b10101010 << (i % 2)).unwrap();
512 }
513 let expected_data = buffer.data();
514
515 let mut expected_transactions = Vec::new();
516 for (row, &data) in expected_data.iter().enumerate() {
517 expected_transactions.push(Transaction::transaction_start());
518 expected_transactions.push(Transaction::write_vec(vec![
519 Register::try_digit(row as u8).unwrap().addr(),
520 data,
521 ]));
522 expected_transactions.push(Transaction::transaction_end());
523 }
524
525 let mut spi = SpiMock::new(&expected_transactions);
526 let driver = Max7219::new(&mut spi);
527 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
528
529 let result = matrix.write_buffer(device_index, &buffer);
530 assert!(result.is_ok());
531 spi.done();
532 }
533
534 #[test]
535 fn test_write_buffer_invalid_index() {
536 let mut spi = SpiMock::new(&[]); let driver = Max7219::new(&mut spi).with_device_count(1).unwrap();
538 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
539 let buffer = MatrixBuffer::new();
540
541 let result = matrix.write_buffer(1, &buffer); assert_eq!(result, Err(Error::InvalidDeviceIndex));
544 spi.done();
545 }
546
547 #[test]
548 fn test_draw_char() {
549 let device_index = 0;
550 let ch = 'A';
551 let expected_bitmap = STANDARD_LED_FONT.get_char(ch);
552
553 let mut expected_transactions = Vec::new();
554 for (row, &data) in expected_bitmap.iter().enumerate() {
555 expected_transactions.push(Transaction::transaction_start());
556 expected_transactions.push(Transaction::write_vec(vec![
557 Register::try_digit(row as u8).unwrap().addr(),
558 data,
559 ]));
560 expected_transactions.push(Transaction::transaction_end());
561 }
562
563 let mut spi = SpiMock::new(&expected_transactions);
564 let driver = Max7219::new(&mut spi);
565 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
566
567 let result = matrix.draw_char(device_index, ch);
568 assert!(result.is_ok());
569 spi.done();
570 }
571
572 #[test]
573 fn test_draw_char_with_font() {
574 let device_index = 0;
575 let ch = 'Z';
576 let test_font = STANDARD_LED_FONT;
577 let expected_bitmap = test_font.get_char(ch);
578
579 let mut expected_transactions = Vec::new();
580 for (row, &data) in expected_bitmap.iter().enumerate() {
581 expected_transactions.push(Transaction::transaction_start());
582 expected_transactions.push(Transaction::write_vec(vec![
583 Register::try_digit(row as u8).unwrap().addr(),
584 data,
585 ]));
586 expected_transactions.push(Transaction::transaction_end());
587 }
588
589 let mut spi = SpiMock::new(&expected_transactions);
590 let driver = Max7219::new(&mut spi);
591 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
592
593 matrix
594 .draw_char_with_font(device_index, ch, &test_font)
595 .expect("valid character");
596 spi.done();
597 }
598
599 #[test]
600 fn test_draw_char_invalid_index() {
601 let mut spi = SpiMock::new(&[]); let driver = Max7219::new(&mut spi).with_device_count(1).unwrap();
603 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
604
605 let result = matrix.draw_char(1, 'A'); assert_eq!(result, Err(Error::InvalidDeviceIndex));
608 spi.done();
609 }
610
611 #[test]
612 fn test_draw_text_single_device() {
613 let text = "H";
614 let expected_ch = 'H';
615 let expected_bitmap = STANDARD_LED_FONT.get_char(expected_ch);
616 let mut expected_transactions = Vec::new();
620 for (row_index, &data) in expected_bitmap.iter().enumerate() {
621 let digit_register = Register::try_digit(row_index as u8).unwrap();
622 expected_transactions.push(Transaction::transaction_start());
623 expected_transactions.push(Transaction::write_vec(vec![digit_register.addr(), data]));
625 expected_transactions.push(Transaction::transaction_end());
626 }
627
628 let mut spi = SpiMock::new(&expected_transactions);
629 let driver = Max7219::new(&mut spi);
630 let mut matrix = SingleMatrix::from_driver(driver).expect("valid initialization");
631
632 let result = matrix.draw_text(text);
633 assert!(result.is_ok());
634 spi.done();
635 }
636
637 #[test]
638 fn test_draw_text_multi_device() {
639 let device_count = 4;
640
641 let text = "Hi";
642 let expected_bitmap_h = STANDARD_LED_FONT.get_char('H');
643 let expected_bitmap_i = STANDARD_LED_FONT.get_char('i');
644 let mut expected_transactions = Vec::new();
649 for row_index in 0..8 {
651 let digit_register = Register::try_digit(row_index).unwrap();
652 expected_transactions.push(Transaction::transaction_start());
653 let data_for_device_1 = expected_bitmap_h[row_index as usize];
657 let data_for_device_0 = expected_bitmap_i[row_index as usize];
658 expected_transactions.push(Transaction::write_vec(vec![
659 digit_register.addr(),
660 data_for_device_1, digit_register.addr(),
662 data_for_device_0, digit_register.addr(),
664 0x00,
665 digit_register.addr(),
666 0x00,
667 ]));
668 expected_transactions.push(Transaction::transaction_end());
669 }
670
671 let mut spi = SpiMock::new(&expected_transactions);
672 let driver = Max7219::new(&mut spi)
673 .with_device_count(device_count)
674 .unwrap();
675 let mut matrix = Matrix4::from_driver(driver).unwrap();
676
677 let result = matrix.draw_text(text);
678 assert!(result.is_ok());
679 spi.done();
680 }
681
682 #[test]
683 fn test_draw_text_with_font() {
684 let text = "!";
685 pub const TEST_FONT: &[([u8; 8], char)] = &[([0b10101010; 8], '!')];
687 let test_font = LedFont::new(TEST_FONT);
688 let expected_bitmap = [0b10101010u8; 8];
689 let device_count = 2;
690
691 let mut expected_transactions = Vec::new();
692
693 for (row_index, &data) in expected_bitmap.iter().enumerate() {
695 let digit_register = Register::try_digit(row_index as u8).unwrap();
696 expected_transactions.push(Transaction::transaction_start());
697
698 expected_transactions.push(Transaction::write_vec(vec![
700 digit_register.addr(),
701 data, digit_register.addr(),
703 0x00, ]));
705
706 expected_transactions.push(Transaction::transaction_end());
707 }
708
709 let mut spi = SpiMock::new(&expected_transactions);
710 let driver = Max7219::new(&mut spi)
711 .with_device_count(device_count)
712 .unwrap();
713 let mut matrix: LedMatrix<_, 128, 2> = LedMatrix::from_driver(driver).unwrap();
714
715 let result = matrix.draw_text_with_font(text, &test_font);
716 assert!(result.is_ok());
717
718 spi.done();
719 }
720
721 #[test]
722 fn test_clear_buffer() {
723 let mut spi = SpiMock::new(&[]); let driver = Max7219::new(&mut spi);
725 let mut matrix = SingleMatrix::from_driver(driver).unwrap();
726
727 matrix.framebuffer[0] = 1;
729 matrix.framebuffer[10] = 1;
730 matrix.framebuffer[63] = 1;
731 assert_ne!(matrix.framebuffer, [0u8; 64]);
732
733 matrix.clear_buffer();
734
735 assert_eq!(matrix.framebuffer, [0u8; 64]);
736 spi.done();
737 }
738
739 #[test]
740 fn test_clear_screen() {
741 let mut expected_transactions = Vec::new();
743 for row in 0..8 {
744 let digit_register = Register::try_digit(row).unwrap();
745 expected_transactions.push(Transaction::transaction_start());
746 expected_transactions.push(Transaction::write_vec(vec![digit_register.addr(), 0x00]));
747 expected_transactions.push(Transaction::transaction_end());
748 }
749
750 let mut spi = SpiMock::new(&expected_transactions);
751 let driver = Max7219::new(&mut spi);
752 let mut matrix = SingleMatrix::from_driver(driver).unwrap();
753
754 matrix.framebuffer[5] = 1;
756 matrix.framebuffer[15] = 1;
757
758 assert_ne!(matrix.framebuffer, [0u8; 64]);
759
760 let result = matrix.clear_screen();
761 assert!(result.is_ok());
762 assert_eq!(matrix.framebuffer, [0u8; 64]);
763 spi.done();
764 }
765
766 #[test]
767 fn test_flush_single_device() {
768 let mut expected_transactions = Vec::new();
773 for (row, digit_register) in Register::digits().enumerate() {
774 let expected_byte = if row == 0 || row == 7 {
777 0b10101010
778 } else {
779 0b00000000
780 };
781
782 expected_transactions.push(Transaction::transaction_start());
784 expected_transactions.push(Transaction::write_vec(vec![
785 digit_register.addr(),
786 expected_byte,
787 ]));
788 expected_transactions.push(Transaction::transaction_end());
789 }
790
791 let mut spi = SpiMock::new(&expected_transactions);
793 let driver = Max7219::new(&mut spi);
794 let mut matrix = SingleMatrix::from_driver(driver).unwrap();
795
796 matrix.framebuffer[0] = 1; matrix.framebuffer[2] = 1; matrix.framebuffer[4] = 1; matrix.framebuffer[6] = 1; matrix.framebuffer[56] = 1; matrix.framebuffer[58] = 1; matrix.framebuffer[60] = 1; matrix.framebuffer[62] = 1; let result = matrix.flush();
824 assert!(result.is_ok());
825
826 spi.done();
827 }
828
829 #[test]
830 fn test_driver_mut_access() {
831 let expected_transactions = [
832 Transaction::transaction_start(),
833 Transaction::write_vec(vec![Register::Shutdown.addr(), 0x01]),
834 Transaction::transaction_end(),
835 ];
836 let mut spi = SpiMock::new(&expected_transactions);
837 let original_driver = Max7219::new(&mut spi);
838 let mut matrix = SingleMatrix::from_driver(original_driver).unwrap();
839
840 let driver = matrix.driver();
841
842 driver.power_on().expect("Power on should succeed");
843 spi.done();
844 }
845}
846
847#[cfg(all(test, feature = "graphics"))]
848mod graphis_tests {
849 use super::*;
850 use embedded_graphics_core::geometry::Point;
851
852 use embedded_hal_mock::eh1::spi::Mock as SpiMock;
853
854 #[test]
855 fn test_draw_target_draw_iter() {
856 let mut spi = SpiMock::new(&[]);
857 let driver = Max7219::new(&mut spi);
858 let mut matrix = SingleMatrix::from_driver(driver).unwrap(); let pixels = [
862 Pixel(Point::new(0, 0), BinaryColor::On), Pixel(Point::new(1, 0), BinaryColor::Off), Pixel(Point::new(7, 7), BinaryColor::On), Pixel(Point::new(8, 0), BinaryColor::On),
867 Pixel(Point::new(0, 8), BinaryColor::On),
868 Pixel(Point::new(20, 20), BinaryColor::On),
869 ];
870
871 matrix.draw_iter(pixels.iter().cloned()).unwrap();
873
874 let mut expected = [0u8; 64];
875 expected[0] = 1; expected[1] = 0; expected[63] = 1; assert_eq!(&matrix.framebuffer, &expected);
880
881 spi.done();
882 }
883
884 #[test]
885 fn test_draw_target_draw_iter_multi_device() {
886 let mut spi = SpiMock::new(&[]);
887 let driver = Max7219::new(&mut spi).with_device_count(2).unwrap(); let mut matrix: LedMatrix<_, 128, 2> = LedMatrix::from_driver(driver).unwrap(); let pixels = [
892 Pixel(Point::new(0, 0), BinaryColor::On),
897 Pixel(Point::new(7, 0), BinaryColor::On),
902 Pixel(Point::new(8, 1), BinaryColor::On),
907 Pixel(Point::new(15, 7), BinaryColor::On),
912 ];
913
914 matrix.draw_iter(pixels.iter().cloned()).unwrap();
916
917 let mut expected = [0u8; 128];
919 expected[0] = 1; expected[7] = 1; expected[72] = 1; expected[127] = 1; assert_eq!(&matrix.framebuffer, &expected);
925
926 spi.done();
927 }
928}