The LED Matrix Panel, or LMP, for the Arduino Microcomputer, Continued.




Last time, in part 1 we introduced you to the LMP, a simple, lost-cost part that connects directly to the Arduino. This time, we will continue with a simple program I call the Musicator.

It is a simple Spectrum Analyser, completely done in software, which reads the output from an earphone plug (on A01), and generates a 5-channel pattern on the LMP, based on the loudness and frequency of the signal.

Here is a sample video:

All you need, beside the LMP and, of course, the Arduino, is a cable with a jack that fits into an earphone socket. It doesn;t matter whether you use the Left or Right channel.

Step 1: The Sketch (LMP_Musicator01)

  • VUmeter / Light organ (Full db presentation)
  • Horizontal presentation - center-out linear display
  • (c) Copyright 2009
  • All Rights Reserved
  • LED Matrix row:1-5; col:1-6
  • Map PORTB == D8:D12 - pin[row+7] : +v
  • PORTD == D2:D7 - pin[8-col] ; Gnd
  • Our output: col::D2:D7 -ve (LOW) while row::D8:D13 +ve

char msg[] = {
0x11, 0x0a, 0x0e, 0x0e, 0x0a, 0x11, // Starburst
0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
0x11, 0x19, 0x1e, 0x00, 0x1f, 0x10, // Q U
0x1f, 0x00, 0x1e, 0x05, 0x1e, 0x00, // A
0x1f, 0x06, 0x0c, 0x1f, 0x00, 0x03, // N T
0x1f, 0x03, 0x00, 0x12, 0x15, 0x09, // S
0x00, 0x1f, 0x10, 0x1f, 0x00, 0x1f, // U F
0x05, 0x05, 0x00, 0x1f, 0x05, 0x05, // F
0,0,0,0,0,0 }; //Plus an overrun buffer

// bit pattern, 1 byte per column. Top = lsb

char display[6], hdisp[6] ;

int w10kbuf[2], w1kbuf[20], w100buf[100]; // Assume all zeroed (:P)
int w10kidx, w1kidx, w100idx;
// int w10kmax, w5kmax, w1kmax, w500max, w100max;
long w, w10ksum=0, w1ksum=0, w100sum=0;
int www, w10k, w1k, w100, meter[5] ;

int scale=3; // log scale (start at 23)
int recal=0; //AGC rate
int inPin=1 ; // analogRead on A01

