RGB LED cubes. Just ask me!

Hi All.
I have looked around and see a lot of information out there about RGB LRD cubes, some good some not so good, IMHO.

Thought I would put up a thread about my cube and things I know now, after make a cube and running my cube for a few years. My Cube is 8 x 8 x12high, using 5mm RGB LEDs. Very much following the concept designed by Kevin Darrah (on http://www.kevindarrah.com)

Check out my cube here RGB LED Cube "Rectangular Prism" Bragging Rights | All modding! | Pinside.com

First thing. Everyone talks about a cube that is 8 LEDs wide, 8 LEDs deep and 8 LEDs high (8 x 8 x 8 = 512 LEDs). WHY? Well I guess that 8 fits AVR architecture nicely.

The issue with this arrangement is there is no center, so consider making a cube with odd numbers EG 9 x 9 x 9. Having a center will make your animations work better in many cases. My next cube will contain an odd number of LEDs. As for programming, If you write to your LED array a bit like the code below, having any number of LEDs will work. Yes you will require more memory for more LEDs.
Height, I found having a bit more room in the height good for animations. I have room for a ball to bounce. More room for the rain to fall. My cube is 150% higher than its width. Adding height does not make programming any harder.

//*********** Defining the Cube *************
#define BAM_RESOLUTION 4    // EG 4 bit colour = 15 variation of R, G & B (4096 colours)
const  byte Size_Y = 12;//Number of Layers Y axis (levels/Layers)
const  byte Size_X = 8; //Number of LEDs X axis (Left to right across front)
const  byte Size_Z = 8; //Number of LEDs Z axis (front to back)
const  int Size_Layer = Size_X * Size_Z; // Number of LEDs per Layer
const  int Size_Cube = Size_Layer * Size_Y; //Number of LEDs whole Cube.
const  int Size_CW = ((1 << BAM_RESOLUTION) - 1) * 6; //Number of steps in colour wheel. Why x 6, cos any more will give repeat colours.

void LED(int CY, int CX, int CZ, int CR, int CG, int CB) { //****LED Routine****LED Routine****LED Routine****LED Routine

  // First, check and make sure nothing went beyond the limits, Limits are either 0 or Size of the Y,X,Z axis -1 for locations, and 0 or Bam_Resolution - 1 for brightness/colour

  CY = constrain(CY, 0, Size_Y - 1);//Cube Y axis
  CX = constrain(CX, 0, Size_X - 1);//Cube X axis
  CZ = constrain(CZ, 0, Size_Z - 1);//Cube Y axis
  CR = constrain(CR, 0, (1 << BAM_RESOLUTION) - 1); //Cube Red
  CG = constrain(CG, 0, (1 << BAM_RESOLUTION) - 1); //Cube Green
  CB = constrain(CB, 0, (1 << BAM_RESOLUTION) - 1); //Cube Blue


  //There are Y * X * Z LEDs (Size_Cube) in the cube, so when if we write to level (Y) 2, panel (X) 5, row 4 (Z), that needs to be translated into a number from 0 to Size_Cube then devided by the number of bits per byte
  // Since we use an integer we are left with the byte number, the remainder is dropped off. EG (2*64 + 5*8 + 4)/8 = 21.5, the int of this is WhichByte number 21.(Assuming a cube 8*8*8 LEDs)
  int WhichByte = (CY * Size_Layer + CX * Size_X + CZ) / 8;

  // This next variable is the same thing as before except we don't devide by 8, so we get the LED number 0 to Size_Cube.
  //Then we find which bit of the byte is required. EG (From Above) (2*64 + 5*8 + 4) = LED# 172 - (8 * WhichByte) = 4
  int WhichBit = (CY * Size_Layer + CX * Size_X + CZ) - (8 * WhichByte) ;
  // So WhichByte 21 WhichBit 4 is the location we will write too for LED 172.

  //Now we write to our Cube array if Red, Green and Blue are on or off, using Bit angular modulation.
  for (byte I = 0; I < BAM_RESOLUTION; I++) {
    //*** RED ***
    bitWrite(red[I][WhichByte], WhichBit, bitRead(CR, I));

    //*** GREEN ***
    bitWrite(green[I][WhichByte], WhichBit, bitRead(CG, I));

    //*** BLUE ***
    bitWrite(blue[I][WhichByte], WhichBit, bitRead(CB, I));
  }

}//****LED ROUTINE END****

Colours. I picked 4 bit colour. Why? Well it made sense at first. I see others with 8 bit and 6 bit colour.

4 bit colour will give you a possible 4096 colours. Straight up you can see that this is a fair few colours for your cube. Remember that more colour requires more memory, but that is not why I recommend 3 bit colour. If you look at Animation code that incorporate colour wheels (As mine does) you will see that the code will make a step change to next colour of 20 to 100 or more steps at a time.
This is cos each one step change is so slight that you don’t see the change. So why have 16.8 million colours when you are only going to use around 100 or so. My colour wheel generates 90 colours, and I still use steps of 10 or more, so 3 bit colour make lots of sense for you cube.

Check out my colour wheel spread sheet. Cell B1 is where you input the bam resolution of you cube. I’m using 4 bit (15 possibilities per colour). Then press “Update Colour wheel”( I couldn’t get that to auto update.) .) ** If you want a copy of the colour wheel Spread sheet, just ask. Not allowed to attach/upload here.

