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

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

MAX7219 8×8 LED Dot Matrix module

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

The module consists of a 8×8 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
RegisterAddressNote
No-Op0x00
Digit 00x01Control LEDs in Row 0
Digit 10x02Control LEDs in Row 1
Digit 20x03Control LEDs in Row 2
Digit 30x04Control LEDs in Row 3
Digit 40x05Control LEDs in Row 4
Digit 50x06Control LEDs in Row 5
Digit 60x07Control LEDs in Row 6
Digit 70x08Control LEDs in Row 7
Decode mode0x09
Intensity0x0A
Scan Limit0x0B
Shutdown0x0C
Display test0x0F

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

QTYComponent
1Arduino Mega 2560
1MAX7219 8×8 LED Matrix Module
1Jumper Wires

Connect the MAX7219 module with Arduino as follow

Arduino PinsMAX7219 pins
5VVDD
GNDGND
10CS/LOAD
11CLK
12DIN/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.

Leave a Comment