In recent posts I’ve covered how to use LEDs and how to use shift registers and even how to combine shift registers and LEDs to control numeric displays. In this post we’re going to use 64 LEDs in an 8×8 LED grid.

Wait! You did this one back in 2014!

True. I was a very special kind of stupid back then – I didn’t understand the documentation for the pins on the LED grid. I figured out how to control it through trial and error. This time we’re going do it with shift registers and read the datasheet correctly so there’s less fooling around.

This is the dimension diagram included on the product page for the LED grid.

Take a look at the picture in the bottom right. The COL anodes and ROW cathode values (without circles) are the columns and rows of the grid, and the adjacent number in a circle is the pin number on the back of the panel. You might as “Why didn’t they put pin 1 to row 1, and so on?” A mystery for the ages. The top left image says PIN1 is in the south-west corner. But which corner is that? The grid looks symmetrical from all sides.

The south edge of the grid has a small dimple in the bottom center (under the 1088BS printing). The pin numbers go up in a counter-clockwise direction, same as on an integrated circuit like the shift registers.

In the previous tutorial with on how to use four digit seven segment displays I organized all the anodes on one shift register and all the cathodes on another. In this version the anodes (columns denoted C) and cathodes (rows denoted R) are all mixed up. By doing it a different way I hope you see there’s more than one way to solve a problem. Which you use is up to you.

I really like the symmetry of the wires going into the 8×8 LED grid. I like it even more when I lay out the traces on a PCB.

Notice that the footprint for the LED grid includes the dimple at the bottom center.

Persistence of Vision

Persistence of vision becomes trickier as the number of lights goes up. In the earlier tutorials we had a low number of lights so the slice of time dedicated to each light was big. Turning on each light long enough that all lights looked lit was easy enough. 128 lights start to flicker because the slice of time is much smaller… if we light every light one at a time. Thankfully we have shared anodes and shared cathodes, so we can turn on up to eight lights at once in a single row or column.

Lighting a single LED

By reading the data sheet and the wiring diagram, I worked out which shift register pins do what.

LED Shift register location pin pin order row 1 9 u1 15 0 row 2 14 u1 5 5 row 3 8 u2 7 15 row 4 12 u1 3 3 row 5 1 u2 15 8 row 6 7 u2 6 14 row 7 2 u2 1 9 row 8 5 u2 4 12 col 1 13 u1 4 4 col 2 3 u2 2 10 col 3 4 u2 3 11 col 4 10 u1 1 1 col 5 6 u2 5 13 col 6 11 u1 2 2 col 7 15 u1 6 6 col 8 16 u1 7 7

From this I could make a sorted list such that row[n] would give me the shift register pin for that row or column.

#define WIDTH 8

#define HEIGHT 8

const char rows[WIDTH ] = { 0,5 ,15, 3, 8,14, 9,12 };

const char cols[HEIGHT] = { 4,10,11, 1,13, 2, 6, 7 };

This way I could make changes to a block of memory, Serial.print() the block of memory to check it’s sane, and then fast copy the memory to the registers in a single method.

#define NUM_REGISTERS 16

char registers[NUM_REGISTERS];

#define SR_SER 2 // data

#define SR_LAT 3 // latch

#define SR_CLK 4 // clock

// feed the 15th shift register value in first and the 0th last.

void updateRegisters() {

for(int i=0;i<NUM_REGISTERS;++i) {

// set the data

digitalWrite(SR_SER,registers[NUM_REGISTERS-1-i]);

// shift everything in one step

digitalWrite(SR_CLK,HIGH);

digitalWrite(SR_CLK,LOW);

}

// flip the latch - copy the shift registers to the outputs

digitalWrite(SR_LAT,HIGH);

digitalWrite(SR_LAT,LOW);

}

#ifdef ROWS_ARE_ANODES

#define ROW_ON HIGH

#define ROW_OFF LOW

#define COL_ON LOW

#define COL_OFF HIGH

#else

#define ROW_ON LOW

#define ROW_OFF HIGH

#define COL_ON HIGH

#define COL_OFF LOW

#endif

// state == ROW_ON or ROW_OFF

void setRow(int x,int state) {

registers[rows[x]]=state;

}

// state == COL_ON or COL_OFF

void setCol(int y,int state) {

registers[cols[y]]=state;

}

Now I can call setCol(), setRow() and updateRegisters() to light a single light. I could then repeat that for every light in the grid. By very quickly stepping through all the lights and doing lots of updateRegisters() I can use persistence of vision to light the whole grid.

Drawing pictures

A good optimization would be to turn on several lights on a single row with multiple calls to setCol(). To do that it would be handy to repeat the fast memory copy idea from a picture to the registers[] to the shift registers to the LEDs.

char grid[WIDTH*HEIGHT];

void showGrid() {

int x,y,k;

k=0;

for(y=0;y<HEIGHT;++y) {

setRow(y,ROW_ON);

for(x=0;x<WIDTH;++x) {

// k always = y*WIDTH+x.

setCol( x, grid[k]==1 ? COL_ON : COL_OFF );

++k;

}

updateRegisters();

setRow(y,ROW_OFF);

}

}

With that I can set any light grid[y*WIDTH+x] to 1 for on and 0 for off that I want and then call showGrid(). Here’s an example of a smiley face.

void drawSmiley() {

char smiley[] = {

0,0,0,0,0,0,0,0,

0,0,1,0,0,1,0,0,

0,0,1,0,0,1,0,0,

0,0,0,0,0,0,0,0,

0,1,0,1,1,0,1,0,

0,1,0,0,0,0,1,0,

0,0,1,1,1,1,0,0,

0,0,0,0,0,0,0,0,

};

for(int i=0;i<WIDTH*HEIGHT;++i) {

grid[i]=smiley[i];

}

showGrid();

}

Now you try

easy level: Make a bunch of faces and switch between them to create animation.

tough level: Make a big block of memory for a long picture and then show a tiny section of that block on the screen. Over time, move which part is being shown. It would look like a scrolling message.

expert level: stream data from your PC to the arduino and have it display on the LEDs. Update the video in real time!

Video demo

See Also

KiCAD component and footprint, datasheet, dimension diagram, etc on the 8×8 LED product page.

Github repo for this tutorial, including source code

[products skus=’ELEC-0082, ELEC-0116′]