FriendlyWire.com


beginner-friendly electronics tutorials and projects


Discover the joy of understanding electronics!

MAX7219 LED multiplexing tutorial

December 4, 2021 tutorial

The MAX7219 integrated circuit uses multiplexing to drive up to 64 individual LEDs. And in this tutorial we will learn how the MAX7219 works, what multiplexing is, and how you can use popular MAX7219-based modules to drive 7-segment displays and LED dot matrix displays with a PIC microcontroller.

/*
 * File:   main.c
 * Author: boos
 *
 * Created on 17. September 2021, 03:49
 */

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config CPUDIV = CLKDIV6 // CPU System Clock Selection Bit (CPU system clock divided by 6)
#pragma config USBLSCLK = 48MHz // USB Low SPeed Clock Selection bit (System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8.)
#pragma config PLLMULT = 3x     // PLL Multipler Selection Bit (3x Output Frequency Selected)
#pragma config PLLEN = ENABLED  // PLL Enable Bit (3x or 4x PLL Enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include <xc.h>

// define MAX7219 register addresses (for convenience)
#define MAX7219_MODE_NOP       0b00000000
#define MAX7219_MODE_DECODE    0b00001001
#define MAX7219_MODE_INTENSITY 0b00001010
#define MAX7219_MODE_SCANLIMIT 0b00001011
#define MAX7219_MODE_SHUTDOWN  0b00001100
#define MAX7219_MODE_TEST      0b00001111

// define MAX7219 commands (for convenience)
#define MAX7219_NO_DECODE      0b00000000
#define MAX7219_7SEG_DECODE    0b11111111

// define functions
void MAX7219_send (unsigned char a, unsigned char d);
void MAX7219_update (void);

//default brightness
unsigned char brt = 15;

// abbreviations for convenience
#define SW1  RA5
#define LED  RC2
#define DATA RC5
#define LOAD RC4
#define CLK  RC3


// main function
void main (void) {

    
	// set up port RA5 for the pushbutton
	
	// it is an input
    TRISA5 = 1;
	
	// enable weak pull-up resistors
    WPUA5 = 1;
    nWPUEN = 0;
    
	
	// set up ports RC2, RC3, RC4, and RC5 to control the MAX7219
	
	// they are all outputs
    TRISC5 = 0;
    TRISC4 = 0;
    TRISC3 = 0;
    TRISC2 = 0;
    
	// disable analog features on ports RC2 and RC3
    ANSC2 = 0;
    ANSC3 = 0;
    
	
	// set up the MAX7219 in the 7-segment module
	
    // scan all eight rows
    MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
    MAX7219_update();
    
    // set MAX7219 to no-decoding mode
	// (we are specifying the pattern manually)
    MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
    MAX7219_update();
    
    // set MAX7219 brightness to maximum
    // (any number from 0-15 works)
    MAX7219_send(MAX7219_MODE_INTENSITY, 15);
    MAX7219_update();
    
    // turn ON
    MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
    MAX7219_update();

    // clear all dot matrix displays
    MAX7219_send(1, 0); MAX7219_update();
    MAX7219_send(2, 0); MAX7219_update();
    MAX7219_send(3, 0); MAX7219_update();
    MAX7219_send(4, 0); MAX7219_update();
    MAX7219_send(5, 0); MAX7219_update();
    MAX7219_send(6, 0); MAX7219_update();
    MAX7219_send(7, 0); MAX7219_update();
    MAX7219_send(8, 0); MAX7219_update();
    
	
    // main loop
    while (1) {
    
	
		// increase brightness if the button is pressed
        if (!SW1) {
            LED = 1;
            brt += 1;
            if (brt > 15) {
                brt = 0;
            }
        } else {
            LED = 0;
        }
    
	
        // update the brightness
        MAX7219_send(MAX7219_MODE_INTENSITY, brt);
        MAX7219_update();
    
		
        // send out "tutorial" text
		// ("8" is leftmost display, "1" is display on the right)
		// segments key:  .abcdefg
        MAX7219_send(8, 0b00001111); MAX7219_update();
        MAX7219_send(7, 0b00011100); MAX7219_update();
        MAX7219_send(6, 0b00001111); MAX7219_update();
        MAX7219_send(5, 0b00011101); MAX7219_update();
        MAX7219_send(4, 0b00000101); MAX7219_update();
        MAX7219_send(3, 0b00000100); MAX7219_update();
        MAX7219_send(2, 0b01110111); MAX7219_update();
        MAX7219_send(1, 0b00000110); MAX7219_update();
        
        
    }
    
    return;
    
}

