Introduction: 4x4x4 LED Cube With Charlieplexing

I know there are tons of ibles on this now, but here is the method I used to make a few LED cubes for my brothers this last Christmas. The electronics are cheap and it doesn't take much time to quickly make one of these. I wrote some very simple code to control them as well. I made a green one and a blue one. Both run on batteries (AA or AAA). I tried using coin cells but couldn't get them to spit out enough juice. You could definitely get the LEDs brighter with a more robust power source and transistor network, but that wasn't my goal. 

// scratch that, they are plenty bright

Step 1: Materials Needed

A quick list of the parts you will need for this design (or at least what I used):
  • ATMega328P-PU microcontroller
  • ribbon cable or individual wires (both work just as well) as thin as possible
  • 64 LEDs of your color choice
  • 9 resistors (100 ohm or 120 ohm or close)
  • optional] experimenters / prototype board
you will also need:
  • AVR programmer with ISP connection

I have the wrong resistors shown in the picture... It took me up until the 4th resistor while soldering to realize that I had placed a reel of resistors in the wrong place on my shelves. That's what I get for moving to a new room. I have 22kohm resistors shown instead of the 100ohm resistors that they should all be.

Step 2: LED Circuit

The pictures are hard to read, so I suggest understanding the schematic shown. Basically you are making 4 layers. the top and bottom layer look the same and the middle two look the same, so that simplifies things.

The middle ones are very simple, just a 4x4 grid of LEDs in lines. The top and bottom ones take a second look.

Vertical lines are + voltage and horizontal lines are ground (for understanding the LEDs).

The numbers on my first picture for this step represent what pin they go to.  Connecting each layer to each other should be done in such a way that you are only connecting similar pins. I used pins 1, 2, 3, and 4 to connect layer 1 to layer 2. I used pins 6 and 8 to connect layer 2 to layer 3. I used pins 5, 6, 7, and 8 to connect layers 3 and 4. Then I made sure every pin was connected to every other pin with the same number (i.e. - that all the 9's were connected, all the 8's were connected, etc.). If you miss one, no big deal, just go back and solder in an extra wire. I used the clipped off leads of LEDs to bridge anything that needed it.

Step 3: Controller Board Design

You don't need a very large prototyping board to work on, just enough to fit the ATMega and the resistors. Simply place the microcontroller on the board, and then place the 9 100ohm resistor on pins 2, 3, 4, 5, 6, 11, 12, 13, and 14. In other words, we are using port D and PB0 of the microcontroller. The atmega328p is way overkill for this project, but whatever.

pin 7 will be the (+5V) Vcc where you connect the power source. pin 8 will be (0V) the ground.

pin 1 is reset, pin 17 is MOSI, pin 18 is MISO, and pin 19 is SCK.

Those will be the only pins that we use.

Basically, all you have to do is attach the resistors to each of the pins mentioned as Port D and PB0, and then connect your ribbon cable or wires to the output of the resistors. Then, connect a wire to pins 1, 17, 18, and 19 for the communication to the programmer, and also to 7 and 8 for the power supply (and programmer if needed).

The 9 wires coming out of the resistors connect to the 9 pins on our LED cube. 

Step 4: Code

You will need an AVR programmer (or you can use an arduino, although I did not use this method) with ISP communication.

As mentioned on the last step, you will need to connect your programmer in ISP fashion to the microcontroller. A picture of the ISP pinout is shown, and can be matched to the following pins.

  • MISO = pin 18
  • MOSI = pin 17
  • SCK = pin 19
  • reset = pin 1
  • Vcc = pin 7
  • GND = pin 8
A .zip file with the source code and the hex file for burning is included on this step along with the .c file.
Please note that in the avrdude screen capture, it showns my filename as GccApplication1 in the folder Debug. Change this filepath to whatever you name the file and to wherever you put the file. Otherwise, use the command shown below:

avrdude -c usbtiny -p atmega328p -U flash:w:filepath\filename.hex 

changing usbtiny to whatever programmer you use, and the microcontroller if you used a different one.

***See the attached folder for the code***
*Updated the code to include more functions (1/27/2013)*

Step 5: Code II

// This step is just the C code written out for those who don't want to download the zip file on the last step or this step (same code).

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>

int rn;
int r64;
int r48;
int r32;
int r16;

// cathode (c) is short (-) lead, anode (a) is longer (+) lead
// cathode <---- layer 1 ---------------->  <---- layer 2 ---------------->  <---- layer 3 ---------------->  <---- layer 4 ---------------->
int c[]={2,3,4,5, 1,3,4,5, 1,2,4,5, 1,2,3,5, 6,7,8,9, 6,7,8,9, 6,7,8,9, 6,7,8,9, 1,2,3,4, 1,2,3,4, 1,2,3,4, 1,2,3,4, 6,7,8,9, 5,7,8,9, 5,6,8,9, 5,6,7,9};

// anode   <-------- layer 1 --------------->  <-------- layer 2 --------------->  <-------- layer 3 --------------->  <-------- layer 4 --------------->
int a[]={1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8};

void delay(int ms)  // must write our own delay function since _delay_ms(int) only accepts constants (we can't pass a variable to it)
{
for (int i=0; i <= ms; i++)
{
  _delay_ms(1);   // in milliseconds
}
}

int main (void)
{
rn = 642563;  // for the random seed
DDRD = 0x00000000;
DDRB = 0x00000000;
PORTD = 0x00000000;
PORTB = 0x00000000;
while(1)

  LineSpin(20,1,1);
  LineSpin(16,1,1);
  LineSpin(12,1,1);
  LineSpin(8,1,1);
  LineSpin(6,1,2);
  LineSpin(5,1,2);
  LineSpin(4,1,3);
  LineSpin(3,1,3);
  LineSpin(2,1,4);
  LineSpin(1,1,8);
 
  scroll(75);
  scroll(50);
  scroll(25);
  scroll(10);
  scroll(3);
 
  drawBox(100,40); // multiply inputs to get time in ms
 
  spin(1,60);
  spin(1,40);
  spin(1,30);
  spin(1,20);
  spin(1,15);
  spin(1,10);
  spin(1,8);
  spin(1,6);
  spin(1,5);
  spin(1,4);
  spin(1,3);
  spin(1,3);
  spin(1,3);
  spin(1,3);
  spin(1,3);
  spin(1,3);
  spin(1,3);
 
  rain(100, 100, 16);
 
  Snake_x(2,1,100);
  Snake_x(2,14,60);
  Snake_x(2,16,40);
  Snake_x(2,18,20);
  Snake_x(2,10,10);
 
  layerPattern(50);
  layerPattern(100);
 
  FlashOn(0,0,16);
  FlashOn(1,1,10);
  FlashOn(10,1,6);
 
  randomGen();
  randomGen();
  randomGen();
  randomGen();
  randomGen();
  randomGen();
}
return 0;
}

// op calls the coordinates of the LED from the indexed arrays above
void op(int n) {
DDRD = 0x00000000;
DDRB = 0x00000000;
PORTD = 0x00000000;
PORTB = 0x00000000;
int cn = ((c[n-1])-1);
int an = ((a[n-1])-1);
//---------cathodes
if(cn==8)
{ DDRB = _BV(DDB0);
  PORTB = _BV(PORTB0);
}
else
{ DDRD = _BV(cn);
  PORTD = _BV(cn);
}
//---------anodes
if(an==8) { DDRB |= _BV(an); }
else {  DDRD |= _BV(an); }
}

void allOff()
{    // turns pins 1 - 9 off (this means all pins used are off)
PORTB &= 0;
PORTD &= 0;
    DDRB = 0x00000000; // pinMode INPUT for entire port
    DDRD = 0x00000000; // pinMode INPUT for entire port
}

FlashOn(int refresh_rate, int dark_time, int cycles)
{
allOff();
delay(dark_time);
for(int c=0;c<cycles;c++)
{
  for(int i=1;i<65;i++)
  {
   op(i);
   delay(refresh_rate);
  }
}
}

// ----------------- drawing algorithms ---------------------------------------
void boxFrame(v) {    //rasters a wire-frame of the box
  int list[40]={1,2,3,4,  5,8,  9,12,  13,14,15,16,
               17,20,   29,32,
               33,36,   45,48,
               49,50,51,52,  53,56,  57,60,  61,62,63,64};
  for (int i=0;i<41;i++) {
    op(list[i]);
for (int t=0; t<v; t++)
  _delay_us(10);
  }
}


// layer patterns (AKA slice patterns (z))
void sz1() {     // lights up the bottom layer
  for (int i=1;i<17;i++) {
    op(i);
  }
}
void sz2() {     // lights up the 2nd layer
  for (int i=17;i<33;i++) {
    op(i);
  }
}
void sz3() {     // lights up the 3rd layer
  for (int i=33;i<49;i++) {
    op(i);
  }
}
void sz4() {     // lights up the 4th layer
  for (int i=49;i<65;i++) {
    op(i);
  }
}


// slice patterns (x)
void sx1() {     // lights up the 1st x slice (back)
  int list[16]={1,2,3,4, 17,18,19,20, 33,34,35,36, 49,50,51,52};
  for (int i=0;i<16;i++) {
    op(list[i]);
  }
}
void sx2() {     // lights up the 2nd x slice
  int list[16]={5,6,7,8, 21,22,23,24, 37,38,39,40, 53,54,55,56};
  for (int i=0;i<16;i++) {
    op(list[i]);
  }
}
void sx3() {     // lights up the 3rd x slice
  int list[16]={9,10,11,12, 25,26,27,28, 41,42,43,44, 57,58,59,60};
  for (int i=0;i<16;i++) {
    op(list[i]);
  }
}
void sx4() {     // lights up the 4th x slice (front)
  int list[16]={13,14,15,16, 29,30,31,32, 45,46,47,48, 61,62,63,64};
  for (int i=0;i<16;i++) {
    op(list[i]);
  }
}


// slice patterns (y)
void sy1() {     // lights up the 1st y slice (left)
  for (int i=0;i<64;i=i+4) {
    op(i);
  }
}
void sy2() {     // lights up the 2nd y slice
  for (int i=1;i<64;i=i+4) {
    op(i);
  }
}
void sy3() {     // lights up the 3rd y slice
  for (int i=2;i<64;i=i+4) {
    op(i);
  }
}
void sy4() {     // lights up the 4th y slice (right)
  for (int i=3;i<64;i=i+4) {
    op(i);
  }
}


// rain
void rain(int dur,int inidur,int cycles) {

srand(rn);
rn = ((rn*13)%7)+1;
for(int i=0;i<cycles;i++)
{
  r64 = (rand()%16)+49;
  r48 = r64-16;
  r32 = r48-16;
  r16 = r32-16;
  op(r64);
  delay(inidur); // you can have it pause extra long on the first light if you want with inidur.
  op(r48);
  delay(dur);
  op(r32);
  delay(dur);
  op(r16);
  delay(dur); 
}
}


// random     (generates a hazy random oscillating field of light)
void randomGen() {    
  srand(rn);
  for(int i=0;i<10;i++) {
    int r = (rand()%64)+1;
    op(r);
    delay(100);
  }
  rn++;
}


// spin around z axis
void spin11(int v) {
  int core[8]={6,11, 22,27, 38,43, 54,59}; // upper left and bottom right for each slice
  int outer[8]={1,16, 17,32, 33,48, 49,64};    // upper left and bottom right corner
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}
void spin12(int v) {
  int core[8]={6,11, 22,27, 38,43, 54,59};
  int outer[8]={2,15, 18,31, 34,47, 50,63};
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}
void spin21(int v) {
  int core[8]={7,10, 23,26, 39,42, 55,58};
  int outer[8]={3,14, 19,30, 35,46, 51,62};
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}
void spin22(int v) {    // the top right and bottom left corners
  int core[8]={7,10, 23,26, 39,42, 55,58};
  int outer[8]={4,13, 20,29, 36,45, 52,61};
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}
void spin23(int v) {
  int core[8]={7,10, 23,26, 39,42, 55,58};
  int outer[8]={8,9, 24,25, 40,41, 56,57};
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}
void spin13(int v) {
  int core[8]={6,11, 22,27, 38,43, 54,59};
  int outer[8]={5,12, 21,28, 37,44, 53,60};
  for (int t=0;t<v;t++) {
    for (int i=0; i<8; i++) {
      op(core[i]);
      _delay_us(500);
      op(outer[i]);
      _delay_us(500);
    }
  }
}