I have code for Mega and Due, if you want it just let me know.

The next thing is making the cube bright. I recommend that you use LED drives for your cube. Main reason here is you can set the current for each driver with one resistor. I used MBI5026 sink LED drivers. These also work at 3.3v so also works with my Due.

As for updating the LED drivers, using SPI works well with both the Mega and DUE. With the mega I did not use the Arduino Library version; I wrote my only lines of code, making the update much quicker around 35µs for all 192 bits/layer.

inline static uint8_t myTransfer(uint8_t T_data){			// Fast transfer - If error use more asm statements - Transfer time 33 µs with two asm volatile("nop");
			SPDR = T_data;
			asm volatile("nop");asm volatile("nop");				//These statements make the process do nothing while the data is transferred. It is faster than using the test method "while(!(SPSR & (1<<SPIF)));" but may error.
		}

However I have not had the same success with writing SPI for the DUE and have resorted to using the Due SPI library code, around 45µs for all 192 bits/layer.

I’m using 4 bit colour!!
Originally I use a bit of code that took 15 cube cycles to update all 15 possibilities of shades per colour (using 4 bit colour).

This does not give you the brightest cube.
The reason is the LEDS are off more than they need be. Every 144 µs the interrupted would occur (144 µs was the best timing for flicker free cube), so for every 144 µs the cube is off for 35 µs while the Drivers are updated.

What I have done now is to change the interrupted frequency instead of having 15 set times periods, I now have 4. Each one twice the lengths of the last until the longest period is reached then we start again. So now the cube interrupts are set at 96 µs, 192 µs, 384 µs and 768 µs.
Now my cube is on 1300 µs out of a possible 1440 µs(92% on time) Verse the old way 1635 µs out of a possible 2160 µs.(75% on time).

Mega code for interrupt timing.

  DataTransfer = DataTransfer + Size_Layer / 8; //increment the DataTransfer variable by Size_Layer / 8 = next layer, which is used to load the shiftout buffer.
  
if (++CubeLevel >= Size_Y) {				//Reset to zero if max is reached, all layers of the cube have been on.
  CubeLevel = 0;
  DataTransfer = 0;

  if (++BAM_Bit >= BAM_RESOLUTION) {		//Resets the minimum period of "On time" for selected Bam resolution. 

    switch (BAM_RESOLUTION) {
      case 8:
        OnTime = 1; 						//left at one otherwise we get flicker, Makes 8 bit colour the least brightest.
        break;
      case 7:
        OnTime = 3;
        break;
      case 6:
        OnTime = 6;
        break;
      case 5:
        OnTime = 12;
        break;
      case 4:
        OnTime = 24;
        break;
      case 3:
        OnTime = 48;
        break;
      case 2:
        OnTime = 96;
        break;
      default:
        OnTime = 192;
        break;
    }//switch (BAM_RESOLUTION)
    BAM_Bit = 0;

  } else {
    OnTime <<= 1; //This doubles that last interrupt interval.
    
  }//if (++CubeLevel >= Size_Y)

  OCR1A = OnTime + MinInterruptTime ; //Updates the interrupt clock with new time interval.

}//************** END Tranfer ************************/

Note, by changing the BamResolution, this code will work with 2 to 8 bit colour.
#define BAM_RESOLUTION 4 // EG 4 bit colour = 15 variation of R, G & B (4096 colours)#

DUE code for interrupt timing. I'm using a timer Library to manage the timers on the ARM chip of the due.

