/* * AHAB cutdown device source code. This implements a simple countdown * timer, in a minimal footprint. It provides three modes: * * SET - In set mode, you can set the countdown time. The current set time * will be flashed on the LED; long pulses first for hours, followed * by short pulses for each 10 minutes. A short button press will * increment the time by 10 minutes. A long button press will start * the timer running. * RUN - When the timer is counting down, the LED will flash rapidly. To * cancel the countdown, hold the button down for a long press. This * will return you to SET mode. * BURN- When the countdown is complete, the output line will be raised high, * and the LED will glow steady. BURN mode lasts for 60 seconds, after * which it will return to SET mode. BURN mode can be cancelled with * a long button press. * * This source is for an ATtiny85 microcontroller, clocked at 4.9152MHz. * Set the low fuse byte on the MCU to 0xDF for proper operation. * * Wiring: * B.2: Button tied to ground * B.1: LED tied high * B.0: Trigger, floating. * * Compile this code with avr-gcc, giving it the -mmcu=attiny85 option. * * Copyright 2007, Jon McClintock. * * This software is licensed under the CC-GNU GPL. */ // The CPU clock frequency, in Hz. #define F_CPU 4915200UL #include #include #include #include #include #include // I/O line definitions #define BUTTON_LINE 2 #define LED_LINE 1 #define OUTPUT_LINE 0 // Timer 0 is used to define the state machine and button scan interval. // It is configured to overflow roughly 30 times per second, and generate // an interrupt upon overflow. #define TIMER0_TCCR0B 0x05 // ck/1024 = 4800 ticks/sec #define TIMER0_VALUE 0x56 // 160 ticks to overflow: 30 overflows/sec // Timer 1 is used for the timing events. It is configured to generate a // match interrupt two times per second, and clear upon match. #define TIMER1_TCCR1 0x8F // ck/16384 = 300 ticks/sec #define TIMER1_VALUE 0x60 // 150 ticks to overflow: 2 overflows/sec #define TIMER1_COMP 150 // 150 ticks to overflow: 2 overflows/sec // The machine states. #define STATE_SET 1 #define STATE_RUNNING 2 #define STATE_BURN 3 // The button press types. #define BUTTON_NONE 0 #define BUTTON_SHORT 1 #define BUTTON_LONG 2 /////////////////////////////////// // GLOBALS /////////////////////////////////// volatile unsigned char state; // The current system state volatile unsigned char tick; // Set to true by the button timer. volatile unsigned char target; // The current timer target, in minutes volatile unsigned int count; // Half-seconds elapsed since timer start volatile unsigned int minutes; // Minutes elapsed since timer start /////////////////////////////////// // METHOD PROTOTYPES /////////////////////////////////// unsigned char read_button(); void display_target(); void change_state(unsigned char new_state); void state_enter(); void state_loop(const unsigned char button); void state_exit(); /////////////////////////////////// // MAIN ROUTINE /////////////////////////////////// int main(void) { // Set the data direction bits on port B DDRB = _BV(LED_LINE) | _BV(OUTPUT_LINE); PORTB = _BV(BUTTON_LINE) | _BV(LED_LINE); // Initialize our state machine target = 10; change_state(STATE_SET); // Setup Timer 0 and start it running TCNT0 = TIMER0_VALUE; TIMSK |= _BV(TOIE0); TCCR0B = TIMER0_TCCR0B; // Setup Timer 1 and start it running TCNT1 = 0; OCR1A = TIMER1_COMP; OCR1C = TIMER1_COMP; TIMSK |= _BV(OCIE1A); TCCR1 = TIMER1_TCCR1; // Enable interrupts sei(); // Main processing loop. Sleep, and do stuff when we are awoken. while(1) { unsigned char button; // We use the tick flag to ignore the timing ticks, and only // take action on the scan ticks. This helps us keep better time // when flashing the LED at set intervals. if (!tick) { continue; } // Read the button and process any press. button = read_button(); state_loop(button); tick = 0; sleep_mode(); } return 0; } /////////////////////////////////// // SIGNAL HANDLERS /////////////////////////////////// // The Timer 0 overflow handler. This is when we scan the button // input. It simply sets the tick flag to let the mainloop know that // it should run. ISR(SIG_OVERFLOW0) { TCNT0 = TIMER0_VALUE; tick = 1; } // The Timer 1 match handler. This is when we increment our time value. ISR(SIG_OUTPUT_COMPARE1A) { count++; if ( count == 120 ) { count = 0; minutes++; } } /////////////////////////////////// // UTILITY METHODS /////////////////////////////////// // Performs the button scan procedure unsigned char read_button() { static unsigned char keycount = 0; unsigned char button = BUTTON_NONE; if ( ! (PINB & _BV(BUTTON_LINE)) ) { keycount++; // If the button's been held down for a full second, register // a long press. if ( keycount == 30 ) { button = BUTTON_LONG; } if ( keycount >= 31 ) { keycount = 31; } } else { // We require that the button be held for two 30th's of a // second for the press to register. if ( keycount >= 1 && keycount < 30 ) { button = BUTTON_SHORT; } keycount = 0; } return button; } // Displays the current target time, hours in long pulses, minutes // in short pulses. This is implemented as a mini state machine, // driven monotonically by the quick timer. void display_target() { static unsigned char hours = 0; static unsigned char period = 1, count = 0; static int saved_target = 0; if (saved_target != target) { saved_target = target; period = 1; count = 0; hours = 0; } // If delay is nonzero, we've still got time for this bit if (--period != 0) { return; } // If we're done showing all of the bits, go to the next // state. if (count == 0) { if ( hours || (saved_target < 60) ) { count = (saved_target % 60) / 10; period = 10; hours = 0; } else { count = saved_target / 60; period = 30; hours = 1; } return; } // If the LED is off, turn it on if ( PORTB & _BV(LED_LINE) ) { PORTB = PORTB & ~_BV(LED_LINE); if ( hours ) { period = 15; } else { period = 5; } } else { // Otherwise, turn it off, and move to the next bit. PORTB = PORTB | _BV(LED_LINE); count--; period = 5; } } /////////////////////////////////// // STATE MACHINE IMPLEMENTATION /////////////////////////////////// // The "set" state is when the user is setting the amount of time // to run for. void set_state_loop(const unsigned char button) { switch (button) { case BUTTON_SHORT: target += 10; if (target >= 250) { target = 10; } break; case BUTTON_LONG: change_state(STATE_RUNNING); break; } display_target(); } // The "run" state is when the timer actually counts. void run_state_loop(const unsigned char button) { static unsigned int count = 0; if ( button == BUTTON_LONG ) { change_state(STATE_SET); } if (++count == 4) { PORTB = PORTB ^ _BV(LED_LINE); count = 0; } if ( minutes >= target ) { change_state(STATE_BURN); } } // The "burn" state is triggered after the timer starts. void burn_state_enter() { PORTB = PORTB | _BV(OUTPUT_LINE); PORTB = PORTB & ~_BV(LED_LINE); } void burn_state_loop(const unsigned char button) { if ( minutes >= 1 || button == BUTTON_LONG ) { change_state(STATE_SET); } } void burn_state_exit() { PORTB = PORTB & ~_BV(OUTPUT_LINE); PORTB = PORTB | _BV(LED_LINE); } /////////////////////////////////// // STATE MACHINE HANDLERS /////////////////////////////////// void change_state(unsigned char new_state) { // Exit the previous state state_exit(); // Set the new state state = new_state; count = minutes = 0; // Enter the new state state_enter(); } void state_enter() { switch (state) { case STATE_BURN: burn_state_enter(); break; } } void state_loop(const unsigned char button) { switch (state) { case STATE_SET: set_state_loop(button); break; case STATE_RUNNING: run_state_loop(button); break; case STATE_BURN: burn_state_loop(button); break; } } void state_exit() { switch (state) { case STATE_BURN: burn_state_exit(); break; } }