Introduction

I build this cube about half a year ago, and I think I should explain how it work and criticize myself for mistakes I made then. So first of all let's see It.

Thumbs up would be appreciated

Multiplexing

Have you wondered how I've manged to control 64 LEDs with just a few pin?
it's called multiplexing.Generally speaking, The circuit light up each level in the cube individually for a few microseconds then it processed to the next level and so on.this goes so fast that our eye sees it as one "image". this method is quite commonly implemented in displays and keyboards.
here is a quick video demonstrating this technique:

 ( Uploaded by chrmoe )

The circuit

In order to be able implement multiplexing in my design I need 20 I/O pins (16 for each led and 4 for each level), which the ATtiny2313 is lacking of. Forthrightly, this can be overcame easily by using a shift register. in my design I used the 595 because there are already written methods/functions to work with it in the Arduino IDE.Furthermore, these shift registers can be controlled just by 3 I/O pins regardless their amount.

In order to control which level is lit up in every single moment, I used the 2N3904 NPN transistor that is driven directly by the uC so it can be manipulated much faster.

As you have seen in the video, there are the ISP pins to which the arduino connects to, in order to program  the ATtiny.


The Schematic:



Back then when I had built It I hadn't put that much effort in to calculation,but then I noticed that the maximum current drawn in to each transistor is about 0.5A, however the absolute maximum allowed for the 2n3904 is 100mA. there reason for my transistor yet to burn out is that the duty cycle of each level is 0.25. Moreover, Non of my animation light up all the 16 LEDs in each level.

The Software

When I wrote the program to run this cube I faced a few obstacles, In order to make it clear to you I will first explain the function that light up the led, then the basics of the whole code and finally the obstacles.A link to the full source code as well as the compiled HEX file can be found at the end of this article.

Take control over the LEDs

Let's begin by the most important function which hands the LEDs.In order it to know what it should display we define a global variable, or the buffer as I'm going to refer to it.

The varible

byte bin[4][2] = {
  {
    B00000000,B00000000        }
  ,{
    B00000000,B00000000        }
  ,{
    B00000000,B00000000        }
  ,{
    B00000000,B00000000        }
};

this buffer holds what should be displayed on the cube in any moment. the first "dimension" of the array defines what level it is, and the second one divides each layer into two.Of course I could use an INT and define it just as bin[4], but the uC will end up spending to much time on converting them back to byte, which is needed because each shift register as just one byte.

we have a buffer that holds what we want to be displayed.So I wrote a function that takes this buffer and display it for certain amount of milliseconds,which are specified when this function is called.
As I mentioned earlier we can light each level individually, and if we do it fast enough, we fool our eyes and we see it as one still image. 

Text Box

void bin_prosses(byte times){

  for(byte j=0;j<times;j++)
    for(byte i=0;i<4;i++)
    {


      PORTB = PORTB & B11110111; //digitalWrite(LATCHPIN, LOW);
      shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, bin[i][0]);
      shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, bin[i][1]);
      PORTB = PORTB | B00001000; //digitalWrite(LATCHPIN, HIGH);

      digitalWrite(i, HIGH);     

      delayMicroseconds(2000);

      digitalWrite(i, LOW);  


    } 

}

As you can see the function is pretty simple.There is a FOR loop which runs 4 time, and the i variable corresponds for each level. Inside the loop, first we disable the LATCH pin and shift the buffer for this level. next,we enable the latch, then we turn on the transistor corresponding for this level, so the LEDs can light up, and we keep them so for 2000 microseconds. And we do so for all the levels.

Note! I used manipulate the latch pin directly,using the PORTB register which is much fast them, using the Arduino built in function.which is quite slow, and speed is a key component for a successful cube.

The minimal time each "image" can be shown is 2*4 = 8 milliseconds, which is nothing.So I put it into another FOR loop which allow the higher level function to control the duration of the next frame.

Your first animation

Okay, now you know how we light up the LEDs, but how do we make animation with it?! that's pretty easy! as you probably know animation consists of frames.If they are switched fast enough it look like a fluent motion. that's exactly what I'm going to do.Let's take the RAIN animation as an example:

Text Box

byte pows[8]={1,2,4,8,16,32,64,128};

void rain(){

  for(int i=0;i<3;i++) //put randomly three rain drops
        bin[3][rand()%2] += pows[rand()%8];

  //Display the buffer for 20*(2*4) = 160mSec = 0.16sec
  bin_prosses(20);

//Shift the buffer down, the rain falls down
  for(int i=0;i<4;i++){
        bin[i][0] = bin[i+1][0];
        bin[i][1] = bin[i+1][1];
  }

  // clear the highest level
  bin[3][0] = 0;
  bin[3][1] = 0;
}