DataTransfer = DataTransfer + 8; //increment the DataTransfer variable by 8, which is used to load the shiftout buffer, next level is the next 8 bytes in the arrays
  //Reset to zero if max is reached, all layers of the cube have been on.
  if (++CubeLevel >= Size_Y) {
    CubeLevel = 0;
    DataTransfer = 0;

    if (++BAM_Bit >= BAM_RESOLUTION) {

      //These sets the minimum time a layer will be, depending on colour resolution. EG 4 bit colour 96µs then 192µs,384µs and 768µs.
      switch (BAM_RESOLUTION) {
        case 8:
          OnTime = 6;       //µs
          break;
        case 7:
          OnTime = 12;      //µs
          break;
        case 6:
          OnTime = 24;      //µs
          break;
        case 5:
          OnTime = 48;      //µs
          break;
        case 4:
          OnTime = 96;      //µs
          break;
        case 3:
          OnTime = 192;     //µs
          break;
        case 2:
          OnTime = 384;     //µs
          break;
        default:
          OnTime = 768;     //µs
          break;
      }//switch (BAM_RESOLUTION)
      BAM_Bit = 0;

    } else {
      OnTime <<= 1; //This doubles the last interrupt interval. EG 4 bit colour 96µs then 192µs,384µs and 768µs.

    }//if (++CubeLevel >= Size_Y)
    Timer3.start(OnTime);   //Updates the interrupt clock with new time interval.

  }//************** END Tranfer ************************/

Another advantage to the increasing time method for updating, it gives more time back to the processor to compute the next part of the animation.

I sent you a message on here. If you get a chance could you take a look at it?

OK, so over the past two years I have been mucking around with this cube.
I have changed from an Arduino Mega to Due.
Changed how the cube works and made a few new animations.

Uploaded a new better quality video to YouTube….
Hope someone watches it….

My Cube is 8 x 8 x12high

That's not a cube then, is it? You could call it a "cuboid"!

Also, this is how you post Link on this forum.

But, well done indeed on the construction. Looks amazing.

I have built 4x4x4 single colour cubes of my own design. I will tackle an rgb 4x4x4 cube one day soon, but was planning on using ws2812b leds for that.

3D snake game. Controlled by gestures. That's what I'd do with that cube.

PaulRB:
That's not a cube then, is it? You could call it a "cuboid"!

Also, this is how you post Link on this forum.

But, well done indeed on the construction. Looks amazing.

I have built 4x4x4 single colour cubes of my own design. I will tackle an rgb 4x4x4 cube one day soon, but was planning on using ws2812b leds for that.

Thanks Paul (How to post a link), still learning my way around on this site.

and you also correct, its a Rectangular prism. But I have found that you don't get a good response to titles like

"8x8x12 Rectangular Prism", although I did make mention of it on my YouTube, and "Cube" is easier to spell....

My first cube was also a 4x4x4 Red LED cube.

As for my "Cuboid", I have it looking good and bright now with minimal off time.

The issue now to improve is to reduce the time between each byte of data being sent via SPI at each update. Right now the start of one byte to the start of the next is 1.7µs. From the last bit of one byte to the first bit of the next is around 1.3µs, 3 time longer than the whole byte took to send. I was able to deduce this time on my Mega, but struggling on my Due.

Chrisbee:
As for my "Cuboid", I have it looking good and bright now with minimal off time.

The issue now to improve is to reduce the time between each byte of data being sent via SPI at each update. Right now the start of one byte to the start of the next is 1.7µs. From the last bit of one byte to the first bit of the next is around 1.3µs, 3 time longer than the whole byte took to send. I was able to deduce this time on my Mega, but struggling on my Due.

Why does that matter? The delay between bytes is not during off time, is it? Or is it preventing you from reaching higher refresh rates and therefore achieving higher colour bit-resolution/depths?

Here's a thought:

   switch (BAM_RESOLUTION) {
      case 8:
        OnTime = 1; //left at one otherwise we get flicker, Makes 8 bit colour the least brightest.
        break;
      case 7:
        OnTime = 3;
        break;
      case 6:
        OnTime = 6;
        break;
      case 5:
        OnTime = 12;
        break;
      case 4:
        OnTime = 24;
        break;
      case 3:
        OnTime = 48;
        break;
      case 2:
        OnTime = 96;
        break;
      default:
        OnTime = 192;
        break;
    }//switch (BAM_RESOLUTION)

Is BAM_RESOLUTION a #define constant? If so, will the c compiler be clever enough to figure out that only one "case" will ever be executed, and optimise away the rest of the switch statement? Either way, why not just have a #define for MINIMUM_ON_TIME (which you change when you change BAM_RESOLUTION), and remove the switch statement code entirely?

Or maybe do it like this:

#if BAM_RESOLUTION == 8
        OnTime = 1; 						//left at one otherwise we get flicker, Makes 8 bit colour the least brightest.
#elif  BAM_RESOLUTION == 7
        OnTime = 3;