// This function sends out the address byte "a" and the data byte "d" in the MAX7219 format
// (Sequence of bits is a7-a6-a5-a4-a3-a2-a1-a0-d7-d6-d5-d4-d3-d2-d1-d0.)
void MAX7219_send (unsigned char a, unsigned char d) {

	// send out address byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (a >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// send out data byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (d >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// reset the data pin back to zero
	// (so that it is not left ON if the last sent bit was a 1)
    DATA = 0;

}

// This function pulls the LOAD pin high and then back to zero, so that the transmitted data
// appears in the MAX7219's output stage.
void MAX7219_update (void) {
	
    LOAD = 1;
    LOAD = 0;
	
}

What you need

Before we get started, here is what you need for this tutorial:

  • We will use the PIC16F1455 as our microcontroller, but of course you can use any microcontroller you like to drive the MAX7219.
  • We also need a 400-pin breadboard, together with a 100μF and 100nF capacitor, an LED, a 220Ω resistor, and a pushbutton.
  • And you also need a MAX7219-based display. We will focus on two types of modules today, an eight-digit 7-segment module (with one MAX7219 included) as well as a 32×8 dot matrix LED display with 256 LEDs (and four MAX7219 driver chips). Here you can see these modules:

Check out the components box for links where you can buy all of these components!

What is multiplexing?

Okay, but what is multimplexing? Say you want to control 64 LEDs. For direct drive we would need 64 wires. But if we connect them in groups of 8 with their anodes and cathodes shared, we only need 16 wires instead of 64:

Say we want to display a smiley face, like this one:

The price that we have to pay for multiplexing is that we cannot control all LEDs at the same time. But we can instead generate each row of this image by connecting the cathodes of that row to ground, and by sending VDD to the LEDs that should be on, for example like this:

But here is the trick: if we do that fast enough for each row, around a thousand times per second (and that is called the multiplexing frequency), our eyes don't notice the flickering anymore and it looks like all LEDs are ON at the same time. This is called persistence of vision, and it is the reason that multiplexing works:

How does the MAX7219 work?

Okay, but how does the MAX7219 fit in? Basically, it takes care of switching these LEDs on and off for us automatically. It is quite convenient, and all that we need to know is how to tell the MAX7219 what LEDs to turn on and off, and it takes care of the timing automatically for us. So let's take a look at it:

The MAX7219 has 24 pins in total. There are eight outputs A1A8 where you connect the anodes of the eight LED rows, and another eight outputs C1C8 where you connect the eight cathodes. With a resistor from Iset to VDD you can set the LED current, and there is a nice table in the datasheet that shows you how to select a resistor value based on the LED current and its color:

VDD is +5V and connected at pin 19, and there are two ground connections at pins 4 and 9. It is also a good idea to place a 100nF bypass capacitor close to the chip.

The MAX7219 has three inputs. DATA, CLOCK, and LOAD. They are used to tell the MAX7219 what to display, more on that in just a second :) The MAX7219 also has an output called DATA OUT which allows you to connect multiple of these chips in a row: simply connect DATA OUT to DATA IN of the next chip, and so on. In that case the LOAD and CLOCK lines all have to connected in parallel:

Now, the two MAX7219-based display modules that we want to drive are basically a combination of all of these components. The dot matrix display actually has four groups of 64 LEDs, each with their own MAX7219 driver chip, and the overall schematic for such a module looks like this:

Later in the video when we drive such a module with our PIC controller I will use this symbol here for the dot matrix module to save some space:

Now what about the 7-segment module? The main idea is that the eight LEDs in every column that the MAX7219 can drive can be thought of as the eight LEDs inside of a common cathode 7-segment display, and here is the schematic:

And later in the video I will use this symbol here to save some space.

And if you need more display real estate you can of course chain both of these types of modules together, or even combinations of them:

Okay, so how do we tell the MAX7219 what to display? On the inside, the MAX7219 has thirteen registers that can each store eight bits. So, controlling the MAX7219 means that we have to send data to these registers:

Each command is made up of eight bits that carry the address of the register we want to write to, and the eight data bits we want to write into that register. We need to send them out in this sequence here, using the three wires DATA, CLOCK, and LOAD:

The MAX7219 reads the DATA line at each rising edge on the CLOCK input. During all of that the LOAD line needs to be low, and only after all sixteen bits have been sent we can pulse it briefly from low to high and then back to low again. This loads the data we just sent into the specific register of the MAX7219.

There is a nice summary of all important registers in the datasheet. Here are some important ones:

  • To turn the MAX7219 on, you need to send a 1 to register number 12.
  • Each group of eight LEDs has a register of their own, so by sending a byte to these eight registers at address 1 to 8 we can control all 64 LEDs. And we will see how that works in a lot of detail later.
  • The brightness of the display can be regulated in 16 steps, all we need to do is send a number between 0 and 15 to register number 10.
  • And to turn the display off again, we need to send a 0 to register 12.

For more details also keep an eye on the source code further below :)

Schematic

This here is our simple test circuit, and the MAX7219 display module can be plugged in at the jumper JP1 later:

The PIC16F1455 controls the DATA, CLOCK, and LOAD input of the module, and the pushbutton and the LED are only here for testing. The capacitors C1 and C2 are added for stability, and the battery G1 here supplies the circuit with 4.5V. This is just for testing, in a real application it would be a better idea to use a dedicated power supply because the LEDs will drain the batteries quickly.

We can then connect the PICkit3 to flash the PIC16F1455, and also plug in a display module. Here is the complete schematic for a 7-segment display module:

And this is the schematic for a dot matrix module instead:

Building the circuit

Now let's go ahead and build this circuit!

  • Step 1

    Place the 400-pin breadboard in front of you, and make sure row 1 is at the top. Insert the PIC16F1455 in row 4 and connect it to power at pins 1 and 14.

  • Step 2

    Insert the 100uF bulk capacitor in the power rail, and then insert the 100nF bypass capacitor as well.

  • Step 3

    Insert the pushbutton between rows 3 and 5, and connect row 3 to ground.

  • Step 4

    Then, place the test LED in rows 10 and 11, with the cathode connecting to the ground rail via the 220Ω resistor.

  • Step 5

    Connect the DATA, LOAD, and CLOCK lines from pins 5, 6, and 7, down to rows 16, 17, and 18 on the breadboard.

  • Step 6

    Insert two more wires for VDD and ground, and this way we can later plug in any MAX7219-based module we want right here because they all tend to have the same pinout, more on that below.

  • Step 7

    Connect both power rails on either side like this, and finally connect the 4.5V battery pack to the power rail.

  • Step 8

    And last, wire up the PICkit3 to the PIC16F1455. VDD and GND can be connected into the power rail, MCLR goes into pin 4 of the PIC16F1455, PGD into pin 10, and PGC into pin 9.

And now you should be looking at something like this:

Then, plug the PICkit3's USB end into your computer. Start the MPLAB IDE and create a new project for the PIC16F1455. Add an empty main.c source file, and finally open the MPLAB IPE to establish a connection between the PIC16F1455 and the PICkit3.

If that was too fast and you want to see how to do that in more detail, I have a dedicated article and video on how to set up MPLAB and how to flash a .hex-file onto a PIC microcontroller that walks you through the entire process in a lot of detail, so check that out later if you want.

The wires on the breadboard are arranged in such a way that we can directly plug in a MAX7291-based 7-segment display module or a dot matrix module. Here is a closeup of their pinouts:

7-segment module

Connect one of the 7-segment display modules to the circuit, and the way we placed the wires on the breadboard the module should plug right in:

Next, copy & paste the 7-segment source from the appendix #1. Compile the code and flash the .hex-file onto the PIC16F1455. Here is what appears on the 7-segment display:

You can also press the button to cycle through the 16 different LED brightness levels.

Let's have a look at the source code. Its basic structure is very simple because we do not use any special microcontroller features, so it should be relatively easy to move this code from the PIC16F1455 to other PIC microcontrollers.

The main idea is in the MAX7219_send()-function:

// This function sends out the address byte "a" and the data byte "d" in the MAX7219 format
// (Sequence of bits is a7-a6-a5-a4-a3-a2-a1-a0-d7-d6-d5-d4-d3-d2-d1-d0.)
void MAX7219_send (unsigned char a, unsigned char d) {

	// send out address byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (a >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// send out data byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (d >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// reset the data pin back to zero
	// (so that it is not left ON if the last sent bit was a 1)
    DATA = 0;

}

The command MAX7219_send() sends out 8 bits of data to a given address inside the MAX7219, using the protocol we talked about earlier. It goes through the eight data bits, in reverse order, and sends them out on the DATA line, one by one. Then, we do the same for the address data bits. And at the end I set the DATA line back to 0 so that it is not left on when unused.

To set up the 7-segment module to work properly, we have do to the following:

// set up the MAX7219 in the 7-segment module

// scan all eight rows
MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
MAX7219_update();

// set MAX7219 to no-decoding mode
// (we are specifying the pattern manually)
MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
MAX7219_update();

// set MAX7219 brightness to maximum
// (any number from 0-15 works)
MAX7219_send(MAX7219_MODE_INTENSITY, 15);
MAX7219_update();

// turn ON
MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
MAX7219_update();

// clear all dot matrix displays
MAX7219_send(1, 0); MAX7219_update();
MAX7219_send(2, 0); MAX7219_update();
MAX7219_send(3, 0); MAX7219_update();
MAX7219_send(4, 0); MAX7219_update();
MAX7219_send(5, 0); MAX7219_update();
MAX7219_send(6, 0); MAX7219_update();
MAX7219_send(7, 0); MAX7219_update();
MAX7219_send(8, 0); MAX7219_update();

The function MAX7219_update() pulses the LOAD line to update the data, and there is a detailed description of all of these commands in the datasheet.

In the main()-loop we adjust the brightness as follows:

// update the brightness
MAX7219_send(MAX7219_MODE_INTENSITY, brt);
MAX7219_update();

And this is how we send data to the different segments:

// send out "tutorial" text
// ("8" is leftmost display, "1" is display on the right)
// segments key:  .abcdefg
MAX7219_send(8, 0b00001111); MAX7219_update();
MAX7219_send(7, 0b00011100); MAX7219_update();
MAX7219_send(6, 0b00001111); MAX7219_update();
MAX7219_send(5, 0b00011101); MAX7219_update();
MAX7219_send(4, 0b00000101); MAX7219_update();
MAX7219_send(3, 0b00000100); MAX7219_update();
MAX7219_send(2, 0b01110111); MAX7219_update();
MAX7219_send(1, 0b00000110); MAX7219_update();

These address numbers here go from 1-8, where 1 is the display on the very right, and 8 is the leftmost display; the eight data bits here correspond to the seven segments and the decimal point of each display. Here are some examples of different code with the corresponding output:

And last, remember that you can always add additional displays as well. This is how it looks like in the schematic:

Only then, for each new display you have to send out an additional command, before pulsing the LOAD line, because the data has to be passed down to the last display in the line, and remember that you also have to send out multiple commands for each display during the initial configuration of the MAX7219 modules.

Dot matrix module

For this part we first disconnect the 7-segment display and instead connect a dot matrix module that internally has four MAX7219 chips chained together with 256 LEDs in total. These typically come with Dupont-style wire connectors, and this is how it looks like when plugged in:

Copy the dot matrix source code from appendix #2 and paste it inside the MPLAB IDE. Compile the code and flash the .hex-file onto the PIC, and this is what you should see:

And just as before, you can use the button to cycle through the different LED brightness levels.

The source code is largely identical to that of the 7-segment module, with only one difference: our MAX7219-based dot matrix module contains four MAX7219 chips, so we need to send out all commands four times (since they get passed down the chain) before pulsing the clock line. For example, this is how we set the brightness:

// update the brightness
MAX7219_send(MAX7219_MODE_INTENSITY, brt);
MAX7219_send(MAX7219_MODE_INTENSITY, brt);
MAX7219_send(MAX7219_MODE_INTENSITY, brt);
MAX7219_send(MAX7219_MODE_INTENSITY, brt);
MAX7219_update();

When looking at the code that sends out the display pattern you see that it also contains four MAX7219_send()-commands before the MAX7219_update():

What is sent out first travels all the way through the chain of the four displays. So, the last command before the update command sends data to the leftmost display in the chain. This time around, the register addresses 1-8 label the LED rows from bottom to top, and the eight bits correspond to the LEDs in each row: the least significant bit is the leftmost LED in each row, so it is kind of mirrored. And, when you look at the code and then at the LEDs, with a little bit of imagination you can see the LED pattern inside the code. Kinda makes you feel like Neo in the Matrix, doesn't it.

And if you want, you can extend your source code to define some characters and create an LED clock that way, and here is how that could look like in the source code, and this is how that clock would look like in the real world:

Let me know if you want to see this as a future video!

YouTube video

I covered this entire tutorial in a dedicated YouTube video:

For those of you who are interested in the code I made a detailed video on the source code as well, and you can find that “PIC program guide” video here:

Final thoughts

I think these MAX7219-based modules are great when you quickly want to show some numbers with a microcontroller. But as you can imagine that there can be some problems.

Ironically, this is only a problem if you want to film your LEDs. For example, behind me on the workbench in my videos, both my power supply and my multimeter flicker because their multiplexing frequency does not align with the shutter speed of my camera. I don't really like how that looks on camera, so if I want to build something that will be in the background of a video I will always try to avoid multiplexing. But if you are not planning on filming these LEDs, then multiplexing is a fantastic solution.

So I hope you learned how easy it is to use these MAX7219-based LED modules, and if you end up using one of these in your projects please make sure to share it with me on social media, I always love seeing your creations.

Thank you so much for watching, let me know what else you want to learn, and I will see you next time! Have a great day!

Appendix 1: 7-segment code

Here you can find the full 7-segment source code. The code (as well as the .hex file) are also available for download in the resources box.

/*
 * File:   main.c
 * Author: boos
 *
 * Created on 17. September 2021, 03:49
 */

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config CPUDIV = CLKDIV6 // CPU System Clock Selection Bit (CPU system clock divided by 6)
#pragma config USBLSCLK = 48MHz // USB Low SPeed Clock Selection bit (System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8.)
#pragma config PLLMULT = 3x     // PLL Multipler Selection Bit (3x Output Frequency Selected)
#pragma config PLLEN = ENABLED  // PLL Enable Bit (3x or 4x PLL Enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include <xc.h>

// define MAX7219 register addresses (for convenience)
#define MAX7219_MODE_NOP       0b00000000
#define MAX7219_MODE_DECODE    0b00001001
#define MAX7219_MODE_INTENSITY 0b00001010
#define MAX7219_MODE_SCANLIMIT 0b00001011
#define MAX7219_MODE_SHUTDOWN  0b00001100
#define MAX7219_MODE_TEST      0b00001111

// define MAX7219 commands (for convenience)
#define MAX7219_NO_DECODE      0b00000000
#define MAX7219_7SEG_DECODE    0b11111111

// define functions
void MAX7219_send (unsigned char a, unsigned char d);
void MAX7219_update (void);

// default brightness
unsigned char brt = 15;

// abbreviations for convenience
#define SW1  RA5
#define LED  RC2
#define DATA RC5
#define LOAD RC4
#define CLK  RC3


