Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Registers Module

In this chapter, we will work on the registers module(registers.rs). We could use the register values directly, but using enums is more idiomatic and provides better type safety. We will define two enums here: Register and DecodeMode.

The Register enum holds the addresses of each digit register and control register values, which we saw in the data transfer section. The DecodeMode enum is for configuring automatic digit decoding on seven-segment displays. For LED matrix displays, it should be set to NoDecode.

Add the neceessary imports for the module:

#![allow(unused)]
fn main() {
use crate::{Result, error::Error};
}

The Register Enum

In this enum, we declare all the MAX7219 registers, with each name matching its register address. To make working with these registers easier, we also implement some basic traits that let us copy and compare the register values.

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Register {
    /// No-op register
    NoOp = 0x00,
    /// Digit 0 register
    Digit0 = 0x01,
    /// Digit 1 register
    Digit1 = 0x02,
    /// Digit 2 register
    Digit2 = 0x03,
    /// Digit 3 register
    Digit3 = 0x04,
    /// Digit 4 register
    Digit4 = 0x05,
    /// Digit 5 register
    Digit5 = 0x06,
    /// Digit 6 register
    Digit6 = 0x07,
    /// Digit 7 register
    Digit7 = 0x08,
    /// Decode mode register
    DecodeMode = 0x09,
    /// Intensity register
    Intensity = 0x0A,
    /// Scan limit register
    ScanLimit = 0x0B,
    /// Shutdown register
    Shutdown = 0x0C,
    /// Display test register
    DisplayTest = 0x0F,
}
}

Next, we will add some functions that will be helpful later.

First, we will add the addr method that converts a register into its raw u8 value. This is useful when we need to send the register address to the MAX7219 chip.

Next, we will add try_digit function that takes a number and tries to convert it into the matching digit register (Digit0 to Digit7). If the number is out of range, it returns an invalid digit error. If you are wondering why I didn't implement the TryFrom trait, it is because I want to accept only digit registers. Later in the code, when the user sends a digit as input, we need a function to check if it is a valid digit. By the way, this is an internal function we will use only inside the crate to validate user input. So, we mark it with pub(crate).

We also declare another helper function that gives us an iterator. This returns all the digit registers. This is useful when you want to loop through all digits - for example, to update every row or column on the display.

#![allow(unused)]

fn main() {
impl Register {
    /// Convert register to u8 value
    pub const fn addr(self) -> u8 {
        self as u8
    }

    /// Try to convert a digit index (0-7) into a corresponding `Register::DigitN`.
    pub(crate) fn try_digit(digit: u8) -> Result<Self> {
        match digit {
            0 => Ok(Register::Digit0),
            1 => Ok(Register::Digit1),
            2 => Ok(Register::Digit2),
            3 => Ok(Register::Digit3),
            4 => Ok(Register::Digit4),
            5 => Ok(Register::Digit5),
            6 => Ok(Register::Digit6),
            7 => Ok(Register::Digit7),
            _ => Err(Error::InvalidDigit),
        }
    }

    /// Returns an iterator over all digit registers (Digit0 to Digit7).
    ///
    /// Useful for iterating through display rows or columns when writing
    /// to all digits of a MAX7219 device in order.
    pub fn digits() -> impl Iterator<Item = Register> {
        [
            Register::Digit0,
            Register::Digit1,
            Register::Digit2,
            Register::Digit3,
            Register::Digit4,
            Register::Digit5,
            Register::Digit6,
            Register::Digit7,
        ]
        .into_iter()
    }
}
}