#include #include /* Pinout used: * ----- * reset - MCU - Vcc2 * module reset (PB3) - MCU - (ADC1) Vcc1 mesurement * module set (PB4) - MCU - not used (linked to GND) * GND - MCU - not used (linked to GND) * ----- */ // assume computer is shutting down if vcc1 is below this value static const uint8_t ADC_DOWN_VALUE = 217; /* times are in tenth of seconds since ISR is triggered every 0.1s~ * see init_timer */ static const uint8_t SLEPT1_TIMEOUT = 10; /* 1s */ static const uint8_t CAPA_TIMEOUT = 10; /* 1s */ static void init_adc() { /* this function initialises the ADC ADC Notes Prescaler ADC Prescaler needs to be set so that the ADC input frequency is between 50 - 200kHz. Example prescaler values for various frequencies Clock Available prescaler values --------------------------------------- 1 MHz 8 (125kHz), 16 (62.5kHz) 4 MHz 32 (125kHz), 64 (62.5kHz) 8 MHz 64 (125kHz), 128 (62.5kHz) 16 MHz 128 (125kHz) set prescaler to 128 for mcu running at 8MHz */ ADMUX = (0 << REFS1) | // Sets ref. voltage to VCC, bit 1 (0 << REFS0) | // Sets ref. voltage to VCC, bit 0 (1 << ADLAR) | // left shift result (0 << REFS2) | // Sets ref. voltage to VCC, bit 2 (0 << MUX3) | // use ADC1 for input, MUX bit 3 (0 << MUX2) | // use ADC1 for input, MUX bit 2 (0 << MUX1) | // use ADC1 for input, MUX bit 1 (1 << MUX0); // use ADC1 for input, MUX bit 0 ADCSRA = (1 << ADEN) | // Enable ADC (0 << ADSC) | // Do not start conversion (0 << ADATE) | // Do not enable Auto Trigger (0 << ADIF) | // Do not set Interrupt flag (0 << ADIE) | // Do not set interrupt enable (1 << ADPS2) | // set prescaler to 16, bit 2 (0 << ADPS1) | // set prescaler to 16, bit 1 (0 << ADPS0); // set prescaler to 16, bit 0 } // Value is between Vcc 3.4V (255) and GND (0). static uint8_t read_adc() { ADCSRA |= (1 << ADSC); // start ADC measurement while (ADCSRA & (1 << ADSC)); // wait till conversion complete return ADCH; // value is between 0 (=GND) and 255 (=Vcc) } static void init_timer() { /* timer_resolution = 1 / (clock_speed / prescaler) * timer_resolution = 1 / (10**6 / 1024) * * in ctc mode, target counts: * * timer_counts = (target_time / timer_resolution) - 1 * timer_counts = (0.1 / (1/(10**6/1024))) - 1 * timer_counts = 96.65625000000001 =~ 97 * * Why did we add the extra +1 to our number of timer counts? In CTC mode, * when the timer matches our desired count it will reset itself to zero. * This takes one clock cycle to perform, so we need to factor that into * our calculations. * * VG notes: more simply 0 to 97 = 98 values = 98 cycles. * * trigger_time = (97+1) * (1/(10**6 / 1024)) =~ 0.100352s * * WGM0[2:0] = 010 = CTC */ TCCR0A = (1 << WGM01) | // CTC (0 << WGM00); // CTC TCCR0B = (0 << WGM02) | // CTC (1 << CS02) | // CS0[2:0] = 101 => prescaler clk/1024 (0 << CS01) | // CS0[2:0] = 101 => prescaler clk/1024 (1 << CS00); // CS0[2:0] = 101 => prescaler clk/1024 OCR0A = 97; TIMSK = (1 << OCIE0A) | // enable CTC interrupt for compare match 0A (0 << OCIE0B) | // disable CTC interrupt for compare match 0B (0 << TOIE0); // disable interrupt for timer0 overflow } static volatile uint8_t sleep_time; static volatile uint8_t capa_time; ISR(TIMER0_COMPA_vect) { ++sleep_time; ++capa_time; } static uint8_t get_capa_time(void) { return capa_time; } static uint8_t get_sleep_time(void) { return sleep_time; } static void reset_capa_time(void) { capa_time = 0; } static void reset_sleep_time(void) { sleep_time = 0; } static void plug_set() { PORTB &= ~(1 << PB3); /* make sure reset line is disabled */ PORTB |= (1 << PB4); /* activate plug modules set */ } static void plug_unset() { PORTB &= ~(1 << PB4); /* deactivate plug modules set */ } static void plug_reset() { /* be sure set line is not active before doing a reset else both coil will * be energized at the same time and thus the behaviour may be * unpredictable. */ plug_unset(); /* activate module reset */ PORTB |= (1 << PB3); } static void plug_unreset() { PORTB &= ~(1 << PB3); } static enum states { ST_1_PLUG_OFF, ST_2_PLUG_SET, ST_3_IDLE, /* when plug is on we are in idle state */ ST_4_PLUG_RESET, /* ST_OFF never reached normally :P it only appears in the diagram */ ST_MAX } current_state = ST_1_PLUG_OFF; enum events { EV_1_CAPA_LOADED, EV_2_SLEPT1, EV_3_UNLOADING, /* no more current, capa temp power unloading */ EV_MAX }; static void s0_e0(void); /* do nothing */ static void s1_e1(void); static void s2_e2(void); static void s2s3_e3(void); static void s4_e2(void); static void (*const state_table [ST_MAX][EV_MAX])(void) = { {s1_e1, s0_e0, s0_e0}, {s0_e0, s2_e2, s2s3_e3}, {s0_e0, s0_e0, s2s3_e3}, {s0_e0, s4_e2, s0_e0}, }; static void process_event(enum events new_event) { state_table[current_state][new_event](); } static void s0_e0(void) {} static void s1_e1(void) /* plug off: capa loaded */ { plug_set(); reset_sleep_time(); current_state = ST_2_PLUG_SET; } static void s2_e2(void) /* plug set: slept1 */ { plug_unset(); current_state = ST_3_IDLE; } static void s2s3_e3(void) /* plug set or idle: unloading */ { plug_reset(); reset_sleep_time(); current_state = ST_4_PLUG_RESET; } static void s4_e2(void) /* plug reset: slept1 */ { plug_unreset(); current_state = ST_1_PLUG_OFF; } int main() { uint8_t adcval; cli(); init_adc(); init_timer(); // digital output DDRB |= (1 << PB3) | // module reset (1 << PB4); // module set PORTB = 0; // outputs set to low and input not set to internal pull-up // first ADC conversion may be inaccurate // atmel advises to discard the result adcval = read_adc(); sei(); for (;;) { /* main loop */ adcval = read_adc(); if (get_capa_time() >= CAPA_TIMEOUT) { process_event(EV_1_CAPA_LOADED); } if (get_sleep_time() >= SLEPT1_TIMEOUT) { process_event(EV_2_SLEPT1); } if (adcval <= ADC_DOWN_VALUE) { reset_capa_time(); process_event(EV_3_UNLOADING); } } return 0; }