8×8 RGB Matrix — 2nd arduino project

OK, now for something completely different…

Some RGB LED matrix effects – Code: matrix_code.pde
(This code will not run flawlessly on up-to-date matrix boards without modification. Please only use this code, unless you exactly know what you’re doing.)

So far I’ve got some code using a timer1 or timer2 interrupt to do several nice effects. Maybe I’ll write some code for the device to listen to commands on the serial line.

But before that happens I need to get the PCB making process going again. It’s either toner transfer if I get the laminator hot enough, or I’ll buy some UV device to do old fashioned photographic layout transfer. What a pity that I sold one of these a few years ago. Now they cost the same numeric value in € as 10 years ago in DM.

latest schematic:

SR = 74HC595, 8bit shift register, SPI interface (clock, data, latch)

UDN2981A = current source driver to power common anode rows

Please also have a look at my projects page for the latest demo code.

Comprehensive writeup at instructables.com

This entry was posted in Arduino., Electronics. and tagged , , , , . Bookmark the permalink.

41 Responses to 8×8 RGB Matrix — 2nd arduino project

  1. Helmut says:

    Hi Robert,
    verschenkst du sowas zu Weihnachten? :-)

    Gruss Helmut

  2. robert says:

    suchst du ein neues hobby ?

  3. Valentine89 says:

    hi Robert, how much deep of solution in this RGB? sorry for my bad english.

  4. robert says:

    Well, nominally it supports up to 32768 colors. That is 15bits (or 5bits per color).
    What the effective (perceived) number of colors is, that can be discerned with your eye, is a totally different matter. I suppose that will be a lot less.

  5. ISO-B says:

    I build 8×8 RGB matrix my self. First I tested with no pwm and rowscanning that all leds work. Then I started to use your code, which I very good. Last row of my matrix does something wierd. It stays brighter than other rows.Do you know how I can fix that?

  6. robert says:

    There are three things that come to my mind:

    • there is a wrong current limiting resistor on the last row
    • the code somehow spends more time processing the last row
    • the display is not turned off outside the PWM generation

    The last point exactly reproduces the error.

    This is why all of the PWM code has this form:

    digitalWrite(__display_enable,LOW); // enable display inside ISR
    the PWM code
    digitalWrite(__display_enable,HIGH); // disable display outside ISR

  7. ISO-B says:

    I have same resistor on each row. red 180ohm and 100ohm on blue and red. So that doesn’t solve this mystery. And I check the code and it have the that you talked about.

  8. ISO-B says:

    When I say last row I mean the row which use the last anode to work.

  9. robert says:

    So you’ve eliminated all of the options I had in mind.

    Did you try swapping the 595 chips ? If the error is persistent when you change positions of the chips, it is a systematic problem (unless of course all the chips should have the same anomaly). If you have an oscilloscope you could check the PWM signal for the last row and see if the effective on-time is different compared to all the other rows.

    What happens if you _remove_ “digitalWrite(__display_enable,HIGH)” ? Does the last row get even brighter ?

  10. ISO-B says:

    • the code somehow spends more time processing the last row
    That is something that I didn’t tested, because I don’t know how to test that.

    Changed the chip.

    Last row is brighter than other row, but when I remove that line all color change to very wierd.

    And now one of rows doesn’t turn on.

  11. robert says:

    So if swapping the 595 chips doesn’t change anything it is also a code problem. And removing the line of code as I suggested should only change the brightness of the last row, but not the colors of all the rest. I have the feeling that code and hardware don’t interact properly. Do you have a schematic for your project ?

  12. ISO-B says:

    Controller: http://lh4.ggpht.com/_45WOFW8ZSb4/SV66O0j6HlI/AAAAAAAAFGg/WcVBQAnX1Vs/s912/LEDMatrixSchematic.png
    I use different resistors as I already said.

    I recorded video so you can better see what happens.

    My own code: http://pastebin.com/m14369991
    That I used to test all leds work.

  13. robert says:

    Aha! Now we’re getting to the poodle’s core.

    In the schematic you’ve posted, some things are different to what my demo code expects.


    OE (output enable) is wired to GND, which gives the “last row” error. My demo code expects OE to be wired to PB1 (Arduino digital pin #9) of an ATmega168/328 chip. And a 10k pull-up resistor should go from PB1 to +5V. This gives control over ON/OFF for the whole LED matrix and eliminates the “last row” brightness problem.


    The order of the 595 chips is different:

    My demo code expects this order (schematic):


    Your schematic + my demo code needs this:


    A wrong order will create “funky” colors.

  14. ISO-B says:

    When I changed code. To met order of colors like they are in hardware. It work, but the last row is still brighter…
    New video: http://share.ovi.com/flash/player.aspx?media=the_banda.10478

    I hope you can see those videos.

    Code change:



  15. robert says:

    Yes I can see the videos. The last one is almost OK.

    I’m 99.9% certain, that the remaining problem comes from not correctly wiring the OE pins of the 595 chips.

    The micro controller spends about 50% of the time generating the PWM signals (only running the interrupt code, nothing else). The other 50% is free for whatever comes in the loop() function. If it happens that at the end of the ISR() function the LEDs of the last row are ON because the code decided it should be like that, and the display is not turned off by setting OE to +5V, the LEDs will stay on for the remaining 50% of CPU time, as the CPU is doing other stuff and cannot deal with the LEDs. Therefore the last row – and only the last row – will get about 50% more brightness. This is what you see. I had that problem in the beginning as well. That is why I do “<display on> – PWM – <display off>”.

  16. ISO-B says:

    I bought 10k resistor. Where I should put it?

    PS: I hate CAPTCHA, because I forget to fill it everytime. So I need to write my comments twice. :D

  17. robert says:

    1.) Schematic
    2.) connect all OE (or G) pins together
    3.) connect the whole net to PB1, so that “#define __display_enable 9” makes sense !
    4.) connect the 10k resistor from PB1 to +5V

    PS: I hate SPAM –> CAPTCHA ;-)

  18. ISO-B says:

    So I need to separate OE and GROUND pins. After that connect OE pins to arduino pin 9. And but 10k resistor between arduino pin 9 and +5V. Am I right?

  19. ISO-B says:

    Thanks man now it works like it should be. I will but video when I finnish rest of table.

  20. y says:

    Hi, i’m trying to build 8×8 RGB Matrix — 2nd arduino project
    and i would put a microphone in for make that leds go with music..
    You know how?

  21. robert says:

    You will definitely need an amplifier to make it work properly.

    Something like this might work:


  22. Andy says:

    Hi – I put together the circuit on a breadboard using a regular Arduino Duemilanove 328, connected to PC via USB. I get the basic code to run ok, but I can’t get the perl script to connectfor the show_ppm version. I installed ActivePerl in order to run perl on the pc, I am not sure if the problem is with that? (Perl definitely runs ok). I get errors about the -T in the first line of your code, and if I remove that, then I get “Can’t locate Device/serialPort.pm in @INC. Is it possible to use this code without your custom board? I am really keen to make some animations for the LED matrix so any help with getting this working would be much apprieciated!! Thanks.

  23. Andy says:

    Hi – thanks a lot, it is starting to work, but now I get errors “Use of uninitialized value in division (/) at C:\Perl\eg\show_ppm.pl line 65” (also for lines 66 & 67) . I only count 25 x3 of these errors (ie for each line 25 x) but maybe it went through whole image matrix but there is ony space left on screen for these…

    I hope I put the image file name in correct place:

    open(FILE, “cross.ppm”); # open .ppm image
    my @image = ; # load the data in an array

    Thanks again!

  24. robert says:


    The version I posted should be called like so: “perl show_ppm.pl image.ppm”. This assumes that show_ppm.pl and image.ppm are in the same folder.

    The complaints about ‘uninitialized division’ suggests that the image has not been read and the array that should hold the data is empty.

  25. Andy says:

    It still gives the same error ;-( !

  26. Andy says:

    Hi – everything is in right folder, and perl is working ok. I called the script the way you said but it doesn’t help. It does seem to be something about the image not being read properly, but why?

  27. Andy says:

    Aha – sorry, it appears the fault is in my .ppm file! I tried it now with your original files and it works ;-) Now I try to a) get the image to stay longer (you have it fading away, yes?) b) get the animations working…..
    Thanks for your help getting the perl & serial port working!

  28. Andy says:

    I’ve now installed Gimp, but I still get problems saving a .ppm that will work. I’ve saved as “raw”, 8×8 pixels, RGB. Is it a pc thing..?
    Saw that the fading is in the arduino code – but I don’t see how to make the animation run….the perl script is the same in both your versions as far as I can see? I’d (ideally) like to run animations on the matrix standalone (not connected to the pc) – is this possible with your code and setup?
    Thanks again!

  29. robert says:

    You can adjust the fadeout timeout in show_image.pde here:

    #define __fade_out_display 2000 // in milliseconds

    To run stand alone animations, the loop function of the arduino code should look like this:

    void loop(void) {
    /* this would show the images stored in the above PROGMEM arrays
    byte counter;
    for (counter=0; counter<10; counter++) {

    There's already an animation stored in the code, consisting of 10 frames.

    To convert your own .ppm images, use 'convert_ppm_to_array.pl' on the image. It will spit out c-code, which you can put into the arduino code in the same manner as shown in the little demo.

    The images must be named like 'frame1.ppm' etc, so the converter knows how to number the arrays for the c-code. Be sure to also adjust

    const char * frames[] = {frame1,frame2,frame3,frame4};


    I create .ppm images using GIMP like this:

    Create a new image with 8x8 pixels, draw draw draw, save as e.g. "frame5.ppm", chose option: RAW.

    BTW, I hope you use the code I've published on my gitweb site. Anything else should be pretty much obsolete.

  30. Andy says:

    Hi – thanks for this, I’ll try it again later, I’m working today so don’t have arduino with me ;-} Not sure what the problem with the .ppm’s I am making is, as I have created them exactly as you say. Will keep trying! Thanks anyway for your excellent project!

  31. Andy says:

    Hi! Yes, things are looking up, thank you!
    – I am able to create own images in Gimp and process them with the perl script to get the code to add to the arduino code.
    – I’ve currently got 15 frames, do you know what is the limit, or does it depend on complexity of the images?
    – My matrix is not displaying MY images correctly pixel for pixel except for the very simple ones. In your code with the random colour generator I get a huge scale of colours so it is definitely capable of displaying across the colour spectrum, but it appears that the convert_ppm_to_array.pl script is maybe limiting the colours, or else the arduino code is not displaying the colours correctly. Any suggestions on that?
    Thanks again!

  32. robert says:

    Each image takes 193 bytes of FLASH memory + 2 bytes of RAM I think (for the pointer array). The latter could be put into FLASH as well, but that gets a bit too complicated for my simple mind. If you read the PROGMEM tutorial on avrfreaks.net you can fix it yourself :-)

    So an ATmega328 should be able to hold way over 100 images, depending on how much other code you put in there. Image complexity doesn’t matter.

    Regarding colors, the standard code runs a color depth of 5bits per color, which totals in 32768 shades. However, many of these will look the same to the human eye. The converter script has a variable called $color_scaler, which is set to 8. 24bit images allow for 255 shaded per color. That gets divided by 8, which gives about 32.

    Strongly contrasting colors work best.

  33. Andy says:

    Wow, ok, that’s great – I _should_ be able to do something with that! ;-) (BTW if you have a simple mind, I have sawdust!!). Regarding the colours, yes it could be that my eyes just don’t catch the colours, I was just looking at it in the dark and of course colours are much stronger, and yes, contrasting colours work best. I’ll keep trying out some different types of image to see what works best. I’ll put some video up once I have something worth you seeing. Great to have solved all the basic bugs now, thanks!

  34. Andy says:

    OK, that’s great to know – I’ll experiment with different types of image and see what works best. Great that now all the basics are working ;-) I’ll let you know how I get on in a few days. Thanks again for you help with this!

  35. robert says:


    Seems like one of your last comments was caught by the spam filter. I didn’t check for a while, but now it’s online. Strange that I didn’t get an email from my blog…

  36. Diego Rodriguez says:

    GOT IT ALL WORKING, YOU ROCK MAN! I still dont understand all your code with interrupts and hardware timers but i’m working on it. But still, YOU ROCK!

    • robert says:

      Glad you’ve got it working (whew) ;-)

      The interrupt part isn’t that difficult at all once you’ve wrapped your head around it. I guess mostly all the register names are confusing at first, what they do and so forth. Having a look at the datasheet for ATmega168/328 should answer most questions regarding that.

      You can play with all numbers in there. Assuming nothing else is plugged in, the worst that can happen is erratic behaviour or a blank screen ;-)

  37. Alexey says:

    Hi Robert,
    first, thanks for sharing the info.

    In my project I use an RGB common anode 8×8 matrix + 3xMAX7219 drivers. It works just fine without dimming distinct LEDs, so I can get 7 different colors (R, G, B, RG, RB, etc.). PWM for single LEDs is the next step now, and that is where I came accross your method to dimm LEDs (using the cycle and related stuff).

    One question regarding the timer you are using:
    TCCR1B &= ~ ( (1<<CS11) );
    TCCR1B |= ( (1<<CS12) | (1<<CS10) );
    It seems you triger the interrupt ca. every 4.2 seconds, right? Isn't too rare? I do not have exactly the same implementation so I cannot test it in reality.

    In my opinion the interrupt has to be called every single moment so a person doesn't see any flickers. According to that I set the timer to be:
    TCCR1B |= (1<<CS10);
    But the problem is that the interrupt's internal code takes too long to update the LEDs so it starts to flicker.

    So the first question is above and the second is whether you was trying to use MAX7219 drivers (whether you have any success on it)?

    thanks in advance

    • robert says:

      Timer1 is clocked at 16MHz/1024 = 15.6kHz !

      And the interrupt timing is not constant when using binary weighted method. I can’t say much more about that, as I have absolutely no clue which version of the code you looked at.

      I have used these max chips before, but for 7-segment displays. I have never thought about using them for a matrix, because they are expensive as hell.

Comments are closed.