Introduction: 64 Pixel RGB LED Display - Another Arduino Clone

Picture of 64 Pixel RGB LED Display - Another Arduino Clone
This display is based on an 8x8 RGB LED Matrix. For testing purposes it was connected to a standard Arduino board (Diecimila) using 4 shift registers. After getting it to work I permatized it on a fabbed PCB. The shift registers are 8-bit wide and are easily interfaced with the SPI protocol. Pulse width modulation is used to mix the colors, more on that later. Part of the MCU`s RAM is used as a framebuffer to hold the image. The video RAM is parsed by an interrupt routine in the background, so the user can do other useful things like talking to a PC, read buttons and potentiometers.

More information about "Arduino":

Step 1: Pulse Width Modulation for Mixing Colors

Picture of Pulse Width Modulation for Mixing Colors

Pulse width modu - WHAT ?

Pulse width modulation essentially is turning the power fed to an electrical device ON and OFF pretty quickly. The usable power results from the mathematical average of the square-wave function taken over the interval of one period. The longer the function stays in the ON position, the more power you get. PWM has the same effect on the brightness of LEDs as a dimmer on AC lights.

The task ahead is to individually control the brightness of 64 RGB LEDS ( = 192 single LEDs ! ) in a cheap and easy way, so one can get the whole spectrum of colors. Preferably there should be no flickering or other disturbing effects. The nonlinear perception of brightness exhibited by the human eye will not be taken into account here ( e.g. the difference between 10% and 20% brightness seems "bigger" than between 90% and 100% ).

Image (1) illustrates the working principle of the PWM algorithm. Say the code is given a value of 7 for the brightness of LED(0,0). Furthermore it knows there is a maximum of N steps in brightness. The code runs N loops for all possible levels of brightness and all necessary loops to service every single LED in all rows. In case the loop counter x in the brightness loop is smaller than 7, the LED is turned on. If it's larger than 7, the LED is turned off. Doing this very quickly for all LEDs, brightness levels and base colors (RGB), each LED can be individually adjusted to show the desired color.

Measurements with an oscilloscope have show that the display refresh code takes about 50% CPU time. The rest can be used to do serial communication with a PC, read buttons, talk to an RFID reader, send I2C data to other modules...

Step 2: Talking to Shift Registers and LEDs

Picture of Talking to Shift Registers and LEDs

A shift register is a device that allows for loading data serially and a parallel output. The opposite operation is also possible with the appropriate chip.

There's a good tutorial on shift registers on the arduino website.

The LEDs are driven by 8-bit shift registers of the type 74HC595. Each port can source or sink about 25mA of current. The total current per chip sinked or sourced should not exceed 70mA. These chips are extremely cheap, so don't pay more than about 40cents per piece. As LEDs have an exponential current/voltage characteristic, there need to be current limiting resistors.

Using Ohm's law:

R = ( V - Vf ) / I

R = limiting resistor, V = 5V, Vf = LED's forward voltage, I = desired current

Red LEDs have a forward voltage of about 1.8V, blue and green range from 2.5V to 3.5V. Use a simple multimeter to determine that.

For proper color reproduction one should take a few things into account: spectral sensitivity of the human eye (red/blue: bad, green: good), efficiency of the LED at a certain wavelength and current. In practice one simply takes 3 potentiometers and adjusts them until the LED shows proper white light. Of course the maximum LED current must not be exceeded. What's also important here is that the shift register driving the rows must supply current to 3x8 LEDs, so better not push the current up too high. I was successful with limiting resistors of 270Ohm for all LEDs, but that depends on the make of the LED matrix of course.

The shift registers are interfaced with SPI serial. SPI = Serial Peripheral Interface ( Image (1) ).
Opposed to the serial ports on PCs (asynchronous, no clock signal), SPI needs a clock line (SRCLK). Then there's a signal line telling the device when the data is valid (chip select / latch / RCLK). Finally there are two data lines, one is called MOSI (master out slave in), the other one is called MISO (master in slave out). SPI is used to interface integrated circuits, just like I2C. This project needs MOSI, SRCLK and RCLK. Additionally the enable line (G) is used as well.

An SPI cycle is started by pulling the RCLK line to LOW ( Image (2) ). The MCU sends its data on the MOSI line. The logical state of it is sampled by the shift register at the rising edge of the SRCLK line. The cycle is terminated by pulling the RCLK line back to HIGH. Now the data is available at the outputs.

Step 3: Schematic

Picture of Schematic

Image (1) shows how the shift registers are wired. They are daisy-chained, so data can be shifted into this chain and also through it. Therefore adding more shift registers is easy.

Image (2) shows the rest of the schematic with the MCU, connectors, quartz...

The attached PDF file contains the whole works, best for printing.

Step 4: C++ Source Code

Picture of C++ Source Code

In C/C++ usually one has to prototype functions before coding them.

#include <stdio.h>

int main(void);
void do_something(void);

int main(void) {



void do_something(void) {

/* comment */


The Arduino IDE does not require this step, as functions prototypes are generated automatically. Therefore function prototypes won't show up in the code shown here.

Image (1): setup() function

Image (2): spi_transfer() function using hardware SPI of the ATmega168 chip (runs faster)

Image (3): framebuffer code using a timer1 overflow interrupt.

Pieces of code that have a slightly cryptic look for beginners e.g. while(!(SPSR & (1<<SPIF))) {} use the MCU's registers directly. This example in words: "while the the SPIF-bit in register SPSR is not set do nothing".

I just want to emphasize that for standard projects it is really not necessary to deal with these things so closely related to hardware. Beginners should not be frightened by this.

Step 5: Finished Gadget

Picture of Finished Gadget

After having solved all problems and getting the code running, I just had to created a PCB layout and send it off to a fab house. It look so much cleaner :-)

Image (1): fully populated controller board

Image (2): front side of the bare PCB

Image (2): back side

There are connectors breaking out PORTC and PORTD of the ATmega168/328 chip and 5V/GND. These ports contain the serial RX,TX lines, the I2C lines, digital I/O lines and 7 ADC lines. This is intended for stacking shields on the backside of the board. The spacing is suitable for using perfboard (0.1in).

The bootloader can be flashed using the ICSP header (works with adafruit's USBtinyISP). As soon as that is done, just use a standard FTDI USB/TTL serial adapter or similar. I've also added an auto-reset-disable jumper. I've also cooked up a little Perl script (see my blog), that enables auto-reset with FTDI cables which usually doesn't work out of the box (RTS vs. DTR line). This works on Linux, maybe on MAC.

Printed circuit boards and a few DIY KITs are available on my blog. SMD soldering required! See the PDF files for building instructions and sources for LED matrices.

Step 6: Application: CPU Load Monitor for Linux Using Perl

This is a very basic load monitor with a history plot. It's based on a Perl script that gathers the system's "load average" every 1s using iostat. Data is stored in an array which is shifted upon each update. New data is added at the top of the list, the oldest entry gets pushed out.

More detailed information and downloads ( code... ) are available on my blog.

Step 7: Application: Talking to Other Modules Using I²C

Picture of Application: Talking to Other Modules Using I&sup2;C
This is just a proof of principle and by far not the simplest solution for this job.

Using I2C allows for directly addressing up to 127 "slave" boards. Here the board on the right side in the video is the "master" (which initiates all transfers), the left board is the slave (waiting for data). I2C needs 2 signal lines and the usual power lines (+, -, SDA, SCL). As it is a bus, all devices are connected to it in parallel.

Step 8: Application: "Game Cube" :-)

Picture of Application: &quot;Game Cube&quot; :-)

Just a freak thought.

This one also fits into to wooden enclosure shown on the intro page. It's got 5 buttons on its backside which might be used for playing a simple game.


Step 9: Displaying Images / Animations on the Matrix - Quick Hack

Picture of Displaying Images / Animations on the Matrix - Quick Hack
So it's only got 8x8 pixel and a few colors available.

First use something like Gimp to scale down you favorite image to exactly 8x8 pixels and save it as ".ppm" raw format (not ASCII). PPM is easy to read and process in a Perl script. Using ImageMagick and the command line tool "convert" won't work properly.

Upload the new arduino code, then use the Perl script to upload to the controller.

The flicker is just a mismatch of LED refresh and my camera's frame rate. After updating the code a bit, it runs quite zippy. All images are transfered live over serial as you see them.

Longer animations could be stored in an external EEPROM like it is done in various spoke-pov boards.

Step 10: Interactive Control of Stored Animations

Why let the microcontroller have all the fun ?

The Arduino cult is all about physical computing and interaction, so just add a potentiometer and take control ! Using one of the 8 analog to digital converter inputs makes that very simple.

Step 11: Showing Live Video

Using a Perl script and a few modules makes it quite easy to show quasi live video on X11 systems. It was coded on linux and may work on MACs as well.

It works like this:

- get the mouse cursor position
- capture a box of NxN pixel centered at the cursor
- scale the image to 8x8 pixel
- send it to the LED board
- repeat

Step 12: More Light Almost for Free

Picture of More Light Almost for Free
With just two steps the brightness can be upped quite a bit. Replace the 270Ω resistors with 169Ω ones and piggyback another 74HC595 shift register onto IC5.


RyannA1 (author)2017-12-13

Dear Madworm,

How can i use your board to display alphabet letters?

I Gusti KadeS (author)2017-03-13

Dear Madworm,

This is great tutorial, and this is what i look for for all my time before. but i have a question, would you please to add some detail about the scaning (how to give 7 value for N maximum). your project is used 8x8 RGB led instead of i want to try with 8X1 RGB led.

Thanks in advance

madworm (author)I Gusti KadeS2017-03-20


I'm rather busy right now.

If you still want to go the way of using shift registers, I strongly suggest having a look at the Arduino "ShiftPWM" library! It takes away all of the hard work for you.



I Gusti KadeS (author)madworm2017-09-11

Dear Robert,

i'm sorry for my late reply.

thanks a lot for your suggestion.



ArjunN1 (author)2017-01-27

Its a great poject, Can u help me find which pins of the arduino pro mini and uno have to be used here to have proper spi communication, i couldnt find it easy to get around ur code, and didnt understand which all pins are to be used. Plsss kindly help me with ur 5 mins.

madworm (author)ArjunN12017-01-27

Latest code:

In the first few lines you'll find the pin numbers. "Digital" pins 9 - 13 on the UNO and compatible ones. Same numbers on the other board. They should be printed on the PCB.

CinehdvM (author)2016-12-20
Hi, I'm doing a 4x4 rgb led track per m2 of 20 panels, for a matrix with 16 led rgb you need 2 shift (16chanels), use 4 Red, 4Green, 4Blue and 4 Sweep, it is possible to use this code and modify it To make it work?
madworm (author)CinehdvM2016-12-21

Better look at "shiftPWM" or use adafruit neopixel / WS2812B.

skandinaff (author)2015-04-13

Hi Madworm, great tutorial you've got here. So I've been working on implementing your circuit, but I have only arduino mega, and apparently it doesn't work there. I presume, it's something in functions that are working with SPI and timer register, that must be different for Mega.

Would you be able to point out what exactly has to be changed? I relatively new to arduino, and coding myself, so yeah.

I've tried simulation with arduino uno, and it worked fine. Mega doesn't work in simulation either..

madworm (author)skandinaff2015-04-13

I need to look at the ATmega1280 datasheet first. I assume you've got this board (

That will take some time. It is not radically different, but I need to find the right bits & pieces.

NeonBorealis (author)madworm2016-09-29

Excuse me, have you found the difference between both boards?

I'm trying to build this, but so far the functions seem unresponsive.

Any help would be appreciated.

madworm (author)NeonBorealis2016-10-06

I haven't looked at the differences closely. Most likely difference in timer interrupts (naming & which one is free to use) / register naming.

madworm (author)skandinaff2015-04-20

First make sure you've connected the right pins. According to the datasheet [atmega1280] that chip only has ONE hardware SPI device. See where the pins MOSI, MISO, SCK and CS end up on the Arduino Mega.

Some error messages would be helpful, if there are any.

skandinaff (author)madworm2015-04-20

Yeah, I've connected data pin to 51st of arduino mega, clock to 52nd latch to 53rd. (according to second table here -

In proteus simulation I'm getting "Writing to UDR3 while transmission is not enabled"

As for real circuit, I was testing just with one (or four) led(s), not entire matrix, and if I remember correctly, LED(s) just didn't do anything, and shift registers maintained constant levels on their outputs.

madworm (author)skandinaff2015-04-21

That is odd. UDR indicates involvement of an UART.

Try this:

That should "blink" all 4 shift registers on/off every second.

If that works, we know SPI is good.

dancopy (author)2016-09-22

It is possible to connect several modules that make for a longer text?
How would the schematic? Thank you

madworm (author)dancopy2016-09-26

Possible yes, advisable no.

You'd be better off with one much more powerful microcontroller, than having to deal with syncing up many very busy one.

I'd look into WS2812B LEDs or similar, so driving the LEDs themselves is much simpler & doesn't occupy most of the cpu time.

Alfa Romeo Elite (author)2015-09-15

Hey madworm,That's a great instructable right there. But could you please help me in scrolling text on the matrix? I think that's the only feature missing in the instructable..

rfrantzen (author)2015-03-23

Hi Madworm, I'm currently playing with this setup and do wonder one thing, Was there a specific reason to put the resistors on the cathode side? As they are all the same value technically they can be on the anode side as well, reducing the amount to only 8 instead of 24. I know it isn't the cost but it does makes the board a bit cleaner. Or am I overseeing something here?

madworm (author)rfrantzen2015-03-31

Yes, there is an issue.

Putting the resistors on the anode side leads to noticeable and unpleasant brightness variations when more than one colour is turned on.

1 resistor + 1 LED --> normal brightness

1 resistor + 2 LEDs --> roughly half the current for each --> dimmer

rfrantzen (author)madworm2015-04-02

Clear, Thnx

stefa168 (author)2015-02-27

Hi Madworm, thanks a lot for your instructable!

Right now, I'm trying to implement a control with a second arduino, so that the one connected to the matrix can just do that stuff (it won't get interferences from other parts of the code)

I was going to use SPI because with interrupts I can transmit all the 192 Bytes (I will reduce them when I will manage to do this). To do so, I have to use the SS pin of the Processor, which is pin 10. Now, here's the problem: I tried to modify correctly all the sketch so that it doesn't use pin 10 but pin 8. However the matrix filckers and then completely turns off. I don't know what I can do, as I need that pin to be free so that I can use it with the SPI_STC_vect interrupt..

How can I free that pin, so that I can use it for slave SPI?


madworm (author)stefa1682015-02-28

The AVR already uses hardware SPI in master mode to talk to the shift registers. This is quite time-critical interrupt code. I'm not quite sure how you would have it listen as a slave at the same time without disabling the matrix stuff, or if that is possible / advisable at all. Sounds like trouble to me.

stefa168 (author)madworm2015-02-28

But isn't possible to use the the shiftOut function for the matrix instead of the hardware SPI? or it is slower?

madworm (author)stefa1682015-03-31

Yes and no. You can use it, but it is waaaaaaaaaaaay too slow.

MarcM6 (author)2015-01-15

Thank you for posting this example. It gave me inspiration to make a more complete version and write a full driver for tri color matrices while talking to them through a higher level library to draw pixmaps, letters, shapes and get scrolling text in multiple colors:

Would you mind adding a link for folks who might be looking for such a driver?

Cheers, Marc

madworm (author)MarcM62015-03-31

Sorry for answering so late. I didn't get notifications from Instructables for a while, today I got lots.

Luisdlahuerta made it! (author)2014-09-08

Very good project!!! thanks you very much, its running perfect in my protoboard.

congratulations ;)

nice blog

best regards

madworm (author)Luisdlahuerta2014-09-08


sirkevin27 (author)2013-11-23

Hey Madworm,

I spent all day learning about interrupts and now understand nearly all the interrupt business in your code.

I just don't understand TIMER1_CNT = 0x0130 corresponds to 32 levels of brightness... that number is 304 no?

because you have TCNT1 = TIMER1_MAX - TIMER1_CNT
so TCNT1 = 0xFFFF - 0x0130 = 65536 - 304 ? Does that mean TCNT1 is initialized to 65232, increments on every prescaler cycle till it hits 65536, starts the interrupt where TCNT1 is re-initialized to 0xFFFF-0x0130 and does the interrupt business?

I understand this TCNTn business with the CTC mode, but I'm confused how you used it here in normal mode and how you find the value corresponding to the amount of brightness levels. Also was there a benefit to using an overflow interrupt than a CTC one? Once I understand this, I'll finally feel like I truly understand your code (After staring at it all summer).

Thanks!! Really want to build this.

madworm (author)sirkevin272013-11-23

Let me look at that code... it's been a while :-)

Ah... I see.

OK, the purpose of preloading the timer is to have control over the frequency at which it is called. The plain overflow interrupt would take 2^16 cycles to complete, which in this case is too long.

You see, the crude PWM generation relies on persistence of vision and uses a lot of loops in the interrupt code. These loops take some time to finish.

If the ISR frequency is too high, all CPU time is eaten up in the interrupt, up to the point that the system appears to be frozen.

If the frequency is too low, the display will flicker.

The TIMER1_CNT value was determined by trial and error. The more colour-levels are to be displayed, the more time the ISR takes --> TIMER1_CNT must get larger.

Using this manual preloading vs. CTC mode... it should be equivalent. I think in later versions of the code (github) I used CTC mode. Later I switched to binary weighted PWM generation (much less CPU utilization, but it has other issues).

sirkevin27 (author)madworm2013-11-23

Ahh ok! I was thinking there was a formula or something for determining that, but when I calculated the frequency of the interrupts it was 51.23Hz, so I was thinking it was a trial and error kind of value. Now that you've confirmed that, I think I'm ready to build this thing!

Thanks a ton!

madworm (author)2013-08-07

It has been done, but it comes at a cost.

Generating the PWM in software for true-color (sort of) takes up a big slice of CPU time. At some point, there's nothing left for anything else. If I had to do it again for a 128px display, I'd at least use dedicated LED-drivers with integrated PWM generation. A faster micro would help as well.

BAKSTEEN (author)2013-08-07

How about combining 2 of these, for like a game of tetris or just a larger game/screen is is possible to just hook up more ic`s and a second screen ?