// main function
void main (void) {

    
	// set up port RA5 for the pushbutton
	
	// it is an input
    TRISA5 = 1;
	
	// enable weak pull-up resistors
    WPUA5 = 1;
    nWPUEN = 0;
    
	
	// set up ports RC2, RC3, RC4, and RC5 to control the MAX7219
	
	// they are all outputs
    TRISC5 = 0;
    TRISC4 = 0;
    TRISC3 = 0;
    TRISC2 = 0;
    
	// disable analog features on ports RC2 and RC3
    ANSC2 = 0;
    ANSC3 = 0;
    
	
	// set up the MAX7219 in the 7-segment module
	
    // scan all eight rows
    MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
    MAX7219_update();
    
    // set MAX7219 to no-decoding mode
	// (we are specifying the pattern manually)
    MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
    MAX7219_update();
    
    // set MAX7219 brightness to maximum
    // (any number from 0-15 works)
    MAX7219_send(MAX7219_MODE_INTENSITY, 15);
    MAX7219_update();
    
    // turn ON
    MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
    MAX7219_update();

    // clear all dot matrix displays
    MAX7219_send(1, 0); MAX7219_update();
    MAX7219_send(2, 0); MAX7219_update();
    MAX7219_send(3, 0); MAX7219_update();
    MAX7219_send(4, 0); MAX7219_update();
    MAX7219_send(5, 0); MAX7219_update();
    MAX7219_send(6, 0); MAX7219_update();
    MAX7219_send(7, 0); MAX7219_update();
    MAX7219_send(8, 0); MAX7219_update();
    
	
    // main loop
    while (1) {
    
	
		// increase brightness if the button is pressed
        if (!SW1) {
            LED = 1;
            brt += 1;
            if (brt > 15) {
                brt = 0;
            }
        } else {
            LED = 0;
        }
    
	
        // update the brightness
        MAX7219_send(MAX7219_MODE_INTENSITY, brt);
        MAX7219_update();
    
		
        // send out "tutorial" text
		// ("8" is leftmost display, "1" is display on the right)
		// segments key:  .abcdefg
        MAX7219_send(8, 0b00001111); MAX7219_update();
        MAX7219_send(7, 0b00011100); MAX7219_update();
        MAX7219_send(6, 0b00001111); MAX7219_update();
        MAX7219_send(5, 0b00011101); MAX7219_update();
        MAX7219_send(4, 0b00000101); MAX7219_update();
        MAX7219_send(3, 0b00000100); MAX7219_update();
        MAX7219_send(2, 0b01110111); MAX7219_update();
        MAX7219_send(1, 0b00000110); MAX7219_update();
        
        
    }
    
    return;
    
}

// This function sends out the address byte "a" and the data byte "d" in the MAX7219 format
// (Sequence of bits is a7-a6-a5-a4-a3-a2-a1-a0-d7-d6-d5-d4-d3-d2-d1-d0.)
void MAX7219_send (unsigned char a, unsigned char d) {

	// send out address byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (a >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// send out data byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (d >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// reset the data pin back to zero
	// (so that it is not left ON if the last sent bit was a 1)
    DATA = 0;

}

// This function pulls the LOAD pin high and then back to zero, so that the transmitted data
// appears in the MAX7219's output stage.
void MAX7219_update (void) {
	
    LOAD = 1;
    LOAD = 0;
	
}

Appendix 2: dot matrix code

Here you can find the full dot matrix source code. The code (as well as the .hex file) are also available for download in the resources box.

/*
 * File:   main.c
 * Author: boos
 *
 * Created on 17. September 2021, 03:49
 */

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection Bits (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover Mode (Internal/External Switchover Mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config CPUDIV = CLKDIV6 // CPU System Clock Selection Bit (CPU system clock divided by 6)
#pragma config USBLSCLK = 48MHz // USB Low SPeed Clock Selection bit (System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8.)
#pragma config PLLMULT = 3x     // PLL Multipler Selection Bit (3x Output Frequency Selected)
#pragma config PLLEN = ENABLED  // PLL Enable Bit (3x or 4x PLL Enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include <xc.h>

// define MAX7219 register addresses (for convenience)
#define MAX7219_MODE_NOP       0b00000000
#define MAX7219_MODE_DECODE    0b00001001
#define MAX7219_MODE_INTENSITY 0b00001010
#define MAX7219_MODE_SCANLIMIT 0b00001011
#define MAX7219_MODE_SHUTDOWN  0b00001100
#define MAX7219_MODE_TEST      0b00001111

// define MAX7219 commands (for convenience)
#define MAX7219_NO_DECODE      0b00000000
#define MAX7219_7SEG_DECODE    0b11111111

// define functions
void MAX7219_send (unsigned char a, unsigned char d);
void MAX7219_update (void);

// default brightness
unsigned char brt = 15;

// abbreviations for convenience
#define SW1  RA5
#define LED  RC2
#define DATA RC5
#define LOAD RC4
#define CLK  RC3


// main function
void main (void) {

    
	// set up port RA5 for the pushbutton
	
	// it is an input
    TRISA5 = 1;
	
	// enable weak pull-up resistors
    WPUA5 = 1;
    nWPUEN = 0;
    
	
	// set up ports RC2, RC3, RC4, and RC5 to control the MAX7219
	
	// they are all outputs
    TRISC5 = 0;
    TRISC4 = 0;
    TRISC3 = 0;
    TRISC2 = 0;
    
	// disable analog features on ports RC2 and RC3
    ANSC2 = 0;
    ANSC3 = 0;
    
	
	// set up the four MAX7219's of the 8x32 dot matrix module
	
    // scan all eight rows
    MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
	MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
	MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
	MAX7219_send(MAX7219_MODE_SCANLIMIT, 7);
    MAX7219_update();
    
    // set MAX7219 to no-decoding mode
	// (we are specifying the pattern manually)
    MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
	MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
	MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
	MAX7219_send(MAX7219_MODE_DECODE, MAX7219_NO_DECODE);
    MAX7219_update();
    
    // set MAX7219 brightness to maximum
    // (any number from 0-15 works)
    MAX7219_send(MAX7219_MODE_INTENSITY, 15);
	MAX7219_send(MAX7219_MODE_INTENSITY, 15);
	MAX7219_send(MAX7219_MODE_INTENSITY, 15);
	MAX7219_send(MAX7219_MODE_INTENSITY, 15);
    MAX7219_update();
    
    // turn ON
    MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
	MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
	MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
	MAX7219_send(MAX7219_MODE_SHUTDOWN, 1);
    MAX7219_update();

    // clear all dot matrix displays
    MAX7219_send(1, 0); MAX7219_send(1, 0); MAX7219_send(1, 0); MAX7219_send(1, 0); MAX7219_update();
    MAX7219_send(2, 0); MAX7219_send(2, 0); MAX7219_send(2, 0); MAX7219_send(2, 0); MAX7219_update();
    MAX7219_send(3, 0); MAX7219_send(3, 0); MAX7219_send(3, 0); MAX7219_send(3, 0); MAX7219_update();
    MAX7219_send(4, 0); MAX7219_send(4, 0); MAX7219_send(4, 0); MAX7219_send(4, 0); MAX7219_update();
    MAX7219_send(5, 0); MAX7219_send(5, 0); MAX7219_send(5, 0); MAX7219_send(5, 0); MAX7219_update();
    MAX7219_send(6, 0); MAX7219_send(6, 0); MAX7219_send(6, 0); MAX7219_send(6, 0); MAX7219_update();
    MAX7219_send(7, 0); MAX7219_send(7, 0); MAX7219_send(7, 0); MAX7219_send(7, 0); MAX7219_update();
    MAX7219_send(8, 0); MAX7219_send(8, 0); MAX7219_send(8, 0); MAX7219_send(8, 0); MAX7219_update();
    
	
    // main loop
    while (1) {
    
	
		// increase brightness if the button is pressed
        if (!SW1) {
            LED = 1;
            brt += 1;
            if (brt > 15) {
                brt = 0;
            }
        } else {
            LED = 0;
        }
    
	
        // update the brightness
        MAX7219_send(MAX7219_MODE_INTENSITY, brt);
		MAX7219_send(MAX7219_MODE_INTENSITY, brt);
		MAX7219_send(MAX7219_MODE_INTENSITY, brt);
		MAX7219_send(MAX7219_MODE_INTENSITY, brt);
        MAX7219_update();
    
		
        // send out sample data
        MAX7219_send(8, 0b00001111); MAX7219_send(8, 0b00000111); MAX7219_send(8, 0b00000011); MAX7219_send(8, 0b11111111); MAX7219_update();
        MAX7219_send(7, 0b00001111); MAX7219_send(7, 0b00000111); MAX7219_send(7, 0b00000011); MAX7219_send(7, 0b01111111); MAX7219_update();
		MAX7219_send(6, 0b00001111); MAX7219_send(6, 0b00000111); MAX7219_send(6, 0b00000011); MAX7219_send(6, 0b00111111); MAX7219_update();
		MAX7219_send(5, 0b00001111); MAX7219_send(5, 0b00000111); MAX7219_send(5, 0b00000011); MAX7219_send(5, 0b00011111); MAX7219_update();
		MAX7219_send(4, 0b00001111); MAX7219_send(4, 0b00000111); MAX7219_send(4, 0b00000011); MAX7219_send(4, 0b00001111); MAX7219_update();
		MAX7219_send(3, 0b00001111); MAX7219_send(3, 0b00000111); MAX7219_send(3, 0b00000011); MAX7219_send(3, 0b00000111); MAX7219_update();
		MAX7219_send(2, 0b00001111); MAX7219_send(2, 0b00000111); MAX7219_send(2, 0b00000011); MAX7219_send(2, 0b00000011); MAX7219_update();
        MAX7219_send(1, 0b00001111); MAX7219_send(1, 0b00000111); MAX7219_send(1, 0b00000011); MAX7219_send(1, 0b00000001); MAX7219_update();
        
        
    }
    
    return;
    
}

// This function sends out the address byte "a" and the data byte "d" in the MAX7219 format
// (Sequence of bits is a7-a6-a5-a4-a3-a2-a1-a0-d7-d6-d5-d4-d3-d2-d1-d0.)
void MAX7219_send (unsigned char a, unsigned char d) {

	// send out address byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (a >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// send out data byte, start with most significant bit and work backwards
    for (int i=7; i>=0; i--) {
        DATA = (d >> i) & 1;
        CLK = 1;
        CLK = 0;
    }
	
	// reset the data pin back to zero
	// (so that it is not left ON if the last sent bit was a 1)
    DATA = 0;

}

// This function pulls the LOAD pin high and then back to zero, so that the transmitted data
// appears in the MAX7219's output stage.
void MAX7219_update (void) {
	
    LOAD = 1;
    LOAD = 0;
	
}

About FriendlyWire

Beginner-friendly electronics tutorials and projects. Discover the joy of electronics! Keep reading.

Components Needed

1×400-pin breadboard (link)
1×3×AAA 4.5V battery compartment (link)
3×AAA 1.5V battery
1×PIC16F1455 microcontroller (link)
1×100μF capacitor (link, kit)
1×100nF ceramic capacitor (link, kit)
1×5mm LED (kit)
1×220Ω resistor (link, kit)
1×pushbutton (kit)
1×MAX7219 7-segment module (link)
1×MAX7219 dot matrix module (link)

You also need AWG 24/0.6mm single-stranded wire (link) and the PICkit3 (link). Click on the items above to learn more.

Tools Needed

1×PICkit3 programmer
1×side cutter
1×pliers

Let's build a community

How did you get interested in electronics? What do you want to learn? Connect and share your story!

Tag Cloud

  • MAX7219
  • multiplexing
  • 7-segment display
  • dot matrix display
  • serial protocol
  • PIC16F1455
  • MPLAB X IDE
  • MPLAB X IPE
  • XC8 compiler
  • beginner-friendly
  • schematic
  • tutorial