Coding Help for 3x3x3 LED Cube

Hello, I have recently constructed a 3x3x3 LED cube using common cathode LEDs. I believe my hardware is working correctly, however I am having issues controlling the cube. I am new to Arduino and using microcontrollers.

To quickly explain my setup, there are three layers, each with nine LEDs. All the cathodes of the LEDs are connected together on each individual layer. This leads me with 3 ground pins that control the layer select. Then all the anodes of the LEDs are connected vertically leaving me with 9 columns. The columns are driven by 2 74HC595 shift registers that are daisy chained together. The 9 anode wires are connect to the 2nd shift register (the one that has been daisy chained). Since, only 9 out of the 16 pins are being used, 7 pins of the first register (the one that is connected to the Arduino NANO) are unused. The layer pins are switched to ground through a transistor. There are three transistors, one for each layer, and Digital Pins 7,6,5 are connected to the base of the transistors.

My code for my Random pattern works as expected, However the code for the pattern Wipe does not. This pattern is supposed to turn on all the LEDs of only one layer at a time and move up through the layers. Instead the whole cube lights up and stays on. I've tried other patterns besides the ones listed here and they also do not work as intended.

Here is my code:

#include <SPI.h>

int latchPin = 10;
int clockPin = 13;
int dataPin = 11;
int resetPin = 4;

int layer1Data[2];// stores the bytes of LED data into two 8 bit chunks
int layer2Data[2];
int layer3Data[2];
int k = 0; // used to switch layers

/*
 * TOP DOWN VIEW OF 3x3x3 CUBE
 2 1 0
 5 4 3
 8 7 6

 There are a  total of two 74HC595 shift registers daisy chained.
 Each LED can be addressed using a 16-bit value.
 Column 0 is hooked up to the last pin of the 2nd Shift Register. Therefore its 16 bit value is 100000000000000 = 32768. Next to 0 is 1 so its decimal value is 16384. Since we only use 
 9 of the 16 pins the least significant 7 pins on the shift register are unused.
 */



void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
  SPI.begin();  
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  
  
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(resetPin, OUTPUT);

  pinMode(5, OUTPUT);  layer 1 
  pinMode(6, OUTPUT); layer 2
  pinMode(7, OUTPUT); layer 3

  digitalWrite(resetPin, HIGH);

  cli();//stop interrupts
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  
  OCR1A = 10;
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei(); // allow interrupts
}

ISR(TIMER1_COMPA_vect){

digitalWrite(resetPin,HIGH);
  digitalWrite(latchPin, LOW);
// If we are on the First Layer, Output the LED data for Layer 1
  if(k == 0){
    for(int i = 0; i < 2; i++){
      SPI.transfer(layer1Data[i]);
    }
    digitalWrite(latchPin,HIGH);
    PORTD = B10000000; //Turn On Layer 1
    
  }
// If we are on the Second Layer, Output the LED data for Layer 2
  if(k == 1){
    for(int i = 0; i < 2; i++){
      SPI.transfer(layer2Data[i]);
    }
    digitalWrite(latchPin,HIGH);
    PORTD = B01000000; //Turn on Layer 2
  }
// If we are on the Third Layer, Output the LED data for Layer 3
  if(k == 2){
    for(int i = 0; i < 2; i++){
      SPI.transfer(layer3Data[i]);
    }
    digitalWrite(latchPin,HIGH);
    PORTD = B00100000; //Turn on Layer 3  
  }
k++; 
  if (k > 2){ 
  k = 0;   //take another layer in the loop
  } 

  digitalWrite(resetPin,LOW);
}


void loop() {
  
 //Random();
 //Wipe(); 
}


void Random(){
 
  for(int i = 0; i < 2; i++){
    layer1Data[i] = random(255);
    layer2Data[i] = random(255);
    layer3Data[i] = random(255);
    delay(110);
  }
  
}

void Wipe(){
  
  layer1Data[0] = 255;
  layer1Data[1] = 255;
  delay(100);

  layer2Data[0] = 255;
  layer2Data[1] = 255;
  layer1Data[0] = 0;
  layer1Data[1] = 0;
  delay(100);

  layer3Data[0] = 255;
  layer3Data[1] = 255;
  layer2Data[0] = 0;
  layer2Data[1] = 0;
  delay(100);

   layer3Data[0] = 0;
  layer3Data[1] = 0;
  

   
}

1 Like

Well done using code tags for the code!

You use lots of words but please post schematics. In engineering the Shakespeare way of telling is no good.

Where do you set the layer bits on PORTD low?

Here is a schematic of all the wiring. Each output on the registers has a 330 ohm resistor that is not shown.

I do not set the layer bits low in my code. Where would you recommend putting them?

