# 4x4x4 LED Cube With Charlieplexing

29,446

90

13

## 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={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={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={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={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={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={6,11, 22,27, 38,43, 54,59}; // upper left and bottom right for each slice
int outer={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={6,11, 22,27, 38,43, 54,59};
int outer={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={7,10, 23,26, 39,42, 55,58};
int outer={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={7,10, 23,26, 39,42, 55,58};
int outer={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={7,10, 23,26, 39,42, 55,58};
int outer={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={6,11, 22,27, 38,43, 54,59};
int outer={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! :)

## 13 Discussions

So according to your diagram pins 6 and 8 of layer 2 is anode which is to be connected to pins 6 and 8 of layer 3 which are cathode. Then you state to connect all the like numbers together. ( E.G.: All 1's to 1's and all 9's to 9's etc.) I hope I am understanding this correctly. In making the layers I gathered that you don't cross wire the positive and negative lines, which would cause a short. Seemingly I wired them correctly but I'm not sure. But in the stacking of layers you clearly are stating to cross wire positive to negative lines. Is this right?

The reason this isn't a problem is because you only ever have one LED on at a time. Each LED is rapidly toggled on and off in quick succession to make the patterns. You don't end up passing the reverse current through the 'backwards' LED because it will instead flow through the 'forward' LED. Thus, there is no need to worry about a short circuit or damaging and LED. (so yes, correct)