Static Arkeyan Robot

I’ve been building a robot with my boy for quite some time. It doesn’t move, but it does have awesome eyes! The eyes glow from inside the robot.

Arkeyan Robot

The robot is built from sheet metal rescued from old vegetable oil tins, with copper cable for arms and legs and a polystyrene foam head. The eyes were made hollow, with paper glued on behind, an RGB LED behind each eye, and then a layer of alfoil to guide all of the light out through the eyes.

I’ve used an Arduino Uno to drive the LEDs. However, the arduino is programmed in C rather than Arduino language. I gave up on the Arduino environment when gnoduino disappeared from the archlinux repo, and I discovered that it’s not really much harder to code it in C. (It also avoids the hassles of programming around the limitations of the Arduino language.) You also get to do exactly what you want.

There are three inputs, one for each colour. Each input should be connected to a momentary pushbutton connected to ground – such as a pair of wires floating around, in true Maker style. Activating an input fades one of the colours smoothly in or out. It looks pretty cool.

So without further delay, the code:

/*
 * Robot lights
 *
 * Controls the eye LEDs on Joshua’s Arkeyan Robot
 *
 * This program copyright (C) 2015 Joshua & Jared Henley, shared GPLv3
 * Basically, you can copy it around and change it all you like as long as you
 * share under the same terms.
 */

#include <avr/io.h>
#include <avr/interrupt.h>

// Tells us if a colour is to be brightened or darkened.
#define RED_UP 0b00000001 // bit 0 = Red UP
#define RED_DN 0b00000010 // bit 1 = Red DOWN
#define GRN_UP 0b00000100 // bit 2 = Green UP
#define GRN_DN 0b00001000 // bit 3 = Green DOWN
#define BLU_UP 0b00010000 // bit 4 = Blue UP
#define BLU_DN 0b00100000 // bit 5 = Blue DOWN
#define LED_ON 0x00
#define LED_OFF 0xff
uint8_t ramps = 0;

/* Set up an interrupt-driven clock to time the ramp-up/ramp-downs */

volatile uint16_t ticks;

void clock_setup() {
    // Leave the prescaler alone
    TCCR0B = _BV(WGM02) | _BV(CS00); // Configure timer 0 for CTC mode, no prescaler
    TIMSK0 |= _BV(OCIE0A); // Enable CTC interrupt
    OCR0A = 255; // Set CTC compare value
}

// Get the current "time".
uint16_t clock_now() {
    return ticks;
}

// Determine elapsed time.  Acounts for a single overflow.
uint16_t clock_elapsed(uint16_t begin) {
    if (ticks < begin)
        return ticks + (65535 - begin);
    else
        return ticks - begin;
}

ISR(TIMER0_COMPA_vect, ISR_BLOCK) {
    ticks++;
}

uint16_t start_time;

void pwm_setup() {
    // Set up timers 1 and 2 in Fast 8-bit PWM mode, no prescaler
    // Timer 1
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
    TCCR1B = _BV(WGM12) | _BV(CS10);
    OCR1A = LED_OFF; // Set compare value -- Red LED -- off
    OCR1B = LED_OFF; // Set compare value -- Green LED -- off
    // Timer 2
    TCCR2A = _BV(COM2A1) | _BV(WGM20);
    TCCR2B = _BV(WGM21) | _BV(CS20);
    OCR2A = LED_OFF; // Set compare value -- Blue LED -- off
}

void setup() {
    // Set up output pins
    DDRB = 0;
    DDRB |= _BV(PB1); // Red pin is output
    DDRB |= _BV(PB2); // Green pin is output
    DDRB |= _BV(PB3); // Blue pin is output
    // Enable internal pull-ups on inputs
    PORTD = _BV(PD2) |  _BV(PD3) | _BV(PD4);
    clock_setup();
    pwm_setup();
    start_time = clock_now();
    sei(); // Enable the global interrupt flag
}

void loop() {
    // Read input pins
    if (!(PIND & _BV(PD2))) { // Red Pin
        if (OCR1A == LED_OFF) {
            // LED is off, ramp it on
            ramps |= RED_UP;
        }
        if (OCR1A == LED_ON) {
            // LED is on, ramp it off
            ramps |= RED_DN;
        }
    }
    if (!(PIND & _BV(PD3))) { // Green Pin
        if (OCR1B == LED_OFF) {
            // LED is off, ramp it on
            ramps |= GRN_UP;
        }
        if (OCR1B == LED_ON) {
            // LED is on, ramp it off
            ramps |= GRN_DN;
        }
    }
    if (!(PIND & _BV(PD4))) { // Blue Pin
        if (OCR2A == LED_OFF) {
            // LED is off, ramp it on
            ramps |= BLU_UP;
        }
        if (OCR2A == LED_ON) {
            // LED is on, ramp it off
            ramps |= BLU_DN;
        }
    }
    if (clock_elapsed(start_time) >= 800) {
        // Alter PWM outputs
        if (ramps & RED_UP) {
            // Red UP
            OCR1A--;
            if (OCR1A == LED_ON) {
                ramps &= ~(RED_UP | RED_DN);
            }
        }
        if (ramps & RED_DN) {
            // Red DOWN
            OCR1A++;
            if (OCR1A == LED_OFF) {
                ramps &= ~(RED_UP | RED_DN);
            }
        }
        if (ramps & GRN_UP) {
            // Green UP
            OCR1B--;
            if (OCR1B == LED_ON) {
                ramps &= ~(GRN_UP | GRN_DN);
            }
        }
        if (ramps & GRN_DN) {
            // Green Down
            OCR1B++;
            if (OCR1B == LED_OFF) {
                ramps &= ~(GRN_UP | GRN_DN);
            }
        }
        if (ramps & BLU_UP) {
            // Blue UP
            OCR2A--;
            if (OCR2A == LED_ON) {
                ramps &= ~(BLU_UP | BLU_DN);
            }
        }
        if (ramps & BLU_DN) {
            // Blue DOWN
            OCR2A++;
            if (OCR2A == LED_OFF) {
                ramps &= ~(BLU_UP | BLU_DN);
            }
        }
        start_time = clock_now();
    }
}

int main() {
    setup();
    while (1) {
        loop();
    }
}

This code is compiled against avr-libc. It is licenced GPLv3