devilmaycry (author)2011-07-27

Hi Im currently doing GCSE ELECTRONICS KS4 AND i WANT make a project like your one but the problem is I want to make a Arduino Clone if u guys know any step by step instructable for beginners cos im new to micro controllers.MANY THANKS

Tone341 (author)devilmaycry2012-10-17

madworm (author)devilmaycry2011-07-27

Well, the good news is that there is nothing special about making an arduino clone, no magic involved. A minimal working board is just a few parts (no on-board usb).

I don't know if you're shooting at making your own pcb with smd parts or through hole components. There's also nothing special about that, except that it costs serious money to have pcbs fabbed (more than just a few) and getting a non-functional board is quite upsetting. So you'll want to make sure you have a functional prototype of your schematic working on breadboard or vero/perf/strip-board before you shell out big money.

As you've mentioned GCSE, I take it you're in the UK. For small or one-off prototypes, there's a UK site that offers a nice service for just that ( )

As far as minimal arduino clones go, the 'boarduino' seems like a good starting point to me ( ). Easy to build with self-sourced parts.

As far as using PCB layout software, this can be a bit tedious at first, probably even quite annoying. Personally I use KiCAD ( ), which is open source and does not have any constraints like the 'free' version of EAGLE.

Also I'd like to invite you to join the arduino forum on - quite a lot of UK folk hang out there as well.