Here is a top down picture of the cube to show the column numbers that I am referencing:
image

You need to multiplex the patterns. That means loading the pattern for plane/layer 1 and then displaying it for a short period. You then load the pattern for plane 2 into the shift register, shut off plane 1, latch in the pattern over for plane 2 and then turn on plane 2 for a short period. This is repeated for plane 3. When that's done you need to go back to plane 1 again. You're pretty close to that now. Maybe try this in your ISR:

ISR(TIMER1_COMPA_vect)
{
    digitalWrite(resetPin,HIGH);
    digitalWrite(latchPin, LOW);
    // If we are on the First Layer, Output the LED data for Layer 1
    if(k == 0)
    {        
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer1Data[i]);
        }
        PORTD &= ~B00100000; //Turn off Layer 3    
        digitalWrite(latchPin,HIGH);
        PORTD |= B10000000; //Turn On Layer 1    
    }

    // If we are on the Second Layer, Output the LED data for Layer 2
    if(k == 1)
    {
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer2Data[i]);
        }
        PORTD &= ~B10000000; //Turn Off Layer 1    
        digitalWrite(latchPin,HIGH);
        PORTD |= B01000000; //Turn on Layer 2
    }
    
    // If we are on the Third Layer, Output the LED data for Layer 3
    if(k == 2)
    {
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer3Data[i]);
        }
        PORTD &= ~B01000000; //Turn Off Layer 2
        digitalWrite(latchPin,HIGH);
        PORTD = B00100000; //Turn on Layer 3  
    }
    
    k++; 
    if (k > 2)
    { 
        k = 0;   //take another layer in the loop
    } 

    digitalWrite(resetPin,LOW);
}

You might need to play with the timing a bit; turn off the previous layer a longer period of time before turning on the next otherwise you might get some "smear" since the transistors take a non-zero amount of time to shut off.

I implemented your version of the ISR and I am still getting the same result. When I slow down the time interrupt to 1 Hz, The pattern works as it should. It lights up all the LEDs in plane/layer 1 then turns off, all the LEDs in plane/layer 2 light up and turn off, then all the LEDs in plane/layer 3 light up and then it repeats. Whenever the ISR was on its normal value, the LEDs in plane/layer 1 all light up, then all the LEDs in plane/layer 2 light up while all the LEDs in layer 1 were on, then all the LEDs in the third layer lit up all while all the LEDs in layers 1 and 2 were on and then the whole cube just stayed on. Could it be that there is a flaw in the way I am coding the patterns and how I am storing the byte values?

With a clock of 15.625kHz and an OCR1A of 10 you're getting COMP interrupts every 640uS. Three of these gives a "refresh" rate of something like 520Hz. It's possible your bipolar transistors with 4400-ohm base resistors are simply too slow in switching on and off for that frame rate.

I suggest trying a refresh rate of ~30Hz or about 11mS between interrupts. This would be an OCR1A value of ~173.

You might get better performance if you changed the layer transistors to logic-level MOSFETs with 100-ohms or less on the gates.

With a OCR1A of 173 I am getting the same results, only that now the LED flicker is a lot more noticeable.

Can you change your base resistors to, say, 270 or 300-ohms?

I changed the base resistors to 300 ohms. I ran the same code and nothing lit up, Vbe was around 1.35 V for each transistor.

Bizarre that nothing lights up. How are you measuring the Vbe? Using a scope?

If you return the 4400-ohm resistors does its behavior revert to "normal"?

I was initially confused by this as calling them "common cathode LEDs" implies that you are using RGB LEDs but you then refer only to nine column drives, not 27.

When someone describes this ridiculously tedious way of driving a 3 x 3 x 3 (monochrome) cube, I have to presume this is their tentative approach to building an 8 x 8 x 8 cube, as for a 3 x 3 x 3 or a 4 x 4 x 4 monochrome cube, the only sensible approach is to use a MAX7219. :sunglasses:

The Vbe was measured using a Digital Multimeter. Whenever I returned to the 4400 ohm resistors, the cube reverted back to normal.

Are you sure they were 300-ohm and not 300K or 3Mohm?

Yes, they are 300 ohm resistors with 1% tolerance.

The port pins are good to source at least 20mA at 5V VCC (see 328P datasheet table 28.2.)

With a Voh of 4.1V and a Vbe of ~0.7V the current drawn from a port pin would be 4.1 - 0.7 / 300 or 11mA.

Looking at Fig 4 of the datasheet for the 2N2222A the Vce voltage for a base current of 11mA will be about 0.1V for a collector current of 150mA.

IOW, the transistor should be good for a base current of 11mA and the port pins should be good to source this so I don't know why you're seeing the result you are with a 300-ohm base resistor.

Hello, recently I have been trying to control my 3x3x3 LED Cube that I built using my Arduino NANO. The way I have been coding my cube is having an array store the bit values of all the LEDs I want to turn on for the layer. For my 3x3x3 I have three of these arrays one for each layer. Then in my ISR, the layer data gets shifted out based upon which layer we are on. However, whenever I create some patterns to run they do not work as intended. I feel as if there is a simpler way to store the data of the LEDs I want to turn off and then multiplex the layers. If anyone has experience with these types of builds, how would you go about it?

Here is a picture of my schematic:

Here is the code:

#include <SPI.h>

int latchPin = 10;
int clockPin = 13;
int dataPin = 11;


int layer1Data[2];// stores the bytes of LED data
int layer2Data[2];
int layer3Data[2];
int k = 0; // used to switch layers

/*
 * TOP DOWN VIEW OF 3x3x3 CUBE
 2 1 0
 5 4 3
 8 7 6

 There are a  total of two 74HC595 shift registers daisy chained.
 Each LED can be addressed using a 16-bit value.
 Column 0 is hooked up to the last pin of the 2nd Shift Register. Therefore its 16 bit value is 100000000000000 = 32768. Next to 0 is 1 so its decimal value is 16384. Since we only use 
 9 of the 16 pins the least significant 7 pins on the shift register are unused.
 */



void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
  SPI.begin();  
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  
  
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(resetPin, OUTPUT);

  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);

  digitalWrite(resetPin, HIGH);

  cli();//stop interrupts
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 100;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei(); // allow interrupts
}

ISR(TIMER1_COMPA_vect)
{
    // If we are on the First Layer, Output the LED data for Layer 1
    if(k == 0)
    {        digitalWrite(latchPin, LOW);
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer1Data[i]);
        }
        PORTD &= ~B00100000; //Turn off Layer 3    
        digitalWrite(latchPin,HIGH);
        PORTD |= B10000000; //Turn On Layer 1    
    }

    // If we are on the Second Layer, Output the LED data for Layer 2
    if(k == 1)
    {digitalWrite(latchPin, LOW);
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer2Data[i]);
        }
        PORTD &= ~B10000000; //Turn Off Layer 1    
        digitalWrite(latchPin,HIGH);
        PORTD |= B01000000; //Turn on Layer 2
    }
    
    // If we are on the Third Layer, Output the LED data for Layer 3
    if(k == 2)
    {digitalWrite(latchPin, LOW);
        for(int i = 0; i < 2; i++)
        {
            SPI.transfer(layer3Data[i]);
        }
        PORTD &= ~B01000000; //Turn Off Layer 2
        digitalWrite(latchPin,HIGH);
        PORTD |= B00100000; //Turn on Layer 3  
    }
    
    k++; 
    if (k > 2)
    { 
        k = 0;   //take another layer in the loop
    } 

}


void loop() {
  
 //Random();
 Wipe();
 //Fill(); 
}


void Random(){
 
  for(int i = 0; i < 2; i++){
    layer1Data[i] = random(255);
    layer2Data[i] = random(255);
    layer3Data[i] = random(255);
    delay(110);
  }
  
}

void Wipe(){
  

  layer1Data[0] = 255;
  layer1Data[1] = 255;
  layer2Data[0] = 0;
  layer2Data[1] = 0;
  layer3Data[0] = 0;
  layer3Data[1] = 0;
  delay(100);

  layer2Data[0] = 255;
  layer2Data[1] = 255;
  layer1Data[0] = 0;
  layer1Data[1] = 0;
  layer3Data[0] = 0;
  layer3Data[1] = 0;
  delay(100);

  layer3Data[0] = 255;
  layer3Data[1] = 255;
  layer2Data[0] = 0;
  layer2Data[1] = 0;
  layer1Data[0] = 0;
  layer1Data[1] = 0;
  delay(100);

   layer3Data[0] = 0;
  layer3Data[1] = 0;
  layer1Data[0] = 0;
  layer1Data[1] = 0;
  layer2Data[0] = 0;
  layer2Data[1] = 0;

  delay(200);
  layer3Data[0] = 255;
  layer3Data[1] = 255;
  layer2Data[0] = 255;
  layer2Data[1] = 255;
  layer1Data[0] = 255;
  layer1Data[1] = 255;
  delay(200);
  

   
   
}

void Fill()
{
  for(int i = 0; i < 2; i++){
    layer1Data[i] = 255;
    layer2Data[i] = 255;
    layer3Data[i] = 255;
    delay(100);
  }
}

void clearCube(){
  for(int i = 0; i < 2; i++){
    layer1Data[i] = 0;
    layer2Data[i] = 0;
    layer3Data[i] = 0;
    delay(1000);
  }
}

Better Pic of Schematic: