When I started to work with Arduino I was fascinating about LED Cube but it went very hard to make it for me. After a time I found out that it can be very easy.
In this article I focus mainly on very easy code for LED Cube 4x4x4 with shift registers and how to make diffrent patterns for the cube. I did not like codes I saw in such projects so I had to create it my own way.
Construction of the LED CubeI am not going to explain how to join leds to make a cube 4x4x4 because there is a lot of videos on youtube how you can build it. For example: https://www.youtube.com/watch?v=hjrfa04KPYI
If you already have it after some moment of peace and patience you get a cube that has 16 legs and 4 rows. Les´s call these rows layers. The easiest way how to join those 16 legs to Arduino and not to occupy all its pins is to use the shift register 75HC595.
I joined the first 8 legs (1-8) of the cube to the first shift register (pins Q0-Q7) and other 8 legs (9-16) to another one as shown in the table from the top view:
The next step is to connect pins of the shift registers with Arduino as following:
- PIN 8 (both shift registers / GND) to GROUND pin of Arduino
- PIN 9 (Q7´) of the first shift register to PIN 14 (DATA) of the second shift register
- PIN 10 (both shift registers / RESET) to PIN 8 of Arduino
- PIN 11 (both shift registers / CLOCK) to PIN 9 of Arduino
- PIN 12 (both shift registers / LATCH) to PIN 10 of Arduino
- PIN 14 (first shift register / DATA) to PIN 11 of Arduino
- PIN 16 (both shift registers / VCC) to 5V pin of Arduino
The last step is to connect layers of the cube with Arduino:
- PIN 4 - Layer 0 (through 100ohm resistor)
- PIN 5 - Layer 1 (through 100ohm resistor)
- PIN 6 - Layer 2 (through 100ohm resistor)
- PIN 7 - Layer 3 (through 100ohm resistor)
The alfa and omega of the project is to understand how we can control any led in the cube independantly from others. One layer contains 16 leds what leads us to an idea about setting 16 bits of an unsigned int. Shortly, every led in a layer is represented by a number as in this table:
So if we want to turn on the second led in the second row of the layer, we will set layer = 32. To turn on first 2 leds, layer = 1 + 2 = 3. To turn on first row of leds, layer = 1 + 2 + 4 + 8 = 15.
If we would like to move the light in the loop through all the leds in the layer, in the first step we set layer = B0000000000000001 = 1 and then in the loop we will shift this bit by setting layer = layer << 1. Or we can set layer = (1 << count) and increase "count" within the loop. For more information about setting and shifting bits in c++ use google.
The main LOOPNow, when we know how to set leds in a layer we want to control 4 layers. Whereas we can send to our 2 shift registers information about 1 layer only, we use the main loop of Arduino to turn on and off layers in the cycle very quickly so our eyes evaluate it as they would be all changed at the same moment. The code to display values of layers is very simple:
SetShiftReg(layer[k]); //send layer data to shift registers
bitClear(PORTD, 4 + k); //turn ON layer k
delay(1); //important for brightness of leds
PORTD |= B11110000; //turn OFF layers
k++; if (k > 3) k = 0; //take another layer in the loop
In my project I use the button (joined with ground and pin 2 of Arduino) to change patterns I created. Pushing the button sets the boolean "start". This start takes and initializes the next pattern.
Then the loop launches the selected pattern after every time in milliseconds that is set in the value "speedTime".
The tab about the main loop and the button:
#define buttonPin 2
unsigned int layer[4] = {0, 0, 0, 0}; //65535 filled layer
byte k = 0;
bool start = true;
unsigned long delayTime;
int speedTime = 0;
int count;
void setup() {
InitializeShiftReg();
InitializeMyLedCube();
}
void InitializeMyLedCube() {
DDRD = B11110000; //pins D0-D3 as INPUTS, D4-D7 as OUTPUTS
PORTD |= B11110000; //turn OFF layers
pinMode(buttonPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(buttonPin), PushButton, FALLING);
delayTime = millis();
}
byte patternNum = 28;
byte pattern = patternNum - 1;
void loop() {
if (start) {
detachInterrupt(digitalPinToInterrupt(buttonPin));
delay(500); //wait for releasing PushButton
pattern++;
if (pattern > patternNum) pattern = 1;
attachInterrupt(digitalPinToInterrupt(buttonPin), PushButton, FALLING);
count = 0;
}
if (((millis() - delayTime) > speedTime) || start) {
switch (pattern) {
case 1: LayersUpDown(); break;
case 2: FallingDot(); break;
case 3: Rain(); break;
case 4: AllCube(); break;
case 5: Cut(); break;
case 6: Cube(); break;
case 7: Diagonal(); break;
case 8: Mixer(); break;
case 9: Random(); break;
case 10: FallingLayer(); break;
case 11: LayerCut(); break;
case 12: Circle(); break;
case 13: RandomWay(); break;
case 14: SmallCube(); break;
case 15: RandomWayCube(); break;
case 16: GrowingCube(); break;
case 17: FallingLayers(); break;
case 18: GrowingLine(); break;
case 19: CircleEdges(); break;
case 20: CircleSide(); break;
case 21: RandomWayLine(); break;
case 22: RandomWaySide(); break;
case 23: DJCube(); break;
case 24: FillingCube(); break;
case 25: NanoBuilding(); break;
case 26: Curve(); break;
case 27: Snake(); break;
case 28: Julka(); break;
default: break;
}
delayTime = millis();
}
//this part displays layers as they are set in functions
SetShiftReg(layer[k]); //send layer data to shift registers
bitClear(PORTD, 4 + k); //turn ON layer k
delay(1); //important for brightness of leds
PORTD |= B11110000; //turn OFF layers
k++; if (k > 3) k = 0; //take another layer in the loop
}
void PushButton() {
start = true;
}
The "ShiftRegister" tab:
#define latchPin 10 //PORT B2
#define clockPin 9 //PORT B1
#define dataPin 11 //PORT B3
#define resetPin 8 //PORT B0
void InitializeShiftReg() {
DDRB |= B1111; //pins D8-D11 as OUTPUTS
PORTB |= B0001; //resetPin to HIGH
}
void SetShiftReg(unsigned int value) {
bitClear(PORTB, 2); //digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, value >> 8);
shiftOut(dataPin, clockPin, MSBFIRST, value);
bitSet(PORTB, 2); //digitalWrite(latchPin, HIGH);
}
Creating patternsCreating patterns is very easy and the cube became as a big fun to think about new ones and how to make them. As you can see, the code about a pattern is usually very simple.
We must only understand that the main loop of Arduino launches the selected pattern after every time that we set in the start of the pattern in the value "speedTime". In other words, the function for a pattern always changes layers once only, but the main loop repeats it many times.
Let's look at the pattern Rain() as an example:
void Rain() {
if (start) {
start = false; speedTime = 200;
ClearLayers();
}
layer[0] = layer[1];
layer[1] = layer[2];
layer[2] = layer[3];
layer[3] = 1 << random(16);
}
The "start" part launches once only when we turn on the pattern. It sets the speedTime and turns off all leds. Then we always turn on 1 of 16 leds on the top layer randomly and within the loop we move it to lower layers.
Below you can find the code for patterns I created and some samples are in the video. HAVE FUN!
The "Patterns" tab:
bool direct;
unsigned int actualValue;
byte lay, xVal, yVal;
byte place;
void SetLayers(unsigned int value) { //helpful function
layer[0] = value; layer[1] = value; layer[2] = value; layer[3] = value;
}
void InversLayers() { //helpful function
layer[0] = 65535 - layer[0];
layer[1] = 65535 - layer[1];
layer[2] = 65535 - layer[2];
layer[3] = 65535 - layer[3];
}
void ClearLayers() { //helpful function
SetLayers(0);
}
void LayersUpDown() {
if (start) {
start = false; speedTime = 500;
direct = true;
}
ClearLayers();
layer[count] = 65535;
if (direct) {
count++;
if (count > 3) {
direct = false;
count = 2;
}
} else {
count--; if (count < 1) direct = true;
}
}
void FallingDot() {
if (start) {
start = false; speedTime = 100;
count = 4;
actualValue = 1;
direct = true;
}
count--;
ClearLayers();
layer[count] = actualValue;
if (count == 0) {
count = 4;
actualValue = actualValue << 1;
if (actualValue == 0) actualValue = 1;
}
}
void Rain() {
if (start) {
start = false; speedTime = 200;
ClearLayers();
}
layer[0] = layer[1];
layer[1] = layer[2];
layer[2] = layer[3];
layer[3] = 1 << random(16);
}
void Random() {
if (start) {
start = false; speedTime = 200;
}
layer[0] = random(65536);
layer[1] = random(65536);
layer[2] = random(65536);
layer[3] = random(65536);
}
void AllCube() {
if (start) {
start = false; speedTime = 2000;
}
ClearLayers();
switch (count) {
case 0:
SetLayers(65535);
break;
case 1:
layer[1] = 1632;
layer[2] = 1632;
break;
}
count++; if (count > 1) count = 0;
}
void Cut() {
if (start) {
start = false; speedTime = 2000;
layer[0] = 4369;
layer[1] = 13107;
layer[2] = 30583;
layer[3] = 65535;
}
InversLayers();
}
void Cube() {
if (start) {
start = false; speedTime = 2000;
layer[0] = 15+16+128+256+2048+4096+8192+16384+32768;
layer[1] = 1+8+4096+32768;
layer[2] = layer[1];
layer[3] = layer[0];
}
InversLayers();
}
void Diagonal() {
if (start) {
start = false; speedTime = 2000;
layer[0] = 33825;
layer[1] = 33825;
layer[2] = 33825;
layer[3] = 33825;
}
InversLayers();
}
void Mixer() {
if (start) {
start = false; speedTime = 100;
}
switch (count) {
case 0: actualValue = 33825; break;
case 1: actualValue = 17442; break;
case 2: actualValue = 8772; break;
case 3: actualValue = 4680; break;
case 4: actualValue = 960; break;
case 5: actualValue = 3120; break;
}
SetLayers(actualValue);
count++;
if (count > 5) count = 0;
}
void FallingLayer() {
if (start) {
start = false; speedTime = 100;
actualValue = 0;
direct = false;
}
if (actualValue == 0) {
actualValue = 1;
ClearLayers();
direct = !direct;
layer[3 - (direct? 0:3)] = 65535;
}
switch (count) {
case 0:
layer[3 - (direct? 0:3)] -= actualValue;
layer[2 - (direct? 0:1)] = actualValue;
break;
case 1:
layer[1 + (direct? 0:1)] = layer[2 - (direct? 0:1)];
layer[2 - (direct? 0:1)] = 0;
break;
case 2:
layer[0 + (direct? 0:3)] += layer[1 + (direct? 0:1)];
layer[1 + (direct? 0:1)] = 0;
actualValue = actualValue << 1;
break;
}
count++; if (count > 2) count = 0;
}
void LayerCut() {
if (start) {
start = false; speedTime = 100;
}
if (count) layer[0] = layer[0] << 1;
else layer[0] = 4369;
SetLayers(layer[0]);
count++;
if (count > 20) count = 0;
}
void Circle() {
if (start) {
start = false; speedTime = 100;
lay = 3;
}
switch (count) {
case 0: ClearLayers(); layer[lay] = 1; break;
case 1: layer[lay] = 2; break;
case 2: layer[lay] = 4; break;
case 3: layer[lay] = 8; break;
case 4: layer[lay] = 128; break;
case 5: layer[lay] = 2048; break;
case 6: layer[lay] = 32768; break;
case 7: layer[lay] = 16384; break;
case 8: layer[lay] = 8192; break;
case 9: layer[lay] = 4096; break;
case 10: layer[lay] = 256; break;
case 11: layer[lay] = 16; break;
case 12: layer[lay] = 1; break;
}
count++;
if (count > 12) {
count = 0;
if (lay == 0) lay = 3;
else lay--;
}
}
void RandomWay() {
if (start) {
start = false; speedTime = 200;
lay = random(4);
xVal = random(4);
yVal = random(4);
}
switch (random(7)) {
case 0: if ((lay + 1) <= 3) lay++; break;
case 1: if ((lay - 1) >= 0) lay--; break;
case 2: if ((xVal + 1) <= 3) xVal++; break;
case 3: if ((xVal - 1) >= 0) xVal--; break;
case 4: if ((yVal + 1) <= 3) yVal++; break;
case 5: if ((yVal - 1) >= 0) yVal--; break;
default: break;
}
ClearLayers();
layer[lay] = (1 << xVal) << (4 * yVal);
}
void SmallCube() {
if (start) {
start = false; speedTime = 200;
}
switch (count) {
case 0: lay = random(3); ClearLayers(); actualValue = 51; break;
case 1: actualValue = actualValue << 1; break;
case 2: actualValue = actualValue << 1; break;
case 3: actualValue = actualValue << 4; break;
case 4: actualValue = actualValue << 4; break;
case 5: actualValue = actualValue >> 1; break;
case 6: actualValue = actualValue >> 1; break;
case 7: actualValue = actualValue >> 4; break;
case 8: actualValue = actualValue >> 4; break;
}
layer[lay] = actualValue;
layer[lay+1] = actualValue;
count++; if (count > 8) count = 0;
}
void RandomWayCube() {
if (start) {
start = false; speedTime = 200;
lay = random(3);
xVal = random(3);
yVal = random(3);
}
switch (random(7)) {
case 0: if ((lay + 1) < 3) lay++; break;
case 1: if ((lay - 1) >= 0) lay--; break;
case 2: if ((xVal + 1) < 3) xVal++; break;
case 3: if ((xVal - 1) >= 0) xVal--; break;
case 4: if ((yVal + 1) < 3) yVal++; break;
case 5: if ((yVal - 1) >= 0) yVal--; break;
default: break;
}
ClearLayers();
layer[lay] = (51 << xVal) << (4 * yVal);
layer[lay+1] = layer[lay];
}
void GrowingCube() {
if (start) {
start = false; speedTime = 1000;
}
switch (count) {
case 0: ClearLayers(); layer[0] = 1; break;
case 1: layer[0] = 51; layer[1] = 51; break;
case 2: layer[0] = 1911; layer[1] = 1911; layer[2] = 1911; break;
case 3: SetLayers(65535); break;
default: break;
}
count++; if (count > 4) count = 0;
}
void FallingLayers() {
if (start) {
start = false; speedTime = 200;
actualValue = 0;
lay = 4;
}
if (actualValue == 0) {
lay--; if (lay < 1) lay = 3;
actualValue = 1;
ClearLayers();
layer[lay] = 65535;
}
layer[lay] -= actualValue;
layer[lay-1] += actualValue;
actualValue = actualValue << 1;
}
void GrowingLine() {
if (start) {
start = false; speedTime = 100;
}
switch (count) {
case 0:
ClearLayers();
layer[0] = 1;
lay = 0; xVal = 0; yVal = 0;
while ((lay == 0) && (xVal == 0) && (yVal == 0)) {
lay = random(2);
xVal = random(2);
yVal = random(2);
}
break;
case 1: layer[lay] += ((xVal==0)?1:2) << (4 * yVal); break;
case 2: layer[2 * lay] += ((xVal==0)?1:4) << (8 * yVal); break;
case 3: layer[3 * lay] += ((xVal==0)?1:8) << (12 * yVal); break;
case 4: layer[3 * lay] -= ((xVal==0)?1:8) << (12 * yVal); break;
case 5: layer[2 * lay] -= ((xVal==0)?1:4) << (8 * yVal); break;
default: break;
}
count++; if (count > 5) count = 0;
}
void CircleEdges() {
if (start) {
start = false; speedTime = 100;
lay = 3;
ClearLayers();
}
switch (count) {
case 0: layer[lay] = 1; break;
case 1: layer[lay] += 2; break;
case 2: layer[lay] += 4; break;
case 3: layer[lay] += 8; break;
case 4: layer[lay] += 128; break;
case 5: layer[lay] += 2048; break;
case 6: layer[lay] += 32768; break;
case 7: layer[lay] += 16384; break;
case 8: layer[lay] += 8192; break;
case 9: layer[lay] += 4096; break;
case 10: layer[lay]+= 256; break;
case 11: layer[lay] += 16; break;
default: break;
}
count++;
if (count > 12) {
count = 0;
if (lay == 0) {
lay = 3;
ClearLayers();
}
else lay--;
}
}
void CircleSide() {
if (start) {
start = false; speedTime = 1000;
}
switch (count) {
case 0: SetLayers(15); break;
case 1: SetLayers(34952); break;
case 2: SetLayers(61440); break;
case 3: SetLayers(4369); break;
case 4: ClearLayers(); layer[0] = 65535; break;
case 5: SetLayers(34952); break;
case 6: ClearLayers(); layer[3] = 65535; break;
case 7: SetLayers(4369); break;
}
count++;
if (count > 7) count = 0;
}
void RandomWayLine() {
if (start) {
start = false; speedTime = 200;
lay = random(3);
yVal = random(3);
}
switch (random(5)) {
case 0: if ((lay + 1) <= 3) lay++; break;
case 1: if ((lay - 1) >= 0) lay--; break;
case 2: if ((yVal + 1) <= 3) yVal++; break;
case 3: if ((yVal - 1) >= 0) yVal--; break;
default: break;
}
ClearLayers();
layer[lay] = 15 << (4 * yVal);
}
void RandomWaySide() {
if (start) {
start = false; speedTime = 200;
xVal = random(3);
}
switch (random(3)) {
case 0: if ((xVal + 1) <= 3) xVal++; break;
case 1: if ((xVal - 1) >= 0) xVal--; break;
default: break;
}
ClearLayers();
actualValue = 4369 << xVal;
switch (random(2)) {
case 0: SetLayers(actualValue); break;
case 1: layer[1] = actualValue; layer[2] = actualValue; break;
}
}
void DJCube() {
if (start) {
start = false; speedTime = 200;
xVal = random(3);
}
switch (random(3)) {
case 0: if ((xVal + 1) <= 3) xVal++; break;
case 1: if ((xVal - 1) >= 0) xVal--; break;
default: break;
}
switch (xVal) {
case 0: SetLayers(4369); break;
case 1: SetLayers(13107); break;
case 2: SetLayers(30583); break;
case 3: SetLayers(65535); break;
}
}
void FillingCube() {
if (start) {
start = false; speedTime = 200;
count = 4;
actualValue = 1;
}
if ((count == 4) && (actualValue == 1)) ClearLayers();
count--;
layer[count] += actualValue;
if (count == 0) {
count = 4;
actualValue = actualValue << 1;
if (actualValue == 0) actualValue = 1;
}
}
void NanoBuilding() {
if (start) {
start = false; speedTime = 150;
actualValue = 0;
place = 1;
direct = true;
}
if (direct) {
direct = false;
while ((actualValue < place) && (place < 64)) {
lay = random(4);
xVal = random(4);
yVal = random(4);
actualValue = xVal + (yVal * 4) + (lay * 16);
}
}
actualValue = 0;
int xValP = xVal;
int yValP = yVal;
int layP = lay;
while ((actualValue < place) && (place < 64)) {
xVal = xValP;
yVal = yValP;
lay = layP;
switch (random(7)) {
case 0: if ((lay + 1) <= 3) lay++; break;
case 1: if ((lay - 1) >= 0) lay--; break;
case 2: if ((xVal + 1) <= 3) xVal++; break;
case 3: if ((xVal - 1) >= 0) xVal--; break;
case 4: if ((yVal + 1) <= 3) yVal++; break;
case 5: if ((yVal - 1) >= 0) yVal--; break;
default: break;
}
actualValue = xVal + (yVal * 4) + (lay * 16);
}
if (actualValue == place) {
place++;
direct = true;
}
ClearLayers();
if (place < 16) layer[0] = (1 << place) - 1;
else {
if (place < 32) {
layer[1] = (1 << (place - 16)) - 1;
layer[0] = 65535;
} else {
if (place < 48) {
layer[2] = (1 << (place - 32)) - 1;
layer[0] = 65535;
layer[1] = 65535;
} else {
if (place < 64) {
SetLayers(65535);
layer[3] = (1 << (place - 48)) - 1;
} else place = 1;
}
}
}
layer[lay] |= ((1 << xVal) << (4 * yVal));
}
void Curve() {
int line[4] = {4369, 8738, 17476, 34952};
byte pos[12] = {0, 1, 2, 3, 3, 2, 1, 0, 0, 1, 2, 3};
if (start) {
start = false; speedTime = 100;
}
layer[0] = ((pos[count] == 0) ? line[0]: 0) + ((pos[count+1] == 0) ? line[1]: 0) + ((pos[count+2] == 0) ? line[2]: 0) + ((pos[count+3] == 0) ? line[3]: 0);
layer[1] = ((pos[count] == 1) ? line[0]: 0) + ((pos[count+1] == 1) ? line[1]: 0) + ((pos[count+2] == 1) ? line[2]: 0) + ((pos[count+3] == 1) ? line[3]: 0);
layer[2] = ((pos[count] == 2) ? line[0]: 0) + ((pos[count+1] == 2) ? line[1]: 0) + ((pos[count+2] == 2) ? line[2]: 0) + ((pos[count+3] == 2) ? line[3]: 0);
layer[3] = ((pos[count] == 3) ? line[0]: 0) + ((pos[count+1] == 3) ? line[1]: 0) + ((pos[count+2] == 3) ? line[2]: 0) + ((pos[count+3] == 3) ? line[3]: 0);
count++; if (count > 7) count = 0;
}
struct snake {
byte x, y, z;
} obj[7];
void Snake() {
int k;
byte c = 7;
if (start) {
start = false; speedTime = 200;
for (k = 0; k < c; k++) {
obj[k].x = 0;
obj[k].y = 0;
obj[k].z = 0;
}
lay = 0;
xVal = 0;
yVal = 0;
}
int xValP = xVal;
int yValP = yVal;
int layP = lay;
direct = true;
while (direct) {
xVal = xValP;
yVal = yValP;
lay = layP;
switch (random(6)) {
case 0: if ((lay + 1) <= 3) lay++; break;
case 1: if ((lay - 1) >= 0) lay--; break;
case 2: if ((xVal + 1) <= 3) xVal++; break;
case 3: if ((xVal - 1) >= 0) xVal--; break;
case 4: if ((yVal + 1) <= 3) yVal++; break;
case 5: if ((yVal - 1) >= 0) yVal--; break;
default: break;
}
direct = false;
for (k = 0; k < c; k++) {
if ((xVal == obj[k].x) && (yVal == obj[k].y) && (lay == obj[k].z)) direct = true;
}
}
for (k = c-2; k >= 0; k--) {
obj[k+1].x = obj[k].x;
obj[k+1].y = obj[k].y;
obj[k+1].z = obj[k].z;
}
obj[0].x = xVal;
obj[0].y = yVal;
obj[0].z = lay;
ClearLayers();
for (k = 0; k < c; k++) {
layer[obj[k].z] += (1 << obj[k].x) << (4 * obj[k].y);
}
}
void Julka() {
int letters[5] = {59556, 43694, 8750, 58446, 60138};
if (start) {
start = false; speedTime = 1000;
ClearLayers();
}
layer[3] = letters[count];
count++;
if (count == 5) count = 0;
}
Comments