Introduction: Yet Another Daft Punk Coffee Table (5x5 LED Matrix)
The code I used in the Arduino sketch demonstrates direct port manipulation, which is a fast and powerful alternative to digitalWrite().
Update 3/10/2011 : Finished adding the Music Synchronization section of this Instructable.
Update 3/20/2011: I decided to build Lampduino instead of the coffee table.
Note that the flickering in the video below was caused by my video camera. When viewed with the naked eye, no scanning is perceived, and the lights do not flicker.
Music Sync Mode:
Arvid "Try (fenomenon remix)"
Syncing to Daft Punk's "Around the World:
Free-running mode (without music synchronization):
Step 1: Acquire the Parts
25 LED's - I used warm white 5mm LED's purchased on eBay
5 resistors - I used 100 ohm, but the exact value will depend on what LED's you use. I will explain how to calculate in the Wiring step.
NOTE: You may need also need 10 diodes and different resistors, especially if you are using white LED's... please the Troubleshooting section in step 3 for details.
Arduino - I used a Duemilanove clone - in Step 7, I switch to a much cheaper alternative.
wire
white foam board
.005" thick matte drafting film - I bought a sheet from a local artist's supply store. The smallest sheet they had was way more than I needed - 24x36"
If you want to implement music synchronization capabilities, you will also need some more parts, as discussed in the Music Synchronization step.
Tools Needed
hot glue gun
mat knife
soldering iron
solder
pliers/wire cutters
The LED's I used were point source water clear. If you don't want to deal with hotspots in your display, diffused would be better. I had to put blobs of hot glue on mine to diffuse them. The photo below only shows bits of the foam board, because I forgot to photograph the parts before I started. The piece I used was about 16x20" to start with.
Step 2: Build the Display
To make the grid, cut 8 identical 2x10" pieces. Then cut 4 equally spaced slits into each one. The slits should be 1" deep, the same width as the foam board. Interlock the pieces to create the 5x5 grid.
Next, cut a 10x10" piece of foam for the back. Hot glue it to the 5x5 grid.
Cut 2 more 2x10" pieces to form the sides, and glue them on.
Finally, cut the 2 (10 3/8)x2" pieces for the top and bottom. My foam was 3/16" thick, so I had to add 3/8" to the length to make them long enough to cover the sides. Hot glue them on.
Note that in the photo below which includes the top & bottom & sides, the LED's are already installed and wired up. This is because I didn't attach the top/bottom/sides until I was in the testing phase, but it's easier to deal with if you glue them on earlier in the process.
Step 3: Wire It Up
it as a crossbar.
While one can use any of the I/O pins on the Arduino, I chose the pins specifically to allow me to use very compact code to turn on the columns. This will be explained in detail later.
To drive our 5x5 LED matrix, we directly drive the LED's using 10 digital I/O pins on the Arduino.
The anodes are connected to pins. Some existing designs, such as the one in the Arduino Playground don't bother to use current limiting resistors. This is not a good design practice, and can result in burned out LED's, or worse yet, a burned out Arduino. Each I/O pin on the Arduino can source or sink up to 40mA of current. The LED's which I used have the following electrical characteristics:
Forward Voltage = 3.2 ~ 3.4V
Max Continuous Forward Current = 20mA
So if we want to drive the LED's for maximum brightness, we need to target 20mA of current.
To calculate the proper resistor value, we use Ohm's Law:
R = (Vcc - Vf) / If
where
R = resistor value in ohms
Vcc = supply voltage = 5V for the Arduino Duemilanove
Vf = LED forward voltage. I used the average, 3.3V
If = LED current in amperes = .020A
Plugging in the values, we get R = (5 - 3.3) / .02 = 85 ohms. The nearest available standard resistor value is 100 ohms. Always round up instead of down, because if you round down, you will exceed the maximum allowable current.
Notice that we only use 5 resistors. We don't have to put one at each LED, because we will only be driving one row, a maximum of 5 LED's at a time. I said above that each I/O pin can drive 40mA of continuous current, so why can't we drive the whole LED array at once? It's because another constraint is that the total drive current summed up across all the pins can't exceed 200mA. If we turn on all 25 LED's at once, then 25*20mA = 500mA flow, which is way over spec.
So maybe we can turn on 1 row at a time, and scan the rows, like the way a CRT works? If we turn on a whole row of LED's at once, the current is 20mA * 5 = 100mA. This, at first, appears to be OK, because each column (anode) pin is only sourcing 20mA, and we're below the Atmega368P's total 200mA current limit. However, upon more analysis, we can't even drive 5 LED's at once. Why? Because the cathodes of all 5 LED's in a row are connected together into a single I/O pin, and we're not allowed to sink more than 40mA per pin. Therefore, we will write our software so that no more than 2 LED's are turned on at a time, so the row (cathode) pins will sink a maximum of 40mA each. Now, even though we're at the allowable continuous current limit, it's generally not good practice to run a device at its maximum limits. However, since we're going to pulse each LED briefly, and let persistence of vision create the illusion that they're all on at once, it's OK.
Note: I tried running mine w/ 5 LED's lit per row for several hours, and it worked fine, but it's always best to design your circuits within specifications, to ensure long term reliability.
The circuit diagram is below. To summarize the connections:
LED Columns (anodes)
col 0 connects to digital pin 12 (via a 100 ohm resistor)
col 1 connects to digital pin 11 (via a 100 ohm resistor)
col 2 connects to digital pin 10 (via a 100 ohm resistor)
col 3 connects to digital pin 9 (via a 100 ohm resistor)
col 4 connects to digital pin 8 (via a 100 ohm resistor)
LED Rows (cathodes)
row 0 connects to digital pin 7
row 1 connects to digital pin 6
row 2 connects to digital pin 5
row 3 connects to digital pin 4
row 4 connects to digital pin 3
I used simple point to point wiring, fastened with hot melt glue to the back of the display. I know it's messy looking, but it won't be seen, anyway.
Troubleshooting
If your matrix doesn't function properly, first, you should double check your wiring. The sketch also has a testing mode, which cycles through the LED's one by one slowly enough that you can see it. You can enable it by uncommenting the following line in the sketch:
//#define TESTMODE // continuously sequence thru the LED's
by removing the leading //.
One potential mistake is accidentally swapping the columns and rows. If your matrix looks like this video http://www.youtube.com/watch?v=JpLgLbWMrWo in TESTMODE, then you've made this error, and need to swap the row and column connections to your Arduino. Thanks to Instructables user 303_addict for posting the video.
If your wiring is correct, and you are getting more than one LED at a time lighting up, you might be one of the unlucky ones who has LED's that have a high leakage current when reverse biased. White LED's are particulary susceptible to this problem. If this is the case, you will need to add series blocking diodes on the inputs to all the columns, as well as on all the row outputs. So you will need 10 diodes. Any small signal diode will work, such as 1N4001, 1N914, 1N4148, etc. You will also need to adjust the resistor values, because two series diodes will add ~1.4V voltage drop. So in my equation above, use 3.6 for VCC. For my 3.3V LED's you end up with R = (5-1.4-3.3V)/20mA = 15 ohms. I didn't have any 15 ohm resistors handy, so I substituted 10 ohms instead, and using an ammeter, measured 19.5mA .. still within spec. See the last attached image.
Step 4: Diffusing the LED's
At first, I used white copier paper to cover the LED's. It darkened the display to bit too much, and looked grainy. Then I went to an art store, and found .005" thick matte drafting film. It looks a lot better. I cut a 10 3/8 x 10 3/8" piece and glued it to the front.
Step 5: Code
The display code is interrupt driven, and uses the Timer1 library . Timer1 isn't bundled in the Arduino software installation. Therefore, to install it, you must download TimerOne.zip , and unzip its contents into your sketchbook/libraries/TimerOne.
A Frame is a screenful of data. A very straightforward and common way of storing the data would be to use a byte for each LED. This would use 25 bytes per frame. To save memory, we define it as an array of 5 bytes. Each byte represents an entire row of our display. Since a byte is composed of 8 bits, and we only have 5 LED's per row, we only use the bottom 5 bits, and the top 3 bits are ignored. There are 5 bytes because we have 5 rows. We could save another byte by packing 25 bits into 4 bytes, but that complicates the code unnecessarily, and makes it impossible to graphically view the frame declarations in frames.h.
#define DIM 5 // x/y dimension - 5x5 matrix
typedef byte Frame[DIM];
The column LED pins are defined in the cols array.
int cols[DIM] = {12,11,10,9,8};
The row LED pins are defined in the rows array.
int rows[DIM] = {7,6,5,4,3};
To turn on a particular LED, we set its cols pin HIGH and its rows pin LOW. This causes current to flow through the selected LED. The most straightforward way to do this would be to use the digitalWrite() function on each LED in sequence. So to turn on the top left LED, we could use
digitalWrite(cols[0],HIGH);
digitalWrite(rows[0],LOW);
and do that for each LED. However, that's not very efficient. Instead we use direct port manipulation to turn on the columns. The PORTB register corresponds to digital pins 8 to 13.
Note the ordering which we used in our declaration of the cols array above: 12,11,10,9,8. This way, bit0 of PORTB corresponds to the far right column, bit1 corresponds to the next column to the left, until bit4, which corresponds to the far left column. If we set a particular bit to 1, it's the same as calling digitalWrite() with HIGH, and setting a bit to 0 is equivalent to calling digitalWrite() with LOW. Therefore, we could theoretically replace calling digitalWrite() 5 times to set all 5 columns with one write to PORTB. But since we are allowed to turn on at most only 2 LED's at a time, we have to loop 3 times for each column, scanning 2 bits at a time.
// Interrupt routine
// each time display() is called, it turns off the previous row
// and turns on the next row
byte bitMask = B00000011;
void display() {
digitalWrite(rows[row], HIGH); // Turn whole previous row off
if (bitMask == B00010000) {
bitMask = B00000011; // light the right 2 columns (pins 9,8)
// increment row and wrap if necessary
if (++row == DIM) {
row = 0;
}
}
else if (bitMask == B00000011) {
bitMask = B00001100; // light the middle 2 columns (pins 11,10)
}
else { // bitMaskIdx == B00001100
bitMask = B00010000; // light the leftmost column (pin 12)
}
// direct port manipulation.
// PORTB is a pseudo variable for digital pins 8 to 13 The two high bits (6 & 7) map to the crystal pins and are not usable
// the bottom 5 bits are our columns. We don't want to change the other bits,
// so we first mask the bits we ignore, and then set the bits we want to light
PORTB &= B11100000;
PORTB |= curFrame[row] & bitMask;
digitalWrite(rows[row], LOW); // turn on the row
}
Note that our comment above says that display() is an interrupt routine. This is because instead of handling the scanning in our loop() function, we call it in the background via the Timer1 library.
// interrupt interval in uSec
// this determines how long to keep each row turned on
// so if we have 5 rows, we redraw the whole screen
// once every 5 rows * 3 cycles per row * 1000 usec = .015 sec -> 66.67Hz
// if you perceive flickering you can decrease to 500 for a 133.33Hz rate
Timer1.initialize(1000); // fire the interrupt every 1000 microseconds
Timer1.attachInterrupt(display); // call display() at every interrupt.
The main loop simply steps through all of our animation frames.
void loop() {
// increment the frame index and wrap if necessary
if (++curFrameIdx == FRAMECNT) {
curFrameIdx = 0;
}
// select frame for display
setFrame(curFrameIdx);
delay(400); // wait time between frames in ms - reduce this value to speed up the animation, increase it to slow it down
}
The entire sketch is attached below.
Attachments
Step 6: Animation Design
#define FRAMECNT 117
specifies how many frames are contained in your animation.
A frame is just a full screen of data. The default animation that I put in it contains 117 frames.
You can specify frames by just typing them in one by one. For instance, a capital 'Z' could look like:
{ B00011111,
B00000010,
B00000100,
B00001000,
B00011111}
The bottom 5 bits of each byte correspond to the pixels of each row. Any bit which is set to 1 is lit, and 0 is off. The top 3 bits are ignored. This manual process can get rather cumbersome if you want to make complex animations
Ian was kind enough to share the code for his Internet Graffiti Wall . (Thanks, Ian!) I hacked it up to generate code for frames.h. To create your own animations, simply load daftpunkanimationbuilder.html into any java-enabled web browser. Follow the instructions on the screen to create your own animation. When you're satisfied with it, click the Generate button, and copy the code from the text box, and paste it into frames.h, replacing its entire contents.
If you want to save your animation for editing later, check native fmt before clicking Generate, and then copy and paste the contents of the text box into a text file. You can later load your saved animation back into the animation builder by pasting it into the Restore from sequence code text box, and then clicking Add before current frame.
Please send me any cool animations which you are particularly proud of, and I'll add them to this Instructable for others to download.
Attachments
Step 7: Music Synchronization
Circuit Modifications
To allow our LED matrix to synchronize the animations to music, we need to interface it to an audio source. The ATmega328 MCU conveniently contains A/D converters which we can use for this purpose. Although there are various designs for color organs, which work with various frequency bands, I prefer syncing with the bass track.
We could use an analog pin to sample music, and then do frequency analysis on the Arduino, but this would introduce a delay, and still require us to build an antialiasing filter. Since we need to build a filter, we might as well keep things simple. I built an analog low pass filter, fed its output into an analog input in, and then simply tested the amplitude against a threshold. I started with a the simplest possible filter, a first order low pass RC filter. Much to my surprise, it worked fairly well. To design the filter, I first needed to figure out what cutoff frequency to use. The cutoff frequency, Fc, of a filter is the frequency where the power is cut in half. After playing around with graphic equalizers in Winamp while playing music with thumpy bass tracks, I settled on Fc = 100Hz. I didn't have the proper resistor handy, so I used the closest value I had, which resulted in Fc = 133Hz. Here is the circuit:
Parts Needed:
(1) 10uF electrolytic capacitor
(1) 120 ohm resistor
(1) 1/8" TRS jack
The output connects to analog pin 0 on the Arduino. Connect its input to an audio source, such as the headphone jack of a stereo, or an MP3 player. DO NOT CONNECT IT TO SPEAKER LEVEL OUTPUTS FROM AN AMPLIFIER UNLESS YOU'RE SURE IT OUTPUTS <= 5V. Any audio source designed to drive headphones should be safe. Since I use the headphone jack on a stereo, I can still hear the music even when the circuit is plugged in. If you're using something with only one output, such as an MP3 player, you can hook up two TRS jacks in parallel, and then feed the audio from the 2nd jack to a set of powered speakers or headphones.
Note that the circuit above only monitors one channel of audio, which works with most songs, since the bass is generally approximately equal amplitude in both channels. It works OK, but I found that in order to get a high enough input for the Arduino to respond nicely, I have to turn the volume up a bit too high for my tastes. Therefore, I also built the alternate circuit below:
Parts needed:
(1) 10uF electrolytic capacitor
(1) 120 ohm resistor
(3) 10K ohm resistor
(1) 100K ohm resistor
(1) 1/8" stereo TRS jack
(1) TLC272CP op amp IC
UPDATE: Sorry, the schematic diagram above is wrong. There should be a third 10K resistor, R5, which connects between pin 2 (- input of opamp) and GND. The gain of the amp is R4 / R5.
You can substitute any suitable op amp. I used the TLC272CP because that happens to be what I had on hand, and it has the particularly nice characteristic that it doesn't mind running on a single-ended power supply of only 5V, so I could just hook it up to the Arduino's power. The circuit above mixes the left and right audio channels, and has a gain of 10. Just hook it up to your stereo audio source, and connect the output to analog pin 0, as before. The 5V and GND also connect to your Arduino.
If you prefer to use a microphone rather than direct wiring, pepehdes has posted an alternate circuit using an electret mic in Step 1's comments, which he says works well. Note that it is an all-pass design so it will trigger on all frequencies, not just bass. I didn't use a microphone, because my design tracks the bass which might not be picked up very well via small speakers and a microphone.
Sketch Revisions
Next, we need to modify the sketch to read the audio input, and trigger the next animation frame when a threshold is exceeded. The new sketch, daftPunkSyncV2.zip, is attached below. You can switch the sketch between music sync and free-running by modifying
// =0= use timer
int musicSync = 1;
You will need to manually tune the threshold variable:
// trigger next frame when music amplitude > threshold
// increase threshold to decrease sensitivity
// decrease threshold to increase sensitivity
int threshold = 5;
The next frame will be triggered when the input read from analog pin 0 exceeds threshold. If you're running the sketch on a real arduino that has an LED on digital pin 13, it is helpful to uncomment the following line:
#define BLINKY // blink LED on pin 13
by removing the leading // characters. This will cause the LED to blink when threshold is exceeded. I have it turned off in the sketch by default, because I'm using my tiny breadboard instead of an Arduino now, and it doesn't have an LED. Finally, you may want to experiment a bit with minBeatInterval:
// debouncing - filter out consecutive triggers that are too close
// together to slow down the animation. decrease this value
// to make it more sensitive to shorter beat intervals
int minBeatInterval = 1000;
Note that this variable is unit-less. It simply defines how many consecutive loops after the input exceeds our threshold before the sketch is allowed to trigger the next frame. I found that in many songs, bass beats are actually composed of several very closely spaced peaks. If we don't filter them out, then the display will advance several frames for each beat. You might actually like this effect, because it makes the display more lively. Play around with it, and see what setting you prefer.
Attachments
Step 8: Costing Down
To program this board using the Arduino IDE, plug your programmer into the ICSP header. The USBtinyISP supplies 5V power, so not other connection is needed to program and test it. You will need to make some minor tweaks to your Arduino IDE in order to make it play nicely with the USBtinyISP. I have posted detailed instructions on my blog: How to Use the Arduino IDE with an External Programmer.
The next complication is that you need to figure out which pins on your ATmega328P MCU map to the digital pins on the Arduino. The pin mappings can be found on the Arduino website: ATmega8-Arduino Pin Mapping
Someone also made a PDF which you can use to print labels to attach directly to your ATmega328P here: Arduino Breadboard Labels . Here is the specific pin mapping we need for our project:
Arduino Digital Pin -> ATmega328P pin:
digital pin 12 -> ATmega pin 18
digital pin 11 -> ATmega pin 17
digital pin 10 -> ATmega pin 16
digital pin 9 -> ATmega pin 15
digital pin 8 -> ATmega pin 14
digital pin 7 -> ATmega pin 13
digital pin 6 -> ATmega pin 12
digital pin 5 -> ATmega pin 11
digital pin 4 -> ATmega pin 6
digital pin 3 ->ATmega pin 5
analog pin 0->ATmega pin 23
So take the pin that was connected to digital pin 12 on the Arduino, and connect it to pin 18 on the breadboard, and so on.
A cheap way to power the project once you're done building it is to buy a cheap USB wall wart, such as is used to charge various devices. You can get them on eBay, or if you're lucky, your local 99 cent store may carry them. Just cut off the USB connector, and solder the power wires to VCC (pin 7) and GND (pin 8) on the ATmega (MAKE SURE TO CHECK THE POLARITY WITH A METER FIRST. VCC is the positive lead, GND is the negative one). If you solder a USB power supply to your board, make sure to remove the PWR jumper from your USBtinyISP, or unplug your charger from the wall before programming it.