3x3x3 LED Cube

Our 3x3x3 RGB LED Cube was developed as a kit by us, but is now being made and sold directly through SparkFun Electronics. Be sure to check out its product page and pick one up.

The source code, both as a standalone project and as an Adruino project, are available from the product page.

This tutorial will give a general description of how the code works as well as how to modify it for your own custom patterns.

File Overview

The code consists of four main sections, the main file (main.c or cube.ino), hardware drivers and descriptions (hardware.c/h), display driver (driver.c/h), and the patterns (patterns.c/h).

To make the cube perform your own custom patterns you only need to add a function to patterns.c/h and call it from the main file.

Since the main file is starting point of execution lets start there.

The Main File

The main file is fairly small and simply consists of some initialization then an endless while loop.

Take a look at the initialization.

USART_Init(21); //baud 115200
init_timer1(); //set up the timer and start the main interrupt

The first line sets all the directions of the IO pins.

The second line is used to initialize the serial port in the case that it is used to control the cube.

The third line sets up the timer used to keep the cube display refreshed. Since the display driver uses interrupts, you don't have to worry about calling some function periodically.

The last line just shows a nifty test pattern to make sure your cube is working. If you don't like the delay when you plug your cube in you can comment out this line.

Now onto the while loop!

while (1)

All that this code does is simply call the pattern functions. You can rearrange the function calls to allow for different execution order, or even get fancy and randomize them. The arguments to each function dictate how long it will run.

The first line that is commented out, serial_control() is used to make the cube listen to the serial port. If you uncomment this line the code will get stuck in the serial_control() method and none of the other patterns will execute. However, it can be cool to use to connect to your computer and do something like the following video.

In this video, the song is being analyzed as it plays and generating a visual for the cube. The pluggin runs on Linux and its source is avaible here.

Driver Functions

Before we get into how to create your own patterns, there are a handful of helper functions in the driver.h file to checkout.

void clear_buffer (void);
/* clear_buffer simply writes 0 to all of cube_buf */

void fill_buffer (uint8_t r,uint8_t g,uint8_t b);
/* fill_buffer fills the buffer with the color defined by r g and b */

void tint_buffer (uint8_t r,uint8_t g,uint8_t b);
/* tint_buffer adds r g and b to the whole buffer */

void display_buffer (void);
/* dispaly_buffer copies cube_buf to clear_buf */

uint8_t put_XYZ (uint8_t x,uint8_t y,uint8_t z,uint8_t r,uint8_t g,uint8_t b);
/* put_XYZ changes the color of the LED at the 3D coordinate defined by
x y and z to the color defined by r g and b. */

uint8_t mix_XYZ (uint8_t x,uint8_t y,uint8_t z,uint8_t r,uint8_t g,uint8_t b);
/* mix_XYZ does the same thing as put_XYZ but it adds the color to the
existing color in cube_buf instead of over writing it */

uint8_t single_XYZ (uint8_t x,uint8_t y,uint8_t z,uint8_t color,uint8_t value);
/* single_XYZ is the same as mix_XYZ but it only changes one color defined
by color, 0=red 1=green and 2=blue. The value added is defined by value. */

void delay_ms(uint16_t ms);
/* delay_ms delays for "ms" amount of ms. This is more accurate than _delay_ms() as
it is based off the ISR timer. */

void delay_vblank(uint16_t blanks);
/* delay_vblank gives an easy way to make sure the full image was displayed before
updating the buffer.

blanks is the number of frames to wait before returning. 60 is around 1 second */

void start_vblank(uint16_t blanks);
/* start_vblank is used to set how many blanks should happen once end_vblank is called.
By having two separate functions you can start the delay, generate the frame, then
delay the remaining time so you get the same delay regardless of the time it takes
to generate the frame */

void end_vblank(void);
/* waits out the remaining time from start_vblank */

These should be used by your functions in patterns.c/h to generate frames.

The only functions that may not be obvious what they do is the vblank methods.

delay_vblank() is used to simply delay a specified number of frames. If you want to ensure that your pattern is displayed for at least a full frame this is the method to use. If you are simply trying to delay for a set amount of time, you should use delay_ms().

The functions start_vblank and end_vblank are useful for setting the frame rate you want your pattern to run at. It is similar to delay_vblank except that start_vblank will return immediately. When you call end_vblank, it will return only once a set number of frames have been rendered since start_vblank as called. That allows you to generate a new frame while keeping track of the time between frames. If your code takes longer than the specified number of frames, end_vblank will simply return.

You should only use the delay functions provided here. The built in delay functions, like _delay_ms(), will not work due to the interrupts. They will delay for much longer than expected and can even vary in delay length. The provided delay_ms() function will be much more accurate.


To help show how you can create your own patterns, lets take a look at flash_rand.

void flash_rand (uint16_t len)
  uint16_t ctr=0;
  for (ctr=0;ctr<len;++ctr)
    fill_buffer(rand() % MAX_COLOR,rand() % MAX_COLOR,rand() % MAX_COLOR);

This code simply fills the cube with a solid random color over and over again.

Notice the use of the MAX_COLOR constant. This is the greatest value you should supply and will result in that channel of the LED being fully on. In the current code this value is 50. It can be tweaked to increase the frame rate or increase the color depth. If you increase the color depth too much you will start to see the cube flicker. 50 was found to be a nice compromise.

The cube is double buffered, meaning whatever pattern you draw will not actually be shown until you call display_buffer(). Once display_buffer() is called the frame you were working on will be copied into the driver's own display buffer. Don't forget to call display_buffer() when you have a new frame.

When you flip the buffer, it will still be dirty. You can use clear_buffer() to set it to black if you need to.

Once you add a function to patterns.c don't forget to add its prototype to patterns.h and add it to the while loop in the main file.

Hopefully this gives you enough information to start playing with your LED cube and make some awesome patterns!