angler (author)2011-10-18

How is the total shift register (per chip) current kept below 70mA? Is this why you chose 270ohm?

angler (author)angler2011-10-18

Oops, just read the 1:8 duty cycle comment below.

acotton1 (author)2011-04-26

Hi madworm,

I have one week to make an RGB LED matrix for school. How did you wire your matrix up before you transferred it all to PCB? Due to time and money constraints, I can't have a PCB manufactured. Do you have photos, schematics, or diagrams?

madworm (author)acotton12011-04-26

One full week... snigger. SCNR.

Before it was transfered to a PCB, I uses a breadboard to test the circuit. Suffice to say that it was no fun at all. So many wires...

If you go to my blog (the link is here somewhere), you will find schematics and photos (flickr) and some code as well. The best entry point is the projects page. Other posts may have outdated content. If you intend to use any of it, make sure to get the latest versions of both from the git repositories, otherwise it may have unpredictable effects.

wmtt (author)2011-03-30

Dear madworm.

Good news, I have success in having a working RGB matrix after following your codes and instruction.

May I know how do i modify the code if I want to run it on a stand alone ATMEGA168/328 but NOT from a Arduino?

Thank you again!

madworm (author)wmtt2011-03-31

Well, once you have compiled the code (for an 168 or 328) into the .hex-files, you can just take your favourite ISP programmer and flash the chips. No need to change the source code. On linux systems the .hex files are temporerily created in '/tmp/build.xxxx', on windows I frankly don't know.