Before we start, I'd recommend to you scroll up and watch the video once again, just to understand what is the RAIN animation. The first thing we do is setting up 3 drops drops at the top of the cube at random location.In case you don't know rand()%8 returns a number between 0 and 7. another thing you should know that if we add a power of 2 to a number it will change one bit inside it's binary structure. I know it sound awful, so lets take an example, here is the structure of a byte :

Decimal
 2^7 = 128 2^6 = 64
 2^5 = 32 2^4 = 16
2^3 = 8
 2^2 = 4 2^1 = 2  2^0 = 1
0
0
 0  0 0
0
 0 0
0

Now lets add 2^5 or 32 to it:
Decimal
 2^7 = 128 2^6 = 64
 2^5 = 32 2^4 = 16
2^3 = 8
 2^2 = 4 2^1 = 2  2^0 = 1
32
0
 0  1 0
0
 0 0
0

So as you can see just one byte had been changed, now lets add 2^2 or 4:
Decimal
 2^7 = 128 2^6 = 64
 2^5 = 32 2^4 = 16
2^3 = 8
 2^2 = 4 2^1 = 2  2^0 = 1
36
0
 0  1 0
0
 1 0
0

Now when you got the point lets return to the code. So now it's pretty east to understand that the following line of code, sets up a random bit on the fouth level. In case you don't remember each level consists of two bytes, which is the reason why I put "rand()%2" in the second parameter.

bin[3][rand()%2] += pows[rand()%8];

I think the first part of setting random drops is the hardest one.Sure it could be simplified by using the bitSet function that built in to the Arduino IDE. But I the ATtiny2313 has just 2Kbytes of Flash.Moreover, this method is way more efficient.

The next thing we do is displaying the buffer for 160 milliseconds, by calling the bin_prosess with 20 as the parameter.The time equals 20*(4 levels)*(each level shown for 2000mS).

As far as I remember the rain should fall down unless you are in Australia :) So In order to get the drops down I shift the array down. here is a graphical representation:

Note! the order of shifting is very important, If we start from the highest level, we will end up having all the levels with the same value.

Some of you may noticed that the value if bin[3] was not changed during shifting process. So the last thing we do is clearing this level up.

Note! calling this function one won't create an animation, each call draws one frame. In order to make an animation out of it, this function should be called continuously inside a loop.

Okay, so now you know how to write a basic animation to the cube. My cube as three more, they relay almost on the same principles that you just saw, so I won't go over them.

Mix It

Now lets take all the code and put it all together. as you can see there is a switch statement that selects which animation should be drawn, according to a global variable called animation.  

Text Box

byte animation;

void loop() {

    if( (millis()%240000)-((animation+1)*60000) > 60000 ) animation++;

    switch(animation){
    case 0: rotate(); break;
    case 1: rain(); break;
    case 2: random_led(); break;
    case 3: upndown(); break;
    }

}

You probably wondering what is the point of this long IF statement, it's increase animation by one every 10 minutes, Or just change animation every 10 minutes. In case you don't know, millis() return the number of milliseconds passed from the start up of the uC. So by basic math you can check if 10 minutes had passed.

The button

Sometimes you might get tired of watching the same animation for 10 minutes, I added a button which allow me to skip to the next one. If you look a the schematic you can see that the button is connected to pin #6 which is an interruption pin. when the button is pressed the pin goes HIGH and the interruption is executed. lets look at the interruption function:

Text Box

void interrupt() {
     EEPROM.write(0, animation+1); //save the next animation
     wdt_enable(WDTO_30MS);  //reset the uC
 }

The code is pretty straight forward, the first line saves the next number of animation to the EEPROM.Next the second line sets up the watch dog which will reset the device in 30MS unless WTD_RESET is called during this time(which obviously is not going to be called).So the device is being restarted and we start to play the next animation.

However the watch dog on the new AVR devices(such as attiny2313) keeps watching after a reset,thus the watch dog should be turned on as soon as the micro boot up. lets look at the setup function:

Text Box

void setup() {

  //disable the WatchDog
  MCUSR = 0;
  wdt_disable();
 
  //get the last animation
  animation=EEPROM.read(0);

   //the greatest animation number is 3
   if(animation>3) animation=0;
 
  //set up inputs and outputs
  DDRA = B00000011;
  DDRD = B00000111;
  DDRB = B00011100;

  //attach the interruption
  attachInterrupt(0,interrupt,RISING);

}

As you can see, as soon as the device is booted up, we disable the watch dog, set up the outputs pin and finally attach the interruption.


That's it! now you know how to build and program an LED cube. Good luck!
if you have any question fell free to ask in the comment section below




Č
ċ
ď
Source.ino
(4k)
Kirill Kulakov,
Feb 18, 2012, 10:25 AM
ċ
ď
compiled.hex
(6k)
Kirill Kulakov,
Feb 18, 2012, 10:27 AM
Comments