/****************************************************************************
 Title:    zoombot
 Author:   Rob Duarte <rahji@rahji.com> 
 Date:     02/05/2008
 Software: AVR-GCC 4.1.2 
 Hardware: ATTiny13 using onboard clock
           microswitch on pb2 active-low
           pb3, pb4 3mm leds sourcing active-high
           pb0, pb1 pc123 optoisolators
           
 Description: pressing the microswitch cycles through four states:
    pre-zoomin, zoomin, pre-zoomout, zoomout, etc
    button debouncing and the flashing of leds during the "pre" states
    is handled in the timer/counter match interrupt - in fact,
    the entire program is in that interrupt, for the most part
 
*****************************************************************************/

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

#define F_CPU  128000UL // attiny with CKSEL=11 (128KHz clock)

#define STATE_PREZOOMIN  0x00
#define STATE_ZOOMIN     0x01
#define STATE_PREZOOMOUT 0x02
#define STATE_ZOOMOUT    0x03

#define ZOOMIN_PIN       PB0
#define ZOOMOUT_PIN      PB1
#define LEDZOOMIN_PIN    PB4
#define LEDZOOMOUT_PIN   PB3

#define NO_LEDFLASHING   0x00
#define SWITCH_PIN       PB2

// this interrupt triggers every time the counter reaches 70 (from 0)
// (this happens every 35ms -- .035 seconds)
// it takes care of both button debouncing and led flashing
ISR (TIM0_COMPA_vect)
{
   static volatile uint8_t zoom_state, button_count, bar_count, led_count, led_flash;

    // if the button state is low (pressed), advance the count variable.
    // the count continues to go up only if it's low for every consecutive check.
    // if this happens 3 times in a row (~10ms), it's debounced.

    if (bit_is_clear(PINB,SWITCH_PIN)) button_count++;
    else button_count = 0; // reset the count - we're bouncing or not pressed
    
    // if the button is low and stable, accept it as a click and handle it.
    // interrupts are disabled inside this ISR, so we can take our time.
    // that's not normal practice, but in this application nothing else will be
    // effected by our timing - the whole app is in this ISR!
    // btw, bar_count makes sure that there's a mandatory delay between successful clicks
    // this specific application shouldn't have clicks anywhere near each other (they're SECONDS apart!)
    if (++bar_count > 50 && button_count > 3) {
        switch (zoom_state)
        {
            case STATE_PREZOOMIN:
                // everything off, this is a pause state
                PORTB &= ~(_BV(ZOOMIN_PIN) | _BV(ZOOMOUT_PIN) | _BV(LEDZOOMOUT_PIN) | _BV(LEDZOOMIN_PIN));
                led_flash = LEDZOOMIN_PIN;     // flash the zoom-in led to indicate the next state
                zoom_state = STATE_ZOOMIN;     // the zoom state to enter the next time the button is pressed
                break;
            case STATE_ZOOMIN:
                PORTB |= _BV(ZOOMIN_PIN) | _BV(LEDZOOMIN_PIN);  // zoom in
                led_flash = NO_LEDFLASHING;    // we want the zoom-in led to stay on now, no flashing
                zoom_state = STATE_PREZOOMOUT; // the state to enter next time
                break;
            case STATE_PREZOOMOUT:
                // everything off, this is a pause state
                PORTB &= ~(_BV(ZOOMIN_PIN) | _BV(ZOOMOUT_PIN) | _BV(LEDZOOMOUT_PIN) | _BV(LEDZOOMIN_PIN));
                led_flash = LEDZOOMOUT_PIN;    // flash the zoom-out led to indicate the next state
                zoom_state = STATE_ZOOMOUT;    // the state to enter next time
                break;
            case STATE_ZOOMOUT:
                PORTB |= _BV(ZOOMOUT_PIN) | _BV(LEDZOOMOUT_PIN);  // zoom out
                led_flash = NO_LEDFLASHING;    // we want the zoom-out led to stay on now, no flashing
                zoom_state = STATE_PREZOOMIN;  // the state to enter next time (loops back to the 1st state)
                break;
        }
        button_count = 0;  // reset the debounce counter for the next time
        bar_count = 0; // reset the counter that gives us a period of no-clicks-allowed time after a successful click
    }
    
    // handle the led flashing
    // basically, if neither led is ON then one of them will be flashing,
    // as described in the state machine above.
    // led_flash evaluates to either false or the pin name of one of the two leds
    if (led_flash) {
       if (++led_count > 10) {        // if the 35ms counter int has triggered 10 times
            PORTB ^= _BV(led_flash);  // then toggle the appropriate led pin
           led_count = 0;             // and reset the counter for the next toggle
       }
    }
}


void setup_hardware( void )
{
    cli(); // disable interrupts, just in case
    
    // define pin data directions
    DDRB  &= ~_BV(SWITCH_PIN);   // pb2 is input - the toggler switch
    PORTB |=  _BV(SWITCH_PIN);   // enable pull-up resistor on pb2
    DDRB  |=  _BV(ZOOMIN_PIN) | _BV(ZOOMOUT_PIN);        // output pins - the optoisolators
    DDRB  |=  _BV(LEDZOOMIN_PIN) | _BV(LEDZOOMOUT_PIN);  // output pins - the led indicators
    
    // setup the only 8-bit timer, used for button debounce and led blinking
    TCCR0B |= _BV(CS00) | _BV(CS01); // add /64 prescaler
    TCCR0A |= _BV(WGM01);            // clear timer on compare match
    TIMSK0 |= _BV(OCIE0A);           // enable compare match a
    OCR0A = 70;  // set compare match value to 70 - matches every 35ms (.035 sec)
    
    sei(); // enable global interrupts
}


int main( void )
{
    setup_hardware();        
    while (1); // do nothing but wait for interrupts
}