Just make sure the FUSE settings of the chips are correct. For an 168 these would be:

a) no bootloader, 16MHz quartz, 16kb usable:

lock: 0x3F
lfuse: 0xFF
hfuse: 0xDD
efuse: 0x01

b) with bootloader, 16MHz quartz, 14kb usable:

lfuse: 0xFF
hfuse: 0xDD
efuse: 0x00

wmtt (author)2011-02-27

Dear Madworm, you are great! I saw your works elsewhere in the net and I think you are the one to answer my question.

I want to build a 5x5x5 LED matrix using RGB LED, I don't think I will have problem controlling each layer but I have 5 layers to control. I want to

1. mix colour (not just the 7 colours) of the RGB and
2. contol the brightness of each and single LED

so I think PWM (software by 74HC595 or by hardware TLC5940) is my answer.

Do you recommend

1. use the 74HC595 and mutliplex them for each layer (5 layers) or
2. Use TLC5940 and multiplex them for each layers?

I worry that by multiplexing the chips, I do not get enough refresh rate and give rise to LED flickering......

Looking forward to your kind assistance. Any information would be much appreciated!!!

madworm (author)wmtt2011-02-28

I'd use TLC5947 if possible. Very similar to 5940, but simpler to use. Needs no external grayscale clock, which keeps the microcontroller busy all the time. Just send the data and forget about it. With these you'll need a few extra transistors to drive the layers. Maybe something like UDN2981A if you can get that one.

