An AVR Powered Arc Reactor

11 Jan 2010
Tagged: Arc Reactor, Arduino, AVR, Electronics, Iron Man, Projects
For Hallowe'en last year I wanted to go as Tony Stark on a night out. There are quite a few Arc Reactor projects floating around the internet, but I wanted something that would shine through a dress shirt, and have a bit of motion to it.

Hardware

I was inspired by Sprite's post on electronic minimalism. He points out that many components that are necessary in traditional electronics can be omitted when using a microcontroller. You can fake some hardware in software. With that in mind, HC Gilje has a great write-up on how to make a truly minimal arduino clone, using the LilyPad arduino firmware. I wrote the software, and did about half the assembly. Matt Englert did the other half, soldering up the controller board in less than half an hour with no schematic and a semi-coherent description from me. It's pretty basic. The ring is made of 3mm white LEDs hotglued into a piece of dollar store cutting board. I think this one is polystyrene rather than HDPE. The LEDs have a common cathode. Their other ends are connected directly to the microcontroller on pins 6 and 11 to 19 -- that corresponds to digital 4 to digital 13 in the arduino environment. There are no current limiting resistors. Each LED is controlled via pulse width modulation or PWM. Rather than a constant current, they are each turned on and off at about 250 Hz. By varying the duty cycle, the microcontroller can set the brightness of each LED. LEDs are like any other component: their specifications are approximate, and recommended values. You can overpower just about any component for small amounts of time. By making sure these LEDs are never given a 100% duty cycle, I felt okay overpowering them. This is an exercise in minimal circuitry. And truth be told, it only needed to last one night. Mouse over these pictures for more details: Arc Reactor (Back) Arc Reactor (Front) Given that a typical AAA has about 750mAh of capacity, and this gizmo was still going at noon the next day, it averaged about 40 milliamps.

Software

The ATmega168 chip only has six hardware PWM outputs, and for this project I needed ten. Dimming an LED is a pretty trivial application. Doing PWM in software will certainly be sufficient. The dimming is handled by a function called update_dimmer(). It is called by a timer interrupt at 4kHz. With 16 dimming levels, a complete PWM cycle occurs about 250 times a second. The animation is handled by animate_rotating_sine(). It runs constantly, but only updates the animation every 25 milliseconds. The animation is timed to rotate about 70 times a minute, or roughly equivalent to the average human heart rate. Here's the source code:
          #define ARC_START 4 // Circle of LEDs starts at digital pin 4 (Arduino, not AVR)
          #define ARC_SIZE 10 // There are 10 leds.
          
          // Dimming parameters.
          #define PIN_DUTY_CYCLE 16 // Number of dimming levels to support
          int pin_duty_cycle = 0; // Where are we in the current dimming cycle.
          int pin_duty[ ARC_SIZE ]; // Gray level for each pin.  Should < PIN_DUTY_CYCLE
          
          // Animation parameters.
          #define ANIMATION_INTERVAL 25 // milliseconds
          long animation_time = 0; // When did we last update the animation?
          #define ANIMATION_CYCLE (((60.0*1000)/(70*ANIMATION_INTERVAL))) // should be about 70bpm -- avg human heart rate.
          int animation_cycle = 0; // Where are we in the animation cycle?
          
          #define CPU_FREQ 8000000.0 // Timer2 runs at this speed.  Needed for interrupt calculations.
          #define TIMER2_DIVISOR 32
          #define TIMER2_FREQ ( CPU_FREQ / TIMER2_DIVISOR )
          
          // The setup() method runs once, when the sketch starts
          void setup() { 
            // Set LED pins to output.
            for( int pin = 0; pin < ARC_SIZE; pin++ ) {
              pin_duty[ pin ] = 0;
              pinMode( ARC_START + pin, OUTPUT );
              digitalWrite( ARC_START + pin, OUTPUT );
            }
            
            // Update our dimming level periodically.
            SetupTimer2(4000);
            
            rotate_blink();
          }
          
          // Initialize Timer 2
          // Shamelessly taken from http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
          unsigned char SetupTimer2( float timeoutFrequency ){
            unsigned char result; //The timer load value.
          
            //Calculate the timer load value
            result=(int)((257.0-(TIMER2_FREQ/timeoutFrequency))+0.5);
            //The 257 really should be 256 but I get better results with 257.
          
            //Timer2 Settings: Timer Prescaler /32, mode 0
            TCCR2A = 0;
            TCCR2B = 0<= PIN_DUTY_CYCLE ) ? 0 : pin_duty_cycle + 1;
          }
          
          void rotate_blink() {
            // Animation start up.  Pulse each LED.
            for( int pin = 0; pin < ARC_SIZE; pin++ ) {
              pin_duty[ pin ] = 0.75 * PIN_DUTY_CYCLE;
              delay( 100 );
              pin_duty[ pin ] = 0;
              delay( 50 );
            }
          }
            
          void animate_rotating_sine() {
            // We might have just overflowed.  Overflown?
            if( millis() < animation_time )
              animation_time = 0; 
            
            if( millis() - animation_time > ANIMATION_INTERVAL ) {
              
              // Where is the sine rotated overall?
              float animation_angle = PI * 2 * animation_cycle / ANIMATION_CYCLE;
              
              for( int pin = 0; pin < ARC_SIZE; pin++ ) {
                // How far around the arc is this LED?
                float led_angle = PI * 2 * pin / ARC_SIZE;
                // Values tweaked for appearance; there's no science here :)
                pin_duty[ pin ] = ( sin( animation_angle + led_angle ) + 2 ) * PIN_DUTY_CYCLE / 3 *.9;
              }
              
              animation_time = millis();
              animation_cycle = ( animation_cycle >= ANIMATION_CYCLE ) ? 0 : animation_cycle + 1;
            }
          }
          
          // the loop() method runs over and over again,
          // as long as the Arduino has power
          
          void loop()                     
          {
            animate_rotating_sine();
          }