// ------------------ programs ---------------------------------------------
void spin(int spins,int v) {
  for (int i=0; i<spins; i++) {
    spin11(v);
    spin12(v);
    spin21(v);
    spin22(v);
    spin23(v);
    spin13(v);
  }
}


void scroll(int v) {    // one by one, {turn on, wait, off} each LED in order
  for (int n=1;n<65;n++) {
op(n);
    delay(v);      // wait v milliseconds before rastering next LED
  }
}



void allOn(int cycles, int delaytime) {
  for (int i=0;i<cycles;i++) {
    for (int n=1;n<65;n++) {
     op(n);
  for (int t=0; t<delaytime; t++)
   _delay_us(1);
    }
  }
}


void drawBox(int cycles, int v) {  // v is delay in 10's of micro seconds
  for (int i=0;i<cycles;i++) {
    boxFrame(v);
  }
}

void layerZup(int v) {    // higher v = slower
  for(int t=0;t<v;t++) {sz1();}
  for(int t=0;t<v;t++) {sz2();}
  for(int t=0;t<v;t++) {sz3();}
  for(int t=0;t<v;t++) {sz4();}
}
void layerZdown(int v) {
  for(int t=0;t<v;t++) {sz4();}
  for(int t=0;t<v;t++) {sz3();}
  for(int t=0;t<v;t++) {sz2();}
  for(int t=0;t<v;t++) {sz1();}
}
void layerYup(int v) {
  for(int t=0;t<v;t++) {sy1();}
  for(int t=0;t<v;t++) {sy2();}
  for(int t=0;t<v;t++) {sy3();}
  for(int t=0;t<v;t++) {sy4();}
}
void layerYdown(int v) {
  for(int t=0;t<v;t++) {sy4();}
  for(int t=0;t<v;t++) {sy3();}
  for(int t=0;t<v;t++) {sy2();}
  for(int t=0;t<v;t++) {sy1();}
}
void layerXup(int v) {
  for(int t=0;t<v;t++) {sx1();}
  for(int t=0;t<v;t++) {sx2();}
  for(int t=0;t<v;t++) {sx3();}
  for(int t=0;t<v;t++) {sx4();}
}
void layerXdown(int v) {
  for(int t=0;t<v;t++) {sx4();}
  for(int t=0;t<v;t++) {sx3();}
  for(int t=0;t<v;t++) {sx2();}
  for(int t=0;t<v;t++) {sx1();}
}

void layerPattern(int v) {     // higher v = slower shift layer speed
  layerZdown(v);
  layerZup(v);
  layerYdown(v);
  layerYup(v);
  layerXdown(v);
  layerXup(v);
}

LineSpin(int dur, int dt, int cycles)
{
for(int c=0;c<cycles;c++)
{
  int spin_x[]={1,5,9,13,14,15,16,12,8,4,3,2};
  for(int a=1;a<13;a++)
  {
   for(int t=0;t<dur;t++)
   {
    op(spin_x[a]);
    delay(dt);
    op(spin_x[a]+16);
    delay(dt);
    op(spin_x[a]+32);
    delay(dt);
    op(spin_x[a]+48);
    delay(dt);
   }  
  }
}
}

Snake_x(int layer, int cycles, int speed)  // valid layer are 1,2,3,4
{
int start_led = (16 * layer) - 15;
int x[]={1,5,9,13,14,15,16,12,8,4,3,2};
srand(rn);
int r = rand()%100;
int current_layer = layer;
for(int c=0; c<cycles; c++)
{
  for(int i=1;i<13;i++)
  {
   if ((r < 20) && (current_layer != 1))
   {
    current_layer--;
   }
   else if ((r > 80) && (current_layer != 4))
   {
    current_layer++;
   }
  
   op(x[i]+(16*(current_layer-1)));
  
   delay(speed);
   r = rand()%100;
  }
}
}

Step 6: Test & Finish

I would be posting videos but I didn't think to before giving them away. Maybe I will make another and show it off. In the meantime, all I have to share are the finished pictures.

As always, if you have ANY questions, please ask. I love helping when I can.

*Update (1/27/2013) - Since Kiteman asked so nicely, here is the picture of the cube I made today and a video of most of the functions running. Looks even better than the rest! :)