The multiplexing makes it dimmer, but with professional driver chips you can compensate to some degree by adjusting the external current reference resistor. Just make sure that you never stop multiplexing...

But for color mixing anything is better than 595 chips. It may be doable, but at some point you'll wish you hadn't gone that way ;-) A whole lot of time is wasted just for generating the pwm signals.

wmtt (author)madworm2011-03-06

Dear madworm

Thanks for your kind reply. I have already received the 5947 samples from Ti and will give it a try. But exactly do i multiplex it, can you give me some direction or exact way / scehmatic that I can follow.....

One more question, is your 64-pixel RGB matrix true color and not just the 7-color....?

Thank you once again.

WM Tang

madworm (author)wmtt2011-03-06

For starters, you can find an example for a single line of 8 RGB LEDs driven with a 5947 there:

For multiplexing, you need to have a look at the BLANK input. It should be used when a new row is addressed.

A project based on the 5940 is this one:

It should convey the principle of what you need to do.

What the 5947 needs is similar. You need to compensate for the 24 vs 16 channels and throw everything out that deals with GSCLK, as this is handled internally.

My doodad can do more than 7 colors ;-) Technically it supports 32768 shades, but not all of them look different to the eye.

dunnos (author)2011-01-02

When I read the last step I laughed, so hard. Seriously man, piggyback! awesome