void setup() // run once, when the sketch starts
for (int ledPin=2; ledPin<=12; ledPin++) {
pinMode(ledPin, OUTPUT); // sets the digital pin as output
digitalWrite(ledPin,(ledPin<=7)) ; //Disable all cols
lmpWrite(msg,0,15); //Cycle delay is 1/10 secs
// (it's really to initialize the counters)
analogReference(INTERNAL) ; // Do not use DEFAULT or EXTERNAL!
int i= digitalRead(inPin); // remove old data

void loop() // run over and over again
w100 = (w100sum / 23) >> scale; // A cheap filter @ app 100Hz
w1k = abs((w1ksum / 3)-w100) >> scale; // 1kHz
w10k = (www+w10ksum) >> scale; // 10kHz
w= (w100 + w1k + w10k) ; // Total power (VU function)

if (w<13) {
if (recal>=15) { //350mS counts before recalibration of meter
scale-= (scale>3);
} }// increase res only if less than 1/2 scale

// if ((w100>33) || (w1k>30) || (w10k>33)) { // log2
if ((w100+w1k+w10k)>83) { // Peaked: make it less sensitive
w100= w100>>1;
w1k = w1k >>1;
w10k = w10k >>1;
meter[0]= min(w100>>1,31);
meter[1]= min(abs(w100+w1k-w10k)>>2,31);
meter[2]= min(w1k>>2,31);
meter[3]= min(abs(w1k+w10k-w100)>>2,31);
meter[4]= min((w10k*5)>>3,31);

for (int col=0;col<6;col++) {
int c=0;
for (int mm=0; mm<=4; mm++) {
c= c<<1;
c+= (meter[mm] > col);
hdisp[col]= c;
display[0]= hdisp[4];
display[1]= hdisp[2];
display[2]= hdisp[0];
display[3]= hdisp[0];
display[4]= hdisp[2];
display[5]= hdisp[4];


display[0]= hdisp[5];
display[1]= hdisp[3];
display[2]= hdisp[1];
display[3]= hdisp[1];
display[4]= hdisp[3];
display[5]= hdisp[5];

/* Vertical display routine
//fillVBar (display,5,scale+1);
int i=0;
int s=scale-1;
for (int ii=0; ii<5; ii++) {
i= i | (s & 1);
i= i<<1;
s= s>>1;
display[5]= i;


void fillVBar(char disp[],int o, int val) {
int j= B100000; // digit pattern
val= constrain(val,2,127) ;
while (val>2) {
j|= (j>>1) ;
val= val>>1 ;

void lmpWrite(char disp[],int stchar,int steps)
/* Usage: Loads bit pattern in disp[], starting at stchar,
  • 1 byte per column, left-to-right, top-to-bottom
  • Map PORTB == D8:D12 - pin[row+7] : +v
  • PORTD == D2:D7 - pin[8-col] ; Gnd
int col, row, cval, lit=0, ww=0;
long delayStart=millis() + 90*(constrain(steps,1,100)) ;
// Display / loop time
www= 0 ;
do {
for (col=0; col<6; col++) {
cval = disp[col+stchar];
for (row=1; row<=5; row++) { //only 5 lower bits
if (cval & 1) { // light this LED
if (lit==0)
digitalWrite(7-col,LOW); //col needs activating
lit++ ; // in case we need to know how many LEDs are lit
// Cycle stealing here, to read VU input
ww=max(ww,analogRead(inPin)) ; //Raw volume data (rectified)
www=max(ww,www) ; //Peak retect
delayMicroseconds(80); //This makes sure the panel is
// lit the same period of time.
digitalWrite(row+7,LOW); //Turn LED off
cval = cval>>1 ; // check the next bit
digitalWrite(7-col,HIGH); // turn col OFF
w10ksum+= www-w10kbuf[w10kidx]; // This is our 'capacitor'
w10kidx= ((w10kidx<1)); // 0 or 1
//if (w10kidx>3) w10kidx=0;
//w10ksum=ww ; // Increase the high-end response

w1ksum+= ww-w1kbuf[w1kidx];
if (w1kidx>19) w1kidx=0;
//w1kidx= ((w1kidx<19) && (w1kidx++)); // 0 - 19

w100sum+= ww-w100buf[w100idx];
if (w100idx>99) w100idx=0;
//w100idx= ((w100idx<1) && (w100idx++)); // 0 - 199

lit = 0;
while (delayStart > millis()) ;

Step 2: Program Notes

The major consideration for this program is time.

There are three primary tasks involved in this program:

1) Get the input voltage. The 16MHz Arduino takes 100uS to read the input, which means that even if it does nothing else, the maximum frequency it can detect reliably is 3kHz, so we use something called signal aliasing, beginning with taking a number of readings as quickly as possible (5 in our program) and summing them in separate locations. By taking averages of different ranges of samples, we can approximate the low and mid range frequencies, the difference is assumed to be the high reading. This is by no means exact science but here, we just need rough proportions, so exactness is not critical.

2) We will be doing statistical analysis of a large set of numbers, so everything is reduced to integers to save storage space and to speed up calculations. There will be no conversion from the basic 10-bit input of the Analog Input port.

3) Translation and display on the LMP. Each LED is switched on for about the time it takes to read the Analog pin, and each 'frame' (the 6 x 5 matrix) is shown twice, so that we can display 1.5db steps. This takes about 100mS. The time here is critical - any slower, the eye will start to see flicker, too fast, the 'patterns' will look dim and badly formed.

AGC is incuded, so that the program can work with a wide range of volume and music. A minimum of 150mV is required, but it should be no problem with regular listening levels. The "scale' variable can be lowered for more sensitivity, but will make the program more susceptible to electrical noise - in which casee, bypass the input with a 0.1uF capacitor to ground..

To maximize the 'dynamic' nature of the music, the most significant 2bits are dropped, and the remaining 4 are 'stretched' to fit the 6 horizontal LEDs.

Step 3: What's Next?

Send me your suggestions...

But here's a sneak peek at the next generation of the LMP... COLOR!

Electrically identical to (and interchangeable with) the mono-LMP, except for the addition of current limiting to allow us to use any color LED and still have the same current run through it.

Here's a quick look:

Another video of a soft ballad to show the Musicator's dynamic response:



    • Remix Contest

      Remix Contest
    • Tape Contest

      Tape Contest
    • Paper Contest

      Paper Contest

    26 Discussions


    3 years ago on Introduction

    Hi, I like the idea, but could you add a minimal schemtics? Even hand-drawn or ASCII-art is fine. I'm not very sure about how to wire the input (Direct, over an capacitor, over an voltage-divider+capacitor, ...). Thanks a lot


    7 years ago on Introduction

    i have build the lmp and constructed as in the instruc, but now all the musicator does is keep the leds high when i input an audio signal :(

    1 reply

    Reply 7 years ago on Introduction

    If the lmp works with the other sketches then make sure the input is tied to A01 through an isolating cap (0.01uF or so). Some MP3 players use direct (bridge) output and there's a DC component which will 'pin' the circuit. It's also possible (but not likely) the input signal itself is too high - all you need is one channel from an earphone output.


    thanx a lot for your quick response...It's my first project so i'm a little bit...nervous. Can we have more details about building the board on the project 1.... (solder etc....)

    Thanx Ben


    Reply 8 years ago on Introduction

    A01 is a General Purpose input pin in the Arduino - check the datasheet of your model for more details.

    I connected the White wire from a salvaged earphone plug to A01 and the black to Ground.


    8 years ago on Introduction

    i want to start the project but I don't understand where you put your earphone socket !!!!?????


    8 years ago on Introduction

    To fruitkid101 and others who'd been waiting for the scrolling write-up.

    Sorry, but I'm on a really tight schedule at work and have not had time to get to it, but the code above has EVERYTHING you need, specifically the lmpwrite routine which does the actual scrolling message. Put your pattern in msg[] and it'll work - there is nothing else you need. The code does not depend on any external libraries or code (apart from the Arduino's built-in binary loader). 

    I will get to a more detailed Instrutable when I get the chance.



    9 years ago on Introduction

    Is it possible to expand the amount of leds and would you have to change the code to make the different patterns? If it is how would you?

    3 replies

    Reply 9 years ago on Introduction

    The maximum number of controlled LEDs in this project is limited by the number of ports on the microprocessor, as I mentioned at the top. Beyond that, we will need to resort to multiplexing, which will further complicate the circuit and/or code.

    Having said that, the software is written to have each vertical LED controlled by a single bit of a word, so we have room to go to 16 LEDs vertically. The number of horizontal LEDs is limited by the processing time of the uP so that we can both SEE the LEDs (the eyes need 1/10 of a sec to register each change) and to perceive motion and groups.

    What is the application you have in mind?


    Reply 8 years ago on Introduction

    Ha! I built one for the arduino mega (tons of pins) It's way bigger and can do simple animations, Great!


    Reply 9 years ago on Introduction

    Ok, thanks for the info; i was just wondering if it could be done.


    9 years ago on Introduction

     I have zero experience with arduinos so please excuse my ignorance,

    is it possible to operate this without it being connect to the computer, or does the arduino receive the code from the computer during operation?

    1 reply

    Reply 8 years ago on Introduction

    Yes, you can use it without the computer. When you upload the code, the arduino stores it and will use it whenever powered up. If you want to use the arduino without a computer, you just have to supply 5v and Gnd to it either by making a cable that can do that, or otherwise


    9 years ago on Introduction

    Love the project.  I am actually thinking of trying to do the same thing using a FFT transform.  If it is not too much trouble, could you provide a more commented version of your code.  I do not just want to use it but understand it and adapt it.  Thanks.

    2 replies

    Reply 9 years ago on Introduction

    He's implemented it very cleverly as a series of discrete digital filters, rather than an FFT. Would an FFT work better ? 


    Reply 9 years ago on Introduction

    Not quite sure if an FFT would be easier but the problem with the FFT is the memory involved in the computations.  It would be cool to implement it thought.  Yeah I see the digital filters now.  Thanks.


    9 years ago on Step 1

    Oh my god... it actually works!


    9 years ago on Step 3

    If I paid you to build me one of these, what would it cost?


    9 years ago on Introduction

    Phenomenal project! Software is so important in a project like this - very well executed!