...
#elif  BAM_RESOLUTION == 2
        OnTime = 96;
#else
        OnTime = 192;
#endif

PaulRB:
Why does that matter? The delay between bytes is not during off time, is it? Or is it preventing you from reaching higher refresh rates and therefore achieving higher colour bit-resolution/depths?

Thanks for taking an interest. And YES the delay I'm referring to is during the "Off Time". For me the cube needs to be as bright and as vibrant as possible, so off time is the enemy.

The interrupt code take around 51µs to complete, of which 44µs is off time, measured with my scope.
In this time, I change the layer and update the registers with the new data. The layer changing is quick, maybe 1µs, the rest of the time is updating shift reg. This is the "Slow" code.

This is the code involved.

for (byte myData = 0; myData < Size_Layer / 8; myData++) {
    SPI.transfer(green[BAM_Bit][DataTransfer + myData]);
  }
  for (byte myData = 0; myData < Size_Layer / 8; myData++) {
    SPI.transfer(red[BAM_Bit][DataTransfer + myData]);
  }
  for (byte myData = 0; myData < Size_Layer / 8; myData++) {
    SPI.transfer(blue[BAM_Bit][DataTransfer + myData]);
  }

Here are some pics from my scope of the SPI clock out to the registers.

One/ the time it takes to shift out 24 bytes(43.4µs) . The layer data.

Two/ the time from one byte to the next(1.79µs). Data shift out is 21MHz - SPI clock divider = 4

Three/ the time from the last bit of one byte to the first of the next (1.42µs). The code is spending a lot of time between byte shift outs.... This is where we need to improve.

I’m sure it is the SPI library code slowing things down. I was able to get my Mega to do the updating shift reg bit in 32µs by not using the SPI library and writing my own SPI sending code. Below.

inline static uint8_t myTransfer(uint8_t T_data){	// Fast transfer
//If error use more asm statements - Transfer time 33 µs with two asm volatile("nop");

			SPDR = T_data;
			asm volatile("nop");asm volatile("nop");

//These statements make the process do nothing while the data is transferred. 
//It is faster than using the test method "while(!(SPSR & (1<<SPIF)));".
		}

I think the SPI library is waiting for flags and listening, ETC. Things that I don’t require. Things that take time. My guess anyway.
Also you are correct, I don’t need to have the Case statement at all, but it is there to make the code transportable.

YES the delay I'm referring to is during the "Off Time".

Surely, that's what you need to fix? Shifting out data should be done during On-time. During Off-time, you want to do the absolute minimum, switching off a layer, latching the serial data and switching on the new layer. Does your hardware design prevent that?

I don't need to have the Case statement at all, but it is there to make the code transportable

But you have made other changes which make your code less transportable, such as directly accessing the AVR SPI registers instead of using the SPI library.

PaulRB:
Surely, that's what you need to fix? Shifting out data should be done during On-time. During Off-time, you want to do the absolute minimum, switching off a layer, latching the serial data and switching on the new layer.

Ummmm! I was under the impression that you must blank the registers before sending data. The timing diagram also has OE (blanking) as high during data transfer and low for LED's on.

I'll give this a go later today and let you know. Just gave this a go, you get ghosting. The OE must be high during data transfer. Good idea but..

If you get ghosting, you must be doing something wrong. Does your circuit make the latch enable pins available? You can transfer data all day long but it won't affect the outputs of the led drivers until you latch the data. So as long as you blank the display before you do that, you can transfer data while the leds are on.

Just thought I would add a bit to this thread rather than start a new one.
I have a new animation “Rubik’s Cube” in an RGB LED cube.
To see the cube in action Chrisbee's Rubik's Cube Part 1

then a bit about the code watchChrisbee's Rubik's Cube Part 2

If anyone wants any of this code, just let me know.

Hey, need a help regarding 8x8x8 LED cube.

I was learning to make led cubes, and use this as a reference:youtube tutorial for 8x8x8 cube

But I accidently made a common anode base rather than a common cathode base. So can I fix this with some code or I have to redo the whole cube ?

You will need to re-design the driver circuits. Code changes will also be needed. You will need to decide if it will take less time to do that compared to re-making the cube.

Also, please do not hijack existing forum threads. Your cube does not have the same design as the OP's cube, so your question is not relevant to this thread. You should start your own thread.

Hi,

I have made RGB 8x8x8 led cube, using driver ic MBI5026. I have seen your cube on youtube it is amazing.

I have no knowledge of Arduino coding and trying to learn.

I would request you to kindly provide the complete code for mega board.
my email: is yomika2@gmail.com.