16x8 LED Matrix (MAX7219)

Hi everyone,

I have created a 16x8 led matrix and I’m driving it with two MAX7219 drivers. I have it fully working thanks to Tomas123’s code example:


I had to modify it to accommodate my extra driver and the text scrolls slowly now. I’ve checked there are no delays involved. I think its a matter for code optimization.

I’m not sure but maybe i should be changing to lc.setRow instead. I’m just unsure how to implement it in my code when the font is all in columns (I had to create all of the lowercase by hand - a lot of time).

If anyone can see any obviously unnecessary or inefficient lines I’d appreciate it.



//We always have to include the library
#include "LedControl.h"
#include "font.h"
#include <avr/pgmspace.h>

Now we need a LedControl to work with.
pin 12 is connected to the DataIn
pin 11 is connected to the CLK
pin 10 is connected to LOAD
We have 2 MAX72XX.

char message_00[] PROGMEM = ("This is a test "); // Message to be displayed

LedControl lc = LedControl(2,4,3,2); // Initialise LEDControl Library

PROGMEM const char *messages[] = {message_00};

uint8_t screen_mem[16];				// screen memory - number of columns on screen
uint8_t active_row=1;				// active row
uint8_t message_ptr = 0;			// points to the active char in the message
uint8_t active_char = 0;			// stores the active char
uint8_t message_length = 0;			// stores the length of the active message
uint8_t char_ptr = 0;				// points to the active col in the char
uint8_t char_length = 0;			// stores the length of the active char

void setup() {
	int devices=lc.getDeviceCount();
	//we have to init all devices in a loop
	for(int address=0;address<devices;address++) {
		/*The MAX72XX is in power-saving mode on startup*/
		/* Set the brightness to a medium values */
		/* and clear the display */

* copy screen_mem[] to LED Matrix 8x8
void UpdateMatrix() {
	/* here is the data for the characters */
	int i;
	for (i=0; i<8; i++){

* show_char
* Displays the actual message.
* Scrolls the screen to the left and draws new pixels on the right.
* char stop bit is bit.7
void show_char(const prog_uint8_t string[]) {
	uint8_t i;
	uint8_t b;

	// shift the screen to the left
	for (i = 0; i < 15; i++) {
		screen_mem[i] = screen_mem[i+1];
	// advance a char if needed
	if (char_length == 0x80) {
		//reset stop bit
		char_length =0;	

		//read next char from progmem
		//this is an alternativ PROGMEM access:
		//active_char =  pgm_read_byte_near(string + message_ptr);

		//string stop byte 0x00
		if (active_char == 0) {
			message_ptr = 0;
			char_length = 0x80; // immediately read next char

		active_char -= CHAR_OFFSET;
		char_ptr = 0;

		// this makes the space between two chars

	// read pixels for current column of char
	b = pgm_read_byte(&font[active_char * 5 + char_ptr]);
	//char_length= (b & 0x01);
	//b = (b >> 1);
	char_length= (b & 0x80);
	b = (b & 0x7F);
	// write pixels into screen memory
	screen_mem[15] =b;

void loop() {


#include <avr/io.h>
#include <avr/pgmspace.h>

#ifndef FONT_H_
#define FONT_H_

#define CHAR_OFFSET 0x20

const uint8_t font[] PROGMEM = {
  // 5 chars bitmap,stop bit is bit.7
0x00,0x00,0x80,0x80,0x80,	// 0x20,32,space
0x00,0x30,0x7D,0x30,0x80,	// 0x21,33,!
0x70,0x60,0x00,0x70,0xE0,	// 0x22,34,"
0x12,0x3F,0x12,0x3F,0x92,	// 0x23,35,#
0x12,0x6A,0x2B,0xA4,0x80,	// 0x24,36,$
0x63,0x64,0x08,0x13,0xE3,	// 0x25,37,%
0x36,0x49,0x35,0x02,0x85,	// 0x26,38,&
0x00,0x70,0x60,0x80,0x80,	// 0x27,39,'
0x00,0x3E,0x41,0x80,0x80,	// 0x28,40,(
0x00,0x41,0x3E,0x80,0x80,	// 0x29,41,)
0x08,0x3E,0x1C,0x3E,0x88,	// 0x2A,42,*
0x08,0x08,0x3E,0x08,0x88,	// 0x2B,43,+
0x00,0x02,0x03,0x80,0x80,	// 0x2C,44,,
0x08,0x08,0x08,0x08,0x88,	// 0x2D,45,-
0x00,0x03,0x03,0x80,0x80,	// 0x2E,46,.
0x02,0x04,0x08,0x10,0xA0,	// 0x2F,47,/
0x3E,0x45,0x49,0x51,0xBE,	// 0x30,48,0
0x00,0x21,0x7F,0x81,0x80,	// 0x31,49,1
0x23,0x45,0x49,0x49,0xB1,	// 0x32,50,2
0x22,0x49,0x49,0x49,0xB6,	// 0x33,51,3
0xC,0x14,0x24,0x7F,0x84,	// 0x34,52,4
0x7A,0x49,0x49,0x49,0xC6,	// 0x35,53,5
0x1E,0x29,0x49,0x49,0x86,	// 0x36,54,6
0x40,0x47,0x48,0x50,0xE0,	// 0x37,55,7
0x36,0x49,0x49,0x49,0xB6,	// 0x38,56,8
0x30,0x49,0x49,0x4A,0xBC,	// 0x39,57,9
0x00,0x1B,0x1B,0x80,0x80,	// 0x3A,58,:
0x00,0x1B,0x1B,0x80,0x80,	// 0x3B,59,;
0x08,0x14,0x22,0xC1,0x80,	// 0x3C,60,<
0x12,0x12,0x12,0x12,0x92,	// 0x3D,61,=
0x00,0x41,0x22,0x14,0x88,	// 0x3E,62,>
0x20,0x40,0x4D,0x48,0xB0,	// 0x3F,63,?
0x3E,0x41,0x5D,0x55,0xBC,	// 0x40,64,@
0x3F,0x44,0x44,0x44,0xBF,	// 0x41,65,A
0x7F,0x49,0x49,0x49,0xB6,	// 0x42,66,B
0x3E,0x41,0x41,0x41,0xA2,	// 0x43,67,C
0x7F,0x41,0x41,0x41,0xBE,	// 0x44,68,D
0x7F,0x49,0x49,0x49,0xC1,	// 0x45,69,E
0x7F,0x48,0x48,0x48,0xC0,	// 0x46,70,F
0x3E,0x41,0x49,0x49,0xAF,	// 0x47,71,G
0x7F,0x08,0x08,0x08,0xFF,	// 0x48,72,H
0x41,0x7F,0xC1,0x80,0x80,	// 0x49,73,I
0x06,0x01,0x01,0x01,0xFE,	// 0x4A,74,J
0x7F,0x08,0x14,0x22,0xC1,	// 0x4B,75,K
0x7F,0x01,0x01,0x01,0x81,	// 0x4C,76,L
0x7F,0x20,0x10,0x20,0xFF,	// 0x4D,77,M
0x7F,0x20,0x10,0x08,0xFF,	// 0x4E,78,N
0x3E,0x41,0x41,0x41,0xBE,	// 0x4F,79,O
0x7F,0x48,0x48,0x48,0xB0,	// 0x50,80,P
0x3E,0x41,0x45,0x42,0xBD,	// 0x51,81,Q
0x7F,0x48,0x48,0x4C,0xB3,	// 0x52,82,R
0x32,0x49,0x49,0x49,0xA6,	// 0x53,83,S
0x40,0x40,0x7F,0x40,0xC0,	// 0x54,84,T
0x7E,0x01,0x01,0x01,0xFE,	// 0x55,85,U
0x7C,0x02,0x01,0x02,0xFC,	// 0x56,86,V
0x7E,0x01,0x1E,0x01,0xFE,	// 0x57,87,W
0x63,0x14,0x08,0x14,0xE3,	// 0x58,88,X
0x70,0x08,0x07,0x08,0xF0,	// 0x59,89,Y
0x47,0x49,0x51,0xE1,0x80,	// 0x5A,90,Z
0x7F,0x41,0xC1,0x80,0x80,	// [
0x20,0x10,0x08,0x04,0x82,	// '\'
0x41,0x41,0x7F,0x80,0x80,	// ]
0x10,0x20,0x40,0x20,0x90,	// ^
0x01,0x01,0x01,0x01,0x81,	// _
0x40,0x20,0x10,0x80,0x80,	// `
0x02,0x15,0x15,0x15,0x8F,	// 0x61,97,a
0x7F,0x09,0x11,0x11,0x8E,	// 0x62,98,b
0x0E,0x11,0x11,0x11,0x82,	// 0x63,99,c
0x0E,0x11,0x11,0x09,0xFF,	// 0x64,100,d
0x0E,0x15,0x15,0x15,0x8C,	// 0x65,101,e
0x08,0x3F,0x48,0x40,0xA0,	// 0x66,102,f
0x08,0x15,0x15,0x15,0x9E,	// 0x67,103,g
0x7F,0x08,0x10,0x10,0x8F,	// 0x68,104,h
0x11,0x5F,0x81,0x80,0x80,	// 0x69,105,i
0x02,0x01,0x11,0xDE,0x80,	// 0x6A,106,j
0x7F,0x04,0x0A,0x91,0x80,	// 0x6B,107,k
0x41,0x7F,0x81,0x80,0x80,	// 0x6C,108,l
0x1F,0x10,0x0C,0x10,0x8F,	// 0x6D,109,m
0x1F,0x08,0x10,0x10,0x8F,	// 0x6E,110,n
0x0E,0x11,0x11,0x11,0x8E,	// 0x6F,111,o
0x1F,0x14,0x14,0x14,0x88,	// 0x70,112,p
0x08,0x14,0x14,0x12,0x9F,	// 0x71,113,q
0x1F,0x08,0x10,0x10,0x88,	// 0x72,114,r
0x09,0x15,0x15,0x15,0x82,	// 0x73,115,s
0x10,0x7E,0x11,0x01,0x82,	// 0x74,116,t
0x1E,0x01,0x01,0x02,0x9F,	// 0x75,117,u
0x1C,0x02,0x01,0x02,0x9C,	// 0x76,118,v
0x1E,0x01,0x06,0x01,0x9E,	// 0x77,119,w
0x11,0x0A,0x04,0x0A,0x91,	// 0x78,120,x
0x18,0x05,0x05,0x05,0x9E,	// 0x79,121,y
0x11,0x13,0x15,0x19,0x91,	// 0x7A,122,z


#endif /*FONT_H_*/

Did you ever try running this using SPI as we discussed elsewhere in the forum, vs using software bit-bang?

CrossRoads: Did you ever try running this using SPI as we discussed elsewhere in the forum, vs using software bit-bang?

I haven't as yet, that would mean rewriting the whole thing wouldn't it? Would it still work with the fonts.h? I honestly don't understand the SPI method as I haven't looked into it yet.

I think you may be able to rewrite just the section that sends the data to the MAX7219.
It consists of 8 registers, one usage is to write to each register as if you were writing the data out to 7-segment +decimal point display.
Another method is to write it out with no decode, so you are controlling the bits directly.

To write to a register you would send a couple of SPI commands, which then use the hardware SPI interface to clock the data out at 4 MHz rate.
For example

  digitalWrite(SS,LOW);  // take the SS pin low to select the chip
  SPI.transfer(register_0);  // select the Address,   <<< this is then the much faster hardware 8-bit shiftout
  SPI.transfer(number_to_display);      // select the data            <<< this is then the much faster hardware 8-bit shiftout
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip

so you could put 8 of these in a loop for each device, conceptually:

for (x = 0 to 1){
for (y = 0 to 7){
  digitalWrite(SS[x],LOW);  // take the SS pin low to select the chip
  SPI.transfer(register[y]);  // select the Address,   <<< 
  SPI.transfer(number_to_display[y+number]);      // select the data   <<< number = 0-7, then 8-15
  digitalWrite(SS[x],HIGH);   // take the SS pin high to de-select the chip
next y;}
next x;}

couple of arrays needed:
SS[0,1] = the SS pins used
register[0,1,2,3,4,5,6,7] = addresses of the 7219 data registers
number_to_display[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] = your patterns that get shifted across these columns
what you called screen_mem[0-15]

I’m pretty sure that will update a lot faster than bit-banging the clock & data lines via software writes.

Read the datasheet, see what command is needed to use no-decode mode, and if the registers thus represent columns or rows. Hopefully columns and they line up how you wired your displays.

Thanks for all of that CrossRoads. I will eventually figure that out. Another question out of interest, do you think lc.setRow would be quicker?

In the LEControl.CPP file you will see it is using shiftOut, which is very slow. You should be able to change that to use either direct port manipulation or better yet - SPI.

Here is the place in code that looks like it would need to be updated.

void LedControl::spiTransfer(int addr, volatile byte opcode, volatile byte data) {
    //Create an array with the data to shift out
    int offset=addr*2;
    int maxbytes=maxDevices*2;

    for(int i=0;i<maxbytes;i++)
    //put our device data into the array
    //enable the line 
    //Now shift out the data 
    for(int i=maxbytes;i>0;i--)
    //latch the data onto the display

If the one line in the above code is changed from …

to use either shiftOutFast or spi_transfer in the below post you may find speed improvements. This post was me learning the same thing and providing the results. If you hit issues or have questions attempting to implement this I’ll jump in an help.

To use this code you may need to update your pins to 10 - latch - 13 clock and data 11.

Since there is a tone of code in that post - here are the important parts (may have missed something but this is a good start).

//--- vars 
//--- Pin connected to ST_CP of 74HC595
int latchPin = 10;
//--- Pin connected to SH_CP of 74HC595
int clockPin = 13;
//--- Pin connected to DS of 74HC595
int dataPin = 11;

//--- Used for faster latching
int latchPinPORTB = latchPin - 8;

//----- Setup()

 //set pins to output because they are addressed in the main loop
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);


  //--CMD SerialCommander.initialize(runCommands);

//----- Other routines

void latchOn(){

void latchOff(){

void setupSPI(){
  byte clr;
  SPCR |= ( (1<<SPE) | (1<<MSTR) ); // enable SPI as master
  //SPCR |= ( (1<<SPR1) | (1<<SPR0) ); // set prescaler bits
  SPCR &= ~( (1<<SPR1) | (1<<SPR0) ); // clear prescaler bits
  clr=SPSR; // clear SPI status reg
  clr=SPDR; // clear SPI data reg
  SPSR |= (1<<SPI2X); // set prescaler bits
  //SPSR &= ~(1<<SPI2X); // clear prescaler bits

byte spi_transfer(byte data)
  SPDR = data;                    // Start the transmission
  loop_until_bit_is_set(SPSR, SPIF); 
  return SPDR;                    // return the received byte, we don't need that

If you implement the above code - then use the spi_transfer method in place of the line detailed above … so …


is changed to


That may do it, best of luck - let us know please. Here to help if you hit snags.

Just noticed there is a direct port manipulation library. Using that may work well and would be an easier way to implement shiftOutFast. http://code.google.com/p/digitalwritefast/downloads/list

Not as fast as SPI but may be plenty fast enough. Download and install the above library and include it in your code before the LEDControl library. Then update the LEDControl library as shown below. If you can an error about Untested code ...

void shiftOutFast(int myDataPin, int myClockPin, byte myDataOut) {
  //--- clear data pin

  //Send each bit of the myDataOut byte MSBFIRST
  for (int i=7; i>=0; i--)  {
    //--- Turn data on or off based on value of bit
    if ( bitRead(myDataOut,i) == 1) {
    else {      
    //register shifts bits on upstroke of clock pin  
    //zero the data pin after shift to prevent bleed through
  //stop shifting
  digitalWriteFast(myClockPin, 0);

Then change .. shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); to shiftOutFast(SPI_MOSI,SPI_CLK,spidata[i-1]);

In the library file (after backing it up of course).

"Thanks for all of that CrossRoads. I will eventually figure that out. Another question out of interest, do you think lc.setRow would be quicker?"

No, I think changing to using the actual built in SPI hardware will be quicker. Anything that uses software to simulate the hardware will be slower.

If you look at what happens in the library as marklar wrote, you will see a lot more happens.

Just for giggles I attempted to create a quick and dirty combination of the two libraries. Here is a link to the two library files combined and updated to use shiftOutFast.


Note: I do not have the hardware you have so this is untested but it does compile. Normally you should NOT combine both libraries like this, it was combined due to wanting a simple proof of concept that using shift out fast will indeed make your display run faster.

Hi guys,
To be perfectly honest, most of the stuff you guys have talked about has gone over my head. I’m only new to all of this. I do plan to get into it all but I’m short on time at the moment with uni exams.
Thanks a heap for all of your help though.

Understand about being busy. When you get back into it .. this may help you get to end of goal quicker.

1) Create copy of existing project to modify and save it 2) Unzip and install LEDControlFast into your libraries folder (and restart Arduino if open) http://www.hookedup.com/joe/hookedup.nsf/LEDControlFast 3) Open the new copy of your project, find LEDControl and replace with LEDControlFast 4) Cross your fingers, compile and upload :)

Hope that helps clear up the tons of code and technical details.

Best of luck with exams and projects :)

How about a version that uses the actual SPI hardware to talk to the SPI port on the max7219/7221?

I don't understand why everyone is so hung up on bit banging this stuff out.


  1. Create copy of existing project to modify and save it
  2. Unzip and install LEDControlFast into your libraries folder (and restart Arduino if open)
  3. Open the new copy of your project, find LEDControl and replace with LEDControlFast
  4. Cross your fingers, compile and upload :slight_smile:

When I do this, (including in the included examples i get an error:

'LedControlFast' does not name a type
In file included from LCDemoCascadedDevices.cpp:2:
<path>\arduino-0022\libraries\LEDControlFast/LedControlFast.h:164: error: expected constructor, destructor, or type conversion before 'by'
LCDemoCascadedDevices:12: error: 'LedControlFast' does not name a type
LCDemoCascadedDevices.cpp: In function 'void setup()':
LCDemoCascadedDevices:24: error: 'lc' was not declared in this scope
LCDemoCascadedDevices.cpp: In function 'void loop()':
LCDemoCascadedDevices:38: error: 'lc' was not declared in this scope

When I do this, (including in the included examples i get an error:

I'll hazard a guess, and say that that is not all the errors. There is a scroll bar on the output window. Are you moving it all the way to the top? I'm guessing that there is a message about not being able to find an include file that has scrolled off.

The untested code should have compiled - make sure you have the library in the correct location.

@CrossRoads - I agree that bit banging this stuff out is not the best route and I don't see why the library uses bit bang in the first place. The initial link provided shows how to use SPI as well and as noted a couple of times is the best route.

Hey Guys,
So i checked that the library is in the right place and tried again.
The error is when trying to compile and upload one of the examples, LCDemoMatrix, although it happens in all of the library-included examples.
I have checked I got the whole error log.

I am running arduino-0022

‘LedControlFast’ does not name a type
LCDemoMatrix:11: error: ‘LedControlFast’ does not name a type
LCDemoMatrix.cpp: In function ‘void setup()’:
LCDemoMatrix:21: error: ‘lc’ was not declared in this scope
LCDemoMatrix.cpp: In function ‘void writeArduinoOnMatrix()’:
LCDemoMatrix:44: error: ‘lc’ was not declared in this scope
LCDemoMatrix.cpp: In function ‘void rows()’:
LCDemoMatrix:103: error: ‘lc’ was not declared in this scope
LCDemoMatrix.cpp: In function ‘void columns()’:
LCDemoMatrix:124: error: ‘lc’ was not declared in this scope
LCDemoMatrix.cpp: In function ‘void single()’:
LCDemoMatrix:145: error: ‘lc’ was not declared in this scope

Post your code. I a heading out on vacation but most likely your include is not pointing to LedControlFast.

That was also just a quick toss together to help get you started, if that does not work you may want to go back and read the other posts and start with the simplest example / working code and go from there.

CrossRoads: Did you ever try running this using SPI as we discussed elsewhere in the forum, vs using software bit-bang?

I have a error msg

16x8_LED_MatrixMAX7219.ino:109:18: error: font.h: No such file or directory

How I correct this? :(

How I correct this?

Well, duh. Create the missing file.