max7219_async/
lib.rs

1//! A platform agnostic driver to interface with the MAX7219 (LED matrix display driver)
2//!
3//! This driver was built using [`embedded-hal-async`] traits.
4//!
5//! [`embedded-hal-async`]: https://docs.rs/embedded-hal-async/~1.0
6
7#![deny(unsafe_code)]
8#![deny(warnings)]
9#![no_std]
10
11use embedded_hal_async::spi::SpiDevice;
12
13/// Digits per display
14pub const NUM_DIGITS: usize = 8;
15
16/// Possible command register values on the display chip.
17#[derive(Clone, Copy)]
18pub enum Register {
19    Noop = 0x00,
20    Digit0 = 0x01,
21    Digit1 = 0x02,
22    Digit2 = 0x03,
23    Digit3 = 0x04,
24    Digit4 = 0x05,
25    Digit5 = 0x06,
26    Digit6 = 0x07,
27    Digit7 = 0x08,
28    DecodeMode = 0x09,
29    Intensity = 0x0A,
30    ScanLimit = 0x0B,
31    Power = 0x0C,
32    DisplayTest = 0x0F,
33}
34
35impl From<Register> for u8 {
36    fn from(command: Register) -> u8 {
37        command as u8
38    }
39}
40
41/// Decode modes for BCD encoded input.
42#[derive(Copy, Clone)]
43pub enum DecodeMode {
44    /// No decode for BCD encoded input.
45    NoDecode = 0x00,
46    CodeBDigit0 = 0x01,
47    CodeBDigits3_0 = 0x0F,
48    CodeBDigits7_0 = 0xFF,
49}
50
51/// A MAX7219 chip.
52///
53/// Currently, this driver does not support daisy-chaining multiple MAX7219 chips.
54pub struct Max7219<SPI> {
55    pub spi: SPI,
56}
57
58impl<SPI> Max7219<SPI>
59where
60    SPI: SpiDevice,
61{
62    /// Write a byte to a register on the display chip.
63    async fn write_reg(&mut self, register: impl Into<u8>, data: u8) -> Result<(), SPI::Error> {
64        self.spi.write(&[register.into(), data]).await
65    }
66
67    /// Power on the display.
68    pub async fn power_on(&mut self) -> Result<(), SPI::Error> {
69        self.write_reg(Register::Power, 0x01).await
70    }
71
72    /// Power off the display.
73    pub async fn power_off(&mut self) -> Result<(), SPI::Error> {
74        self.write_reg(Register::Power, 0x00).await
75    }
76
77    /// Clear the display by setting all digits to empty.
78    pub async fn clear(&mut self) -> Result<(), SPI::Error> {
79        self.write_raw(&[0; NUM_DIGITS]).await
80    }
81
82    /// Set intensity level on the display,from `0x00` (dimmest) to `0x0F` (brightest).
83    pub async fn set_intensity(&mut self, intensity: u8) -> Result<(), SPI::Error> {
84        self.write_reg(Register::Intensity, intensity).await
85    }
86
87    /// Set decode mode to be used on input sent to the display chip.
88    ///
89    /// See [`DecodeMode`] for more information.
90    pub async fn set_decode_mode(&mut self, mode: DecodeMode) -> Result<(), SPI::Error> {
91        self.write_reg(Register::DecodeMode, mode as u8).await
92    }
93
94    /// Write a byte to a digit on the display.
95    ///
96    /// A typical 7-segment display has the following layout:
97    ///
98    /// ```txt
99    ///     A
100    ///    ---
101    /// F |   | B
102    ///   | G |
103    ///    ---
104    /// E |   | C
105    ///   | D |
106    ///    ---  . DP
107    /// ```
108    ///
109    /// | Byte        | 7  | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
110    /// |-------------|----|---|---|---|---|---|---|---|
111    /// | **Segment** | DP | A | B | C | D | E | F | G |
112    ///
113    /// To display the number `5`, for example, the byte `0b0101_1011` would be
114    /// sent to the display.
115    #[inline]
116    pub async fn write_digit_bytes(&mut self, digit: u8, value: u8) -> Result<(), SPI::Error> {
117        self.write_reg(digit + 1, value).await
118    }
119
120    /// Write byte string to the display
121    ///
122    /// # Arguments
123    ///
124    /// * `string` - the byte string to send 8 bytes long. Unknown characters result in question mark.
125    /// * `dots` - u8 bit array specifying where to put dots in the string (1 = dot, 0 = not)
126    pub async fn write_str(
127        &mut self,
128        string: &[u8; NUM_DIGITS],
129        dots: u8,
130    ) -> Result<(), SPI::Error> {
131        for (i, b) in string.iter().enumerate() {
132            let reg = NUM_DIGITS as u8 - i as u8; // reverse order
133            let dot = (dots >> (reg - 1)) & 1 == 1;
134            self.write_reg(reg, ssb_byte(*b, dot)).await?;
135        }
136
137        Ok(())
138    }
139
140    /// Write a right justified integer with sign.
141    pub async fn write_integer(&mut self, value: i32) -> Result<(), SPI::Error> {
142        let mut buf = [0u8; 8];
143        let j = base_10_bytes(value, &mut buf);
144        buf = pad_left(j);
145        self.write_str(&buf, 0b00000000).await
146    }
147
148    /// Write a right justified hex formatted integer with sign.
149    pub async fn write_hex(&mut self, value: u32) -> Result<(), SPI::Error> {
150        let mut buf = [0u8; 8];
151        let j = hex_bytes(value, &mut buf);
152        buf = pad_left(j);
153        self.write_str(&buf, 0b00000000).await
154    }
155
156    /// Write a raw value to the display.
157    pub async fn write_raw(&mut self, raw: &[u8; NUM_DIGITS]) -> Result<(), SPI::Error> {
158        for (n, b) in raw.iter().enumerate() {
159            self.write_digit_bytes(n as u8, *b).await?;
160        }
161        Ok(())
162    }
163
164    /// Enable or disable the display test mode.
165    pub async fn set_test(&mut self, enable: bool) -> Result<(), SPI::Error> {
166        self.write_reg(Register::DisplayTest, if enable { 0x01 } else { 0x00 })
167            .await
168    }
169
170    /// Create a new instance of the MAX7219 driver.
171    ///
172    /// After creating a new instance, you should call the [`Max7219::init`]
173    /// method to initialize the display.
174    pub const fn new(spi: SPI) -> Self {
175        Self { spi }
176    }
177
178    /// Set the number of digits to scan (display). The value should be between 1 and 8.
179    pub async fn set_scan_limit(&mut self, limit: u8) -> Result<(), SPI::Error> {
180        self.write_reg(Register::ScanLimit, limit - 1).await
181    }
182
183    /// Initialize the display with the default settings.
184    pub async fn init(&mut self) -> Result<(), SPI::Error> {
185        self.set_test(false).await?;
186        self.set_scan_limit(NUM_DIGITS as u8).await?;
187        self.set_decode_mode(DecodeMode::NoDecode).await?;
188        self.clear().await?;
189        self.power_off().await?;
190        self.power_on().await?;
191
192        Ok(())
193    }
194}
195
196///
197/// Translate alphanumeric ASCII bytes into segment set bytes
198///
199fn ssb_byte(b: u8, dot: bool) -> u8 {
200    let mut result = match b as char {
201        ' ' => 0b0000_0000, // "blank"
202        '.' => 0b1000_0000,
203        '-' => 0b0000_0001, // -
204        '_' => 0b0000_1000, // _
205        '0' => 0b0111_1110,
206        '1' => 0b0011_0000,
207        '2' => 0b0110_1101,
208        '3' => 0b0111_1001,
209        '4' => 0b0011_0011,
210        '5' => 0b0101_1011,
211        '6' => 0b0101_1111,
212        '7' => 0b0111_0000,
213        '8' => 0b0111_1111,
214        '9' => 0b0111_1011,
215        'a' | 'A' => 0b0111_0111,
216        'b' => 0b0001_1111,
217        'c' | 'C' => 0b0100_1110,
218        'd' => 0b0011_1101,
219        'e' | 'E' => 0b0100_1111,
220        'f' | 'F' => 0b0100_0111,
221        'g' | 'G' => 0b0101_1110,
222        'h' | 'H' => 0b0011_0111,
223        'i' | 'I' => 0b0011_0000,
224        'j' | 'J' => 0b0011_1100,
225        // K undoable
226        'l' | 'L' => 0b0000_1110,
227        // M undoable
228        'n' | 'N' => 0b0001_0101,
229        'o' | 'O' => 0b0111_1110,
230        'p' | 'P' => 0b0110_0111,
231        'q' => 0b0111_0011,
232        'r' | 'R' => 0b0000_0101,
233        's' | 'S' => 0b0101_1011,
234        // T undoable
235        'u' | 'U' => 0b0011_1110,
236        // V undoable
237        // W undoable
238        // X undoable
239        // Y undoable
240        // Z undoable
241        _ => 0b1110_0101, // ?
242    };
243
244    if dot {
245        result |= 0b1000_0000; // turn "." on
246    }
247
248    result
249}
250
251/// Convert the integer into an integer byte Sequence
252fn base_10_bytes(mut n: i32, buf: &mut [u8]) -> &[u8] {
253    let mut sign: bool = false;
254    if n == 0 {
255        return b"0";
256    }
257    //don't overflow the display
258    if !(-9999999..=99999999).contains(&n) {
259        return b"Err";
260    }
261    if n < 0 {
262        n = -n;
263        sign = true;
264    }
265    let mut i = 0;
266    while n > 0 {
267        buf[i] = (n % 10) as u8 + b'0';
268        n /= 10;
269        i += 1;
270    }
271    if sign {
272        buf[i] = b'-';
273        i += 1;
274    }
275    let slice = &mut buf[..i];
276    slice.reverse();
277    &*slice
278}
279
280/// Convert the integer into a hexidecimal byte sequence
281fn hex_bytes(mut n: u32, buf: &mut [u8]) -> &[u8] {
282    if n == 0 {
283        return b"0";
284    }
285    let mut i = 0;
286    while n > 0 {
287        let digit = (n % 16) as u8;
288        buf[i] = match digit {
289            0 => b'0',
290            1 => b'1',
291            2 => b'2',
292            3 => b'3',
293            4 => b'4',
294            5 => b'5',
295            6 => b'6',
296            7 => b'7',
297            8 => b'8',
298            9 => b'9',
299            10 => b'a',
300            11 => b'b',
301            12 => b'c',
302            13 => b'd',
303            14 => b'e',
304            15 => b'f',
305            _ => b'?',
306        };
307        n /= 16;
308        i += 1;
309    }
310    let slice = &mut buf[..i];
311    slice.reverse();
312    &*slice
313}
314
315/// Take a byte slice and pad the left hand side
316fn pad_left(val: &[u8]) -> [u8; 8] {
317    assert!(val.len() <= 8);
318    let size: usize = 8;
319    let pos: usize = val.len();
320    let mut cur: usize = 1;
321    let mut out: [u8; 8] = *b"        ";
322    while cur <= pos {
323        out[size - cur] = val[pos - cur];
324        cur += 1;
325    }
326    out
327}