wilhelmmaybach (author)2010-12-02

Hey Madworm, great Instructable, just one question: I understood most of the project, but I dont get the PWM part. I understand you use th 74 595 for selecting or controlling each led and each lead of the LEDs, (1 for red, 1 for green, 1 for blue, 1 for the anode or cathode) but, I dont get how you "connect" the PWM to each RGB, I understood very well what PWM is, but, for example, you have in your PWM a frecuency of 1 Hz (only example), and a PWM resolution of 8 bits, and duty cycle of 25%, ok, you want to connect the output of this PWM to any lead of any led, how do you achieve that?? how do you refresh or shift data into the shift registers to achieve that?? thanks for your response, and please be a little detailed, thanks a lot...:)

madworm (author)wilhelmmaybach2010-12-03

There is no single PWM source that needs "connecting" to the LEDs. The PWM is created by sending a bit-stream to the shift registers at high speed.

If it helps you understand think of it as many virtual PWM sources that are sequentially sampled, which created the bit-stream. All of this is done in software. The result of this is then sent to the shift registers, which just reproduce it. Each SR pin is a PWM source that way.

About This Instructable




Bio: Slightly Dorky High Nerd - You might find some of my stuff on Tindie.
More by madworm:IKEA Samtid mood-light upgradeIKEA Samtid reading light upgradeBreadboard LED indicator
Add instructable to: