Learn C++ with Arduino: Interfacing 8x8 LED Dot Matrix MAX7219

In this tutorial, you will continue learning C++ by building a C++ class for controlling 8x8 LED Dot Matrix MAX7219 using Arduino.

MAX7219 8x8 LED Dot Matrix module

First, let's have a look at the 8x8 LED dot matrix module which is controlled by MAX7219 chip. The module is shown in the below image

The module consists of a 8x8 LED matrix or 64 individual LEDs. These LEDs are arranged in rows and columns which are illustrated in the below figure.

The MAX7219 has 8 registers, numbered from 0 to 7, which are used to control 8 rows of LEDs. Register 0 (DIGIT_0 register) controls 8 LEDs of row 0, register 1 (DIGIT_1 register) controls 8 LEDs of row number 1, and so on. By writing to these 8-bit registers, you can make individual LED on or off. For example, to turn all LEDs off except one LED at row 1 and colum 1, you need to write to register 1 value 0b00000010 in binary or 0x02 in hexadecimal.

Now you have understand the module, the next step is to write a class to control it.

MAX7219 class

To control MAX7219, you will create a class MAX7219 as follow

class MAX7219 {
  private:
    int CS;
    int CLK;
    int MOSI;
    void transfer(uint8_t * p_data, uint8_t len);
    void write_reg(uint8_t reg, uint8_t value);
  public:
    MAX7219(int CS, int CLK, int MOSI);
    void clear(void);
    void set_led(uint8_t row_index, uint8_t col_index);
    void set_row(uint8_t row_index);
    void set_col(uint8_t col_index);
};

This class has a number of member variables and member methods:

Private member variables and methods

  • CS, CLK, MOSI are private member variables. You use these variables to determine which IO pins of Arduino are used for chip select, clock, and Data In lines.
  • transfer() is a private member method to perform an SPI transfer to MAX7219. This function accepts a pointer to a data buffer and length of the data buffer.
  • write_reg() is a private member method to write to a MAX7219 register. The table below listed all MAX7219 8-bit registers
Register Address Note
No-Op 0x00
Digit 0 0x01 Control LEDs in Row 0
Digit 1 0x02 Control LEDs in Row 1
Digit 2 0x03 Control LEDs in Row 2
Digit 3 0x04 Control LEDs in Row 3
Digit 4 0x05 Control LEDs in Row 4
Digit 5 0x06 Control LEDs in Row 5
Digit 6 0x07 Control LEDs in Row 6
Digit 7 0x08 Control LEDs in Row 7
Decode mode 0x09
Intensity 0x0A
Scan Limit 0x0B
Shutdown 0x0C
Display test 0x0F

Public methods

  • MAX7219() is the constructor which is called whenever an instance of class MAX7219 is created. You will implement initialisation such as configuring IO pins as output ports, set default register values in this constructor method.
  • clear() is the method to turn all LEDs off
  • set_led() is the function to turn on an individual LED at position (row_index, col_index)
  • set_row() is to turn on all LEDs in a row
  • set_col() is to turn on all LEDs in a column

MAX7219 member methods

Constructor

The constructor member method is called whenever an object of class MAX7219 is instantiated. You will do the following initialisation steps inside this function:

  • Assign the IO pins that are connected to MAX7219 to the member variables CS, CLK and MOSI. In this project, you will implement SPI transfer using software and you can use any available IO pins as Chip Select, Clock and Data lines. Note that in the below snippet you use C++ this keyword. this is a special pointer that points to the current object, so you can use this->CS to refer to the member variable CS.
  • Configure CS, CLK and MOSI pins as output port. You use Arduino pinMode() API for this purpose.
  • Initialise those lines with digitalWrite() API
  • Call write_reg() to set up the MAX7219. You will implement write_reg() in a little while.
  • Turn off all LEDs by calling clear() method.

    define DECODE_MODE_REG 0x09

    define INTENSITY_REG 0x0A

    define SCAN_LIMIT_REG 0x0B

    define SHUTDOWN_REG 0x0C

    define DISPLAY_TEST_REG 0x0F

    MAX7219::MAX7219(int CS, int CLK, int MOSI) { this->CS = CS; this->CLK = CLK; this->MOSI = MOSI;

    pinMode(this->CS, OUTPUT); pinMode(this->CLK, OUTPUT); pinMode(this->MOSI, MOSI);

    digitalWrite(this->CS, HIGH); digitalWrite(this->CLK, LOW); digitalWrite(this->MOSI, LOW);

    write_reg(DISPLAY_TEST_REG, 0); write_reg(SCAN_LIMIT_REG, 7); write_reg(DECODE_MODE_REG, 0); write_reg(SHUTDOWN_REG, 1);

    clear(); }

