4×4 Arduino controlled LED Cube
We all know that it has been quite a while since we have written any posts on the blog and we apologize to all our readers.
Before telling more about the project and the blog post finally to come I would like to explain the reason for the big delay.
As TKJ Electronics has been evolving quite a bit over the past year we have been provided with a still increasing amount of consultancy work.
This includes everything from PCB Layout, Arduino software development, Robot platform development, ARM processor units and FPGA solutions.
So we have been really busy with all this work lately which unfortunately affected the blog posts. But now we have finally got a new but yet simple project for you:
A 4 by 4 single color LED 3D cube.
You might have seen such a project before but we thought we would like to do the same, get some good pictures of the steps and then make a guide for you.
An LED Cube consists of several LEDs, either single-colored or multi-colored, connected in a grid in a such way that you don’t have to connect each individual LED to an individual I/O pin, but the special grid also reduces the difficulty of the assembly.
In this project we are going to make a 4 by 4 LED cube by combining all the Cathodes (minus) of the LEDs in the same layer (blue) and by combining all the Anodes (plus) of the LEDs in the vertical rows (red).
By toggling fast thru the layer cathodes (blue), enabling only ONE layer at a time, you are able to control all 64 LED with only 20 pins. I know this pin count can be reduced by a numerous of ways, but the grid connections I am going to describe in this post is far the easiest to do and implement.
To be honest with you this project did only require a total of 4 hours for us to complete.
So lets get started!
Step 1 – Guideline platform
First you are going to need a piece of wood as a soldering guideline when making the LED layers.
As you can see in the image we have drilled 16 holes in a 4×4 array. The distance between each hole is 2.5 cm (about 1″) which is just enough for the LED pins to be able to touch eachother when bent, which reduces the need of extra wire.
Start by drilling the holes and then fill up the top row of holes with the LEDs, having the Cathod pins pointing downwards and then pulled to the right.
Then solder all these connections together.
Step 2 – Vertical rows
Now fill LEDs in the 3 holes to the left, in a vertical row, having the Cathodes to the right and then pull upwards to match the other bent cathode row.
Solder this row together and repeat the step for the other 3 vertical rows.
Step 3 – Stabilization wire
Finally add a stabilization wire to the bottom of the 4×4 structure to form a square and connected outer frame.
You have now finished a single layer.
Step 4 – Multi-layers
Repeat Step 1 to 3 to assemble a total of 4 layers. When you have 4 layers you are ready to assemble the cube.
Step 5 – Assembling the layers
Now for the tricky part of the cube – the assembly of the 4 layers.
Start by soldering the 4 corner Anodes together in the two layers. Adjust the soldering-height of each corner to match the same distance as between the LEDs in the layer (2.5cm).
Solder the rest of the Anodes connections. You might have to bend some of the LED pins to make them fit.
Step 6 – Repeat, repeat, repeat
Now it is just a matter of repeating Step 5 by adding a new layer, solder and adjust the corners and the finish up the rest of the connections.
When all layers has been put together you should end up with something like the cube in the image below.
Step 7 – Driving circuitry
After you have soldered all the layers together it is time to prepare the LED cube to be connected and driven by a microcontroller.
I decided to do this by soldering the cube to a PCB Proto board as we are going to add some extra drive circuitry.
The only necessary drive circuitry and some current limiting resistors for all the LED anodes and a transistor, capable of driving enough thru the cathode, even when all the LEDs are lit.
As the circuit is supposed to be driven by a 5V Arduino and the LEDs we have used can be driven with a current up to 60mA when multiplexed, we decided to use a 56 ohm resistor for the LED anodes.
For the cathodes we decided to use a BC337 transistor, capable of driving 1A, which is just enough for driving all the LEDs (960mA).
When putting all this together we ended up having a finished cube-board looking like this.
Step 8 – Controlling the cube
Now the final part is control the cube with a microcontroller. For the ease and rapid development we decided to go with the Arduino MEGA as it had the required pin count (20 output pins) together with the easy programming environment.
The connections from our Cube-board to the Arduino MEGA can be seen in the table below.
- SV1, Pin 1 to 8 <-- Arduino Pin 22 to 29
- SV2, Pin 1 to 4 <-- Arduino Pin 30 to 33
- SV2, Pin 5 <-- Arduino GND
- SV3, Pin 1 to 8 <-- Arduino Pin 39-32
I won’t go much into details with the code. But to summarize we use Timer 2 as an interrupt enabled timer for the multiplexing of the 4 layers.
Everytime the interrupt is run the Cathode pins are toggled, changing the current control layer to the next one. Furthermore the Anode pins are switched on and off to match the current LEDs that has to be set in that specific layer.
In the main loop we are toggling thru 4 different demonstration application – one demonstration with rapid jumping pixels (in z-axis) and three rotating plate demoes (x, y and z axis).
The source code can be found in the box below. OBS. The code is made and tested within Arduino 1.0.1.
#define PrescalerOverflowValue 4
ISR(TIMER2_OVF_vect)
{
if (Prescaler < PrescalerOverflowValue)
Prescaler++;
else {
Prescaler = 0;
Multiplex();
}
}
unsigned char CurrentLED = 1;
unsigned int LEDLayers[4];
void Multiplex(void)
{
switch (CurrentLED)
{
case 0:
digitalWrite(38, LOW);
break;
case 1:
digitalWrite(39, LOW);
break;
case 2:
digitalWrite(40, LOW);
break;
case 3:
digitalWrite(41, LOW);
break;
}
CurrentLED++;
if (CurrentLED > 3)
CurrentLED = 0;
PORTA = (LEDLayers[CurrentLED] & 0xFF);
PORTC = ((LEDLayers[CurrentLED] & 0xFF00) >> 8);
switch (CurrentLED)
{
case 0:
digitalWrite(38, HIGH);
break;
case 1:
digitalWrite(39, HIGH);
break;
case 2:
digitalWrite(40, HIGH);
break;
case 3:
digitalWrite(41, HIGH);
break;
}
}
int JumpingPixels[16][5]; // X, Y, Z, Interval, Time
char JumpingPixelsCount = 0;
void setup(void) {
//Set the pin we want the ISR to toggle for output.
pinMode(38,OUTPUT);
digitalWrite(38, LOW);
pinMode(39,OUTPUT);
digitalWrite(39, LOW);
pinMode(40,OUTPUT);
digitalWrite(40, LOW);
pinMode(41,OUTPUT);
digitalWrite(41, LOW);
DDRA = 0xFF; // Port A as Output
PORTA = 0x00;
DDRC = 0xFF; // Port C as Output
PORTC = 0x00;
//Start up the serial port
Serial.begin(19200);
//Signal the program start
Serial.println("LED Cube Controller");
// Enable Timer 2 interrupt (also used for PWM though)
// This interrupt is divided by a prescaler, and takes care of the multiplexing
TCCR2B = TCCR2B & 0b11111000 | 0x02; // Divisor = 8
TIMSK2 = 1<<toie2;
ClearCube();
}
int globalI;
void loop(void)
{
ClearCube();
Effect_JumpPixelInit(500, 3000);
for (globalI = 0; globalI < 300; globalI++)
Effect_JumpPixel();
for (globalI = 0; globalI < 50; globalI++) {
Effect_TurningX();
delay(100);
}
for (globalI = 0; globalI < 50; globalI++) {
Effect_TurningY();
delay(100);
}
for (globalI = 0; globalI < 50; globalI++) {
Effect_TurningZ();
delay(100);
}
}
unsigned int SetLED(char x, char y, char z, char set_clear)
{
char bitPosition;
unsigned int temp;
if ((y % 2) == 0)
bitPosition = 15 - (((3-y) * 4) + (3-x));
else
bitPosition = 15 - (((3-y+1) * 4) - (3-x+1));
if (set_clear == 0)
LEDLayers[z] &= ~(1 << bitPosition);
else
LEDLayers[z] |= (1 << bitPosition);
}
void ClearCube(void)
{
LEDLayers[0] = 0x0000;
LEDLayers[1] = 0x0000;
LEDLayers[2] = 0x0000;
LEDLayers[3] = 0x0000;
}
void Effect_JumpPixelInit(int timeRange_min, int timeRange_max) {
char x, y, i;
i = 0;
for (y = 0; y < 4; y++) {
for (x = 0; x < 4; x++) {
JumpingPixels[i][0] = x;
JumpingPixels[i][1] = y;
JumpingPixels[i][2] = 0;
randomSeed(analogRead(0)*analogRead(1));
JumpingPixels[i][3] = random(timeRange_min, timeRange_max);
JumpingPixels[i][4] = 0;
i++;
}
}
JumpingPixelsCount = i;
}
void Effect_JumpPixel(void) {
char i;
for (i = 0; i < JumpingPixelsCount; i++)
{
if (JumpingPixels[i][4] <= 50) {
JumpPixel(i);
JumpingPixels[i][4] = JumpingPixels[i][3];
} else {
JumpingPixels[i][4] -= 50;
}
}
delay(50);
}
void JumpPixel(char PixelID) {
signed char dir;
char i;
if (JumpingPixels[PixelID][2] == 0) dir = 1; // increase
else dir = -1; // decrease
for (i = 0; i < 3; i++)
{
SetLED(JumpingPixels[PixelID][0], JumpingPixels[PixelID][1], JumpingPixels[PixelID][2], 0);
JumpingPixels[PixelID][2] += dir;
SetLED(JumpingPixels[PixelID][0], JumpingPixels[PixelID][1], JumpingPixels[PixelID][2], 1);
delay(25);
}
}
const char TurningSteps[6][4][2] = { { {0,0},{1,1},{2,2},{3,3} }, { {1,0},{1,1},{2,2},{2,3} }, { {2,0},{2,1},{1,2},{1,3} }, { {3,0},{2,1},{1,2},{0,3} }, { {3,1},{2,1},{1,2},{0,2} }, { {3,2},{2,2},{1,1},{0,1} } };
char EffectTurningStep = 0;
void Effect_TurningZ(void)
{
char i;
ClearCube();
for (i = 0; i < 4; i++)
{
SetLED(TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 0, 1);
SetLED(TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 1, 1);
SetLED(TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 2, 1);
SetLED(TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 3, 1);
}
if (EffectTurningStep < 5) EffectTurningStep++;
else EffectTurningStep = 0;
}
void Effect_TurningX(void)
{
char i;
ClearCube();
for (i = 0; i < 4; i++)
{
SetLED(0, TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 1);
SetLED(1, TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 1);
SetLED(2, TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 1);
SetLED(3, TurningSteps[EffectTurningStep][i][0], TurningSteps[EffectTurningStep][i][1], 1);
}
if (EffectTurningStep < 5) EffectTurningStep++;
else EffectTurningStep = 0;
}
void Effect_TurningY(void)
{
char i;
ClearCube();
for (i = 0; i < 4; i++)
{
SetLED(TurningSteps[EffectTurningStep][i][0], 0, TurningSteps[EffectTurningStep][i][1], 1);
SetLED(TurningSteps[EffectTurningStep][i][0], 1, TurningSteps[EffectTurningStep][i][1], 1);
SetLED(TurningSteps[EffectTurningStep][i][0], 2, TurningSteps[EffectTurningStep][i][1], 1);
SetLED(TurningSteps[EffectTurningStep][i][0], 3, TurningSteps[EffectTurningStep][i][1], 1);
}
if (EffectTurningStep < 5) EffectTurningStep++;
else EffectTurningStep = 0;
}
A video of the Cube-board in action can be seen below.
How did you arrive at the power requirement of 960mA?
TIA
@Ceesa
That’s a tricky question 😉
Well, to be exact the circuit is not drawing 960mA but only 630mA.
This is due to the LEDs forwards voltage (2.1V) and the Collector-Emitter saturation voltage of the Cathode transistors (0.7V).
Each LED takes about 39mA which is just below the ATMEGA1280 I/O limit of 40mA.
But the ATMEGA1280 does also have a maximum VCC-GND current rating of 200mA, so it is yet unexplained why this circuit works 😉
Regards Thomas
hi, can I use arduino R3? and the code is still the same as above?
Yes. You should be able to use both Arduino Duemilanove and the Arduino Uno’s, including R3.
Regards Thomas
Hi, are you able to show some pictures of the underside of the proto board? Thanks 🙂
@Isaac
Hi Isaac.
Unfortunately not, as the board is no longer in the office.
Regards Thomas
there is an error
Light_cube.ino: In function ‘void Multiplex()’:
Light_cube:39: error: ‘PORTA’ was not declared in this scope
Light_cube.ino: In function ‘void setup()’:
Light_cube:76: error: ‘DDRA’ was not declared in this scope
Light_cube:77: error: ‘PORTA’ was not declared in this scope
‘PORTA’ was not declared in this scope
@Eshan
It looks like you haven’t selected the right board. Make sure that you have selected the Arduino Mega in the tools menu.
Hi bro, may i know what arduino you are using?
Hey buddy, Thanks for this guide, spent the last week or 2 putting mine together.
Any chance you remember the pin outs in a little more detail? i pinned mine as follows but it seems a bit messed up to say the least, im sure i could figure it out with a hour or 2, its just finding the spare time 🙂
1234 – SV1
5678
1234 – SV3
5678
if you’re looking top down that is.
Thanks again for the guide, Kind regards, Matt
@Matthew
also to clarify is SV3 pin 34-> 41? as “SV3, Pin 1 to 8 <– Arduino Pin 39-32" pin 32 & 33 is already taken by SV2 pin 3&4
best regards, Matt
@Iman
Arduino MEGA 1280