Home > TKJ Electronics > 4×4 Arduino controlled LED Cube

4×4 Arduino controlled LED Cube

4×4 Cube-board in action


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.

The finished 4×4 LED 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).

LEDs Interconnection



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.

Guideline platform


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.

Solder the 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.

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.

Stabilizing the outer frame


You have now finished a single layer.

Step 4 – Multi-layers

Assemble 4 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.

Assembling the 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.

Bending might be necessary


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.

Layer by layer…


When all layers has been put together you should end up with something like the cube in the image below.

Finished cube


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.

4×4 Cube Schematic


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.

The finished 4×4 LED Cube


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.

unsigned char Prescaler = 0;
#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.

Categories: TKJ Electronics Tags:
  1. Ceesa
    January 13th, 2013 at 05:06 | #1

    How did you arrive at the power requirement of 960mA?

    TIA

  2. January 13th, 2013 at 23:12 | #2

    @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

  3. naz1234
    August 20th, 2013 at 14:50 | #3

    hi, can I use arduino R3? and the code is still the same as above?

  4. August 20th, 2013 at 22:56 | #4

    Yes. You should be able to use both Arduino Duemilanove and the Arduino Uno’s, including R3.
    Regards Thomas

  5. Isaac
    November 26th, 2013 at 08:18 | #5

    Hi, are you able to show some pictures of the underside of the proto board? Thanks 🙂

  6. November 27th, 2013 at 20:57 | #6

    @Isaac
    Hi Isaac.
    Unfortunately not, as the board is no longer in the office.

    Regards Thomas

  7. Eshan
    August 16th, 2015 at 07:48 | #7

    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

  8. August 17th, 2015 at 14:03 | #8

    @Eshan
    It looks like you haven’t selected the right board. Make sure that you have selected the Arduino Mega in the tools menu.

  9. Iman
    December 2nd, 2015 at 08:53 | #9

    Hi bro, may i know what arduino you are using?

  10. Matthew
    December 4th, 2015 at 22:05 | #10

    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

  11. Matthew
    December 4th, 2015 at 22:24 | #11

    @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

  12. December 4th, 2015 at 23:41 | #12

    @Iman
    Arduino MEGA 1280

  1. No trackbacks yet.