SPI transfer

Next you implement transfer() function to transfer data to MAX7219. You'll need to refer to timing diagram in MAX7219 datasheet in order to understand this piece of code.

  • Before transferring data, you need to pull CS line low.
  • You loop over each data byte in buffer and shift out each bit on MOSI line, starting with most signification bit (MSB), then generate clock signal on CLK line.
  • A mask variable is used to determine the current bit being transferred
  • When all data has been transferred to the MAX7219, you pull the CS line high again.

    void MAX7219::transfer(uint8_t * p_data, uint8_t len) { uint8_t mask;

    digitalWrite(CS, LOW); delayMicroseconds(1);

    for (int i = 0; i < len; i++) { mask = 0x80; do { if (p_data[i] & mask) { digitalWrite(MOSI, HIGH); } else { digitalWrite(MOSI, LOW); }

      delayMicroseconds(1);
      digitalWrite(CLK, HIGH);
      delayMicroseconds(1);
      digitalWrite(CLK, LOW);
    
      mask >>= 1;
    } while (mask != 0);
    

    }

    digitalWrite(CS, HIGH); }

Write MAX7219 register

To write to MAX7219 register, you need two parameters: one parameter is the address of the register to be written to, one parameter is the value to write to the register. You put these parameters in a buffer and send it out by calling transfer() function.

void MAX7219::write_reg(uint8_t reg, uint8_t value) {
  uint8_t tx_data[2] = { reg, value };
  transfer(tx_data, 2);
}

Turn on a specific LED

To turn on a specific LED at row_index, col_index, you need to write to the register address at row_index + 1 and value to be written is determined by 0x01 << col_index

void MAX7219::set_led(uint8_t row_index, uint8_t col_index) {
  write_reg(row_index + 1, 0x01 << col_index);
}

Turn on a row of LEDs

To turn on a row of LEDs, you need to write to the register at address row_index + 1 with value 0xFF.

void MAX7219::set_row(uint8_t row_index) {
  write_reg(row_index + 1, 0xFF);
}

Turn on a column of LEDs

To turn on all LEDs in a column col_index, you need to write to all 8 registers with the same value (1 << col_index). You can do it in a for loop as follow

void MAX7219::set_col(uint8_t col_index) {
  for (int i = 0; i < 8; i++) {
    write_reg(i + 1, 0x01 << col_index);
  }
}

Turn off all LEDs

By writing 0x00 to all 8 registers, you can turn off all 64 LEDs of the LED matrix

void MAX7219::clear(void) {
  for (int i = 0; i < 8; i++) {
    write_reg(i + 1, 0x00);
  }
}

Main program

Now all the heavy work has been done, in the main program, you just need to declare an object of class MAX7219. The following example shows how you can call its member functions to do some interesting tasks.

MAX7219 max7219(10, 11, 12);

void setup() {
  max7219.set_led(7, 7);
  delay(2000);
}

void loop() {
  for (int i = 0; i < 8; i++) {
    max7219.clear();
    max7219.set_row(i);
    delay(1000);
  }

  for (int i = 0; i < 8; i++) {
    max7219.clear();
    max7219.set_col(i);
    delay(1000);
  }
}

Full project code can be seen on Github.

Testing the program

In this step, you'll need a few components. This table describes hardware used in this project

QTY Component
1 Arduino Mega 2560
1 MAX7219 8x8 LED Matrix Module
1 Jumper Wires

Connect the MAX7219 module with Arduino as follow

Arduino Pins MAX7219 pins
5V VDD
GND GND
10 CS/LOAD
11 CLK
12 DIN/MOSI

Open Arduino IDE, set the board, set the port, then Compile and Upload the above program. You will see that each row of the LED matrix is turned on and then each column of the LED matrix is turned on repeated every 1 second.

Wrapping Up

In this article, you have learnt to write a C++ program with Arduino by going through a project with MAX7219. To be able to control MAX7219 module, you need to understand how it works by reading its datasheet, then build a class to control it.