Introduction: 3X3 LED Cube Programming Tips (Arduino Based)

About: former musician. I have made kites, music instruments, electronic doodads, pitching machines, stomp rockets, water rockets, windless windchimes, circuit bent toys, animatronics, clock escapements, CNC router, …

How to make that cube do what you want it to!

I followed the instructable here and built a 3x3 LED cube run by an Arduino. I was delighted! But it could only do one routine. Again and again and again and again and again.... So I decided to try and program a different routine. I needed a change. Do you?

If you study the code in the cube's Arduino sketch you can see all the instructions right in front of you. To me, it looks like the Matrix, streaming by in all its digital confusability. I needed a graphic interface to help me code the routines. I FOUND ONE!!! But it wrote code that contained extra information that I did not want. How to rewrite all those instructions? Read on!

Be prepared, this is a twisted solution, combining an (apparently) anonymous website with a macro in my word processor, but in the end I have come up with a routine that is very powerful indeed!

Step 1: Graphic Pattern Generator

I searched and searched for "LED Cube Programmers" on the web. I found some, but they were for PIC based cubes. I changed the title of my search to "LED Cube Pattern Generator" and BINGO! I finally found this one, and it allowed my simple mind to grasp the relationship between little flashing lights and gobs of 1's and 0's. I could punch little buttons on the screen and see a simulation of what I was doing! Just what I wanted. This generator allows one to use a cube of any size! Press the "new" button and you will be prompted to set up your cube.

'Insert' creates a new frame, just like a simple animation program. We are going to make a 'flip book,' in effect. Each page is programmed and then you hit the insert button to add another frame. Notice the speed parameter near the bottom of the window marked 'frames.' F and E mean 'Fill' and 'Erase.' Program a few frames and hit the 'Play' button. Your animation will be shown in the 'preview' window. Simple! When you are done and want to save your new light show, hit the 'File' button. You will see the buttons marked 'New',' to Code',' Load' and 'Save' appear. Press 'To Code,'and a window will open, asking you to choose in which order you wish to display your data. You can go back and change this later if you get it wrong. Just choose one for now.

You will then see a window with all your animation's coded bytes.

Step 2: Clean Up That Code, Please!

I select all the code in the window and copy. Ok, now here comes the fun part. Open your word processor and paste that code in a new document. I use Libre Office, and have not tried this on other software. You may be able to improve on my crazy method, please share any tips!

Step 3: Rewrite That Mess a Little Bit.

This is what the pattern generator gives me, and it has some extra information I do not want in my sketch, so we need to remove some characters and make it easier for us to use later. First, get rid of the first and last rows of information, all we want is everything in between those two rows. It should look like this:

Step 4: The Solution!

This is a very short example, so there are not many brackets, but if you program a longer animation removing all those brackets would take too much time. Being an inventor (always looking for a solution, probably due to laziness in my case) I set out to find a different way to accomplish that. The solution? MACRO! I did a search and found out why my "record macro" button was not active in LibreOffice: Here is the answer to that question, follow the instructions and you can then record a macro. What this does is allow us to execute a series of edits once, record those actions as a single command and then perform the edit on the entire document quickly. I'll try and guide us through one example of an edit using my macro and LibreOffice.

First I clicked on Record Macro (see pics)
Next I selected all the text using ctrl A
Then I used Find and Replace to edit out the characters in my program information.

In the find and replace menu, enter the open bracket   '{'  at the beginning of the text lines like this: Search for {
in the replace line, leave a blank. Click on 'replace all'. You will get a report of how many symbols were replaced.
That's one! Now we repeat that sequence with the next symbol, the close bracket   ' }' (no quote) and replace it also with a blank.
Next symbol to replace is the '0' followed by lower case letter 'b' (0b) with an upper case 'B' instead. Have a look at the pictures in this step to see what my text is looking like.

After this last Find and Replace step, we click on the button that says 'stop recording,' in order to make this editing function a macro. (see pictures here) You will be prompted to save the macro, try to remember what you call it!

Now erase all the text and save the document as a blank slate for your routine. I called my document 'blank slate' (duh.)
Next time I want to program I can skip the steps to create a new macro, it should already be there for me to use. I can program short routines and save them as text files, then piece together the snippets into a longer routine at a later time.

Step 5: Using the Macro Function to Edit Another Animation

Now we have programmed our word processor to edit the information generated by the pattern generator into a more friendly format that can be used in our sketch. Let's try the whole procedure so far:

Generate a new animation, say three frames of blinks anywhere. Click on New and enter the number of diodes in your cube. Mine is 3,3,3.

Next I will turn on all the lights in the first frame of our animation.
Next step: add another frame with the 'insert' button. Turn off all but one light anywhere.
Click 'insert' once more and add one more frame, all off.

Now we have a new sequence of three frames, time to generate the code.

Click 'File' and then 'To Code' to see the code window.

Select one of the options offered, I chose 'Depth, Height, Width'  (you may have to experiment here)

Select everything inside the code window and copy.

Paste into our word processor document.

Remove the first and last lines as we did earlier.

Select all the text.

Now we want to run that macro. Under 'tools/macro/run macro' we see a list to choose from. You will need to find your macro, probably at the last entry in the list, look around and see if you can find it. This part is difficult for me to describe. I felt that it should have been easier to find, but then I am new to macros. The macro should appear in the window on the right once you have found it.
Click on it and hit run.

That 'should' clean up the code in one step!

Step 6: What Do I Do Now?

Now we have a programmed sequence of frames, and transformed the code to a format that will let us run it inside our Arduino sketch. 'What sketch' you ask? The Arduino sketch I use to drive my cube looks like this:

/*
Based on ledcube.c from Make: September 7, 2007 weekend podcast 
http://blog.makezine.com/archive/2007/09/make_a_pocket_led_cube_we.html

Custom animation programmed by Mark Boszko, http://stationinthemetro.com
*/

#include <avr/pgmspace.h>        // allows use of PROGMEM to store patterns in flash

#define CUBESIZE 3
#define PLANESIZE CUBESIZE*CUBESIZE
#define PLANETIME 3333          // time each plane is displayed in us -> 100 Hz refresh
#define TIMECONST 5          // multiplies DisplayTime to get ms - why not =100?

// LED Pattern Table in PROGMEM - last column is display time in 100ms units
// TODO this could be a lot more compact but not with binary pattern representation
prog_uchar PROGMEM PatternTable[] = {


  B101, B010, B101, B000, B000, B000, B000, B000, B000, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,
  B000, B000, B000, B000, B000, B000, B101, B010, B101, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,
 } 

/*
** Defining pins in array makes it easier to rearrange how cube is wired
** Adjust numbers here until LEDs flash in order - L to R, T to B
** Note that analog inputs 0-5 are also digital outputs 14-19!
** Pin DigitalOut0 (serial RX) and AnalogIn5 are left open for future apps
*/

//int LEDPin[] = {16, 3, 1, 15, 4, 6, 14, 5, 7};
int LEDPin[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10};

//int PlanePin[] = {19, 18, 17};
int PlanePin[] = {13, 12, 11};

// initialization
void setup()
{
  int pin;      // loop counter
  // set up LED pins as output (active HIGH)
  for (pin=0; pin<PLANESIZE; pin++) {
    pinMode( LEDPin[pin], OUTPUT );
  }
  // set up plane pins as outputs (active LOW)
  for (pin=0; pin<CUBESIZE; pin++) {
    pinMode( PlanePin[pin], OUTPUT );
  }
}

// display pattern in table until DisplayTime is zero (then repeat)
void loop()
{
  // declare variables
  byte PatternBuf[ PLANESIZE ];      // saves current pattern from PatternTable
  int PatternIdx;
  byte DisplayTime;        // time*100ms to display pattern
  unsigned long EndTime;
  int plane;      // loop counter for cube refresh
  int patbufidx;   // indexes which byte from pattern buffer
  int ledrow;    // counts LEDs in refresh loop
  int ledcol;    // counts LEDs in refresh loop
  int ledpin;    // counts LEDs in refresh loop

  // Initialize PatternIdx to beginning of pattern table
  PatternIdx = 0;
  // loop over entries in pattern table - while DisplayTime>0
  do {
    // read pattern from PROGMEM and save in array
    memcpy_P( PatternBuf, PatternTable+PatternIdx, PLANESIZE );
    PatternIdx += PLANESIZE;
    // read DisplayTime from PROGMEM and increment index
    DisplayTime = pgm_read_byte_near( PatternTable + PatternIdx++ );   
    // compute EndTime from current time (ms) and DisplayTime
    EndTime = millis() + ((unsigned long) DisplayTime) * TIMECONST;

    // loop while DisplayTime>0 and current time < EndTime
    while ( millis() < EndTime ) {
      patbufidx = 0;    // reset index counter to beginning of buffer
      // loop over planes
      for (plane=0; plane<CUBESIZE; plane++) {
        // turn previous plane off
        if (plane==0) {
          digitalWrite( PlanePin[CUBESIZE-1], LOW );
        } else {
          digitalWrite( PlanePin[plane-1], LOW );
        }

        // load current plane pattern data into ports
        ledpin = 0;
        for (ledrow=0; ledrow<CUBESIZE; ledrow++) {
          for (ledcol=0; ledcol<CUBESIZE; ledcol++) {
            digitalWrite( LEDPin[ledpin++], PatternBuf[patbufidx] & (1 << ledcol) );
          }
          patbufidx++;
        }

        // turn current plane on
        digitalWrite( PlanePin[plane], HIGH );
        // delay PLANETIME us
        delayMicroseconds( PLANETIME );
      }    // for plane
    }    // while <EndTime
  } while (DisplayTime > 0);        // read patterns until time=0 which signals end
}





Step 7: Modifying the Sketch

There a three things I changed in that sketch to make it run on my cube.
1.  The settings for HIGH and LOW level states
2.  The order of the output pins
3.  The data for the routine, which is what this is all really about!

Let's look at this in detail. I have highlighted the words HIGH and LOW here. I reversed them from the original program. You may or may not have too. Experiment.

// initialization
void setup()
{
  int pin;      // loop counter
  // set up LED pins as output (active HIGH)
  for (pin=0; pin<PLANESIZE; pin++) {
    pinMode( LEDPin[pin], OUTPUT );
  }
  // set up plane pins as outputs (active LOW)
  for (pin=0; pin<CUBESIZE; pin++) {
    pinMode( PlanePin[pin], OUTPUT );


This is the order of the outputs the cube is made after. The first row {16, 3, 1, ...} is simply an example. My numbers are directly underneath that row. {2, 3, 4, ...} be sure to include a space after the comma.
Next is the pins for the plane. Same here, first row is an example, next row is the real deal. You can invert your cube with these numbers. Cool!

//int LEDPin[] = {16, 3, 1, 15, 4, 6, 14, 5, 7};
int LEDPin[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10};

//int PlanePin[] = {19, 18, 17};
int PlanePin[] = {13, 12, 11};

The third change is the animation data, the information we have generated and processed so lovingly. Simply copy it from your word processor document and paste it into the program. Like this:
{
  B101, B010, B101, B000, B000, B000, B000, B000, B000, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,
  B000, B000, B000, B000, B000, B000, B101, B010, B101, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,
  B101, B000, B000, B010, B000, B000, B101, B000, B000, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,
  B000, B000, B101, B000, B000, B010, B000, B000, B101, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 50 ,   
  B101, B010, B101, B000, B000, B000, B000, B000, B000, 50 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 40 ,
  B000, B000, B000, B000, B000, B000, B101, B010, B101, 40 ,
  B000, B000, B000, B000, B010, B000, B000, B000, B000, 40 ,
  B101, B000, B000, B010, B000, B000, B101, B000, B000, 40 ,
}

See that bracket at the beginning and end of this numerical data? Make sure you include them in the complete sketch.

Load up the sketch and it should run!
I have included an example to show that it DOES work!
Have fun programming your own personal show on that cube. It is a super toy and a great learning tool.

Step 8: Conclusion

Have you ever tried to write instructions about how to fold a paper airplane? How can something so simple become so difficult? This may seem as difficult to some of you. I understand completely. To my twisted ways of thinking it makes sense, but my mother always said I was "special." So... If you need help, ask. There are programmers out there and people who can teach us ALL a lot about this subject. All I can say is, hack it!

Give it a try and share some of your own routines here!
Enjoy.