#include "tk-attiny.h" #include #include #include #include #include #include #include // typedefs typedef PROGMEM const uint8_t Event; typedef Event * EventPtr; typedef uint8_t (*EventCallbackPtr)(EventPtr event, uint16_t arg); typedef uint8_t EventCallback(EventPtr event, uint16_t arg); typedef uint8_t State(EventPtr event, uint16_t arg); typedef State * StatePtr; typedef struct Emission { EventPtr event; uint16_t arg; } Emission; volatile StatePtr current_state; #define EV_MAX_LEN 16 uint8_t current_event[EV_MAX_LEN]; // at 0.016 ms per tick, 255 ticks = 4.08 s // TODO: 16 bits? static volatile uint8_t ticks_since_last_event = 0; #ifdef USE_LVP // volts * 10 #define VOLTAGE_LOW 30 // MCU sees voltage 0.X volts lower than actual, add X to readings #define VOLTAGE_FUDGE_FACTOR 2 volatile uint8_t voltage; void low_voltage(); #endif #ifdef USE_THERMAL_REGULATION volatile int16_t temperature; void low_temperature(); void high_temperature(); #endif #ifdef USE_DEBUG_BLINK #define OWN_DELAY #define USE_DELAY_4MS #include "tk-delay.h" #define DEBUG_FLASH PWM1_LVL = 64; delay_4ms(2); PWM1_LVL = 0; void debug_blink(uint8_t num) { for(; num>0; num--) { PWM1_LVL = 32; delay_4ms(100/4); PWM1_LVL = 0; delay_4ms(100/4); } } #endif #define A_ENTER_STATE 1 #define A_LEAVE_STATE 2 #define A_TICK 3 #define A_PRESS 4 #define A_HOLD_START 5 #define A_HOLD_TICK 6 #define A_RELEASE 7 #define A_RELEASE_TIMEOUT 8 // TODO: add events for over/under-heat conditions (with parameter for severity) #define A_OVERHEATING 9 #define A_UNDERHEATING 10 // TODO: add events for low voltage conditions #define A_VOLTAGE_LOW 11 //#define A_VOLTAGE_CRITICAL 12 #define A_DEBUG 255 // test event for debugging // TODO: maybe compare events by number instead of pointer? // (number = index in event types array) // (comparison would use full event content, but send off index to callbacks) // (saves space by using uint8_t instead of a pointer) // (also eliminates the need to duplicate single-entry events like for voltage or timer tick) // Event types Event EV_debug[] = { A_DEBUG, 0 } ; Event EV_enter_state[] = { A_ENTER_STATE, 0 } ; Event EV_leave_state[] = { A_LEAVE_STATE, 0 } ; Event EV_tick[] = { A_TICK, 0 } ; #ifdef USE_LVP Event EV_voltage_low[] = { A_VOLTAGE_LOW, 0 } ; #endif #ifdef USE_THERMAL_REGULATION Event EV_temperature_high[] = { A_OVERHEATING, 0 } ; Event EV_temperature_low[] = { A_UNDERHEATING, 0 } ; #endif Event EV_click1_press[] = { A_PRESS, 0 }; // shouldn't normally happen, but UI might reset event while button is down // so a release with no recorded prior hold could be possible Event EV_release[] = { A_RELEASE, 0 }; Event EV_click1_release[] = { A_PRESS, A_RELEASE, 0 }; #define EV_1click EV_click1_complete Event EV_click1_complete[] = { A_PRESS, A_RELEASE, A_RELEASE_TIMEOUT, 0 }; #define EV_hold EV_click1_hold // FIXME: Should holds use "start+tick" or just "tick" with a tick number? // Or "start+tick" with a tick number? Event EV_click1_hold[] = { A_PRESS, A_HOLD_START, 0 }; Event EV_click1_hold_release[] = { A_PRESS, A_HOLD_START, A_RELEASE, 0 }; Event EV_click2_press[] = { A_PRESS, A_RELEASE, A_PRESS, 0 }; Event EV_click2_release[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, 0 }; #define EV_2clicks EV_click2_complete Event EV_click2_complete[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, A_RELEASE_TIMEOUT, 0 }; Event EV_click3_press[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, A_PRESS, 0 }; Event EV_click3_release[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, 0 }; #define EV_3clicks EV_click3_complete Event EV_click3_complete[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, A_RELEASE_TIMEOUT, 0 }; // ... and so on // A list of event types for easy iteration EventPtr event_sequences[] = { EV_click1_press, EV_release, EV_click1_release, EV_click1_complete, EV_click1_hold, EV_click1_hold_release, EV_click2_press, EV_click2_release, EV_click2_complete, EV_click3_press, EV_click3_release, EV_click3_complete, // ... }; #define events_match(a,b) compare_event_sequences(a,b) // return 1 if (a == b), 0 otherwise uint8_t compare_event_sequences(uint8_t *a, const uint8_t *b) { for(uint8_t i=0; (i=0; i--) { uint8_t err = state_stack[i](event, arg); if (! err) return 0; } return 1; // event not handled } void emit(EventPtr event, uint16_t arg) { // add this event to the queue for later, // so we won't use too much time during an interrupt append_emission(event, arg); } // Search the pre-defined event list for one matching what the user just did, // and emit it if one was found. void emit_current_event(uint16_t arg) { //uint8_t err = 1; for (uint8_t i=0; i<(sizeof(event_sequences)/sizeof(EventPtr)); i++) { if (events_match(current_event, event_sequences[i])) { //DEBUG_FLASH; //err = emit(event_sequences[i], arg); //return err; emit(event_sequences[i], arg); return; } } //return err; } void _set_state(StatePtr new_state) { // call old state-exit hook (don't use stack) if (current_state != NULL) current_state(EV_leave_state, 0); // set new state current_state = new_state; // call new state-enter hook (don't use stack) if (new_state != NULL) current_state(EV_enter_state, 0); } int8_t push_state(StatePtr new_state) { if (state_stack_len < STATE_STACK_SIZE) { // TODO: call old state's exit hook? // new hook for non-exit recursion into child? state_stack[state_stack_len] = new_state; state_stack_len ++; _set_state(new_state); return state_stack_len; } else { // TODO: um... how is a flashlight supposed to handle a recursion depth error? return -1; } } StatePtr pop_state() { // TODO: how to handle pop from empty stack? StatePtr old_state = NULL; StatePtr new_state = NULL; if (state_stack_len > 0) { state_stack_len --; old_state = state_stack[state_stack_len]; } if (state_stack_len > 0) { new_state = state_stack[state_stack_len-1]; } _set_state(new_state); return old_state; } uint8_t set_state(StatePtr new_state) { // FIXME: this calls exit/enter hooks it shouldn't pop_state(); return push_state(new_state); } // TODO? add events to a queue when inside an interrupt // instead of calling the event functions directly? // (then empty the queue in main loop?) // TODO? new delay() functions which handle queue consumption? // TODO? new interruptible delay() functions? //static volatile uint8_t button_was_pressed; #define BP_SAMPLES 16 uint8_t button_is_pressed() { // debounce a little uint8_t highcount = 0; // measure for 16/64ths of a ms for(uint8_t i=0; i (BP_SAMPLES/2)); //button_was_pressed = result; return result; } //void button_change_interrupt() { ISR(PCINT0_vect) { //DEBUG_FLASH; // something happened //ticks_since_last_event = 0; // add event to current sequence if (button_is_pressed()) { push_event(A_PRESS); } else { push_event(A_RELEASE); } // check if sequence matches any defined sequences // if so, send event to current state callback emit_current_event(0); } // TODO: implement ISR(WDT_vect) { /* // TODO? safety net for PCINT, in case it misses a press or release uint8_t bp = button_is_pressed(); if (bp != button_was_pressed) { // TODO: handle missed button event if (bp) { push_event(A_PRESS); } else { push_event(A_RELEASE); } emit_current_event(0); } */ //timer ++; // Is this needed at all? /* if (ticks_since_last_event & 0b00000111 ) { DEBUG_FLASH; } */ //if (ticks_since_last_event < 0xff) ticks_since_last_event ++; // increment, but loop from 255 back to 128 ticks_since_last_event = (ticks_since_last_event + 1) \ | (ticks_since_last_event & 0x80); //static uint8_t hold_ticks = 0; // TODO: 16 bits? // callback on each timer tick emit(EV_tick, ticks_since_last_event); // if time since last event exceeds timeout, // append timeout to current event sequence, then // send event to current state callback // //hold_event(ticks) // //emit(EV_press_hold, hold_ticks); // emit_current_event(hold_ticks); // or // //release_timeout() // //emit(EV_press_release_timeout, 0); // emit_current_event(0); #if defined(USE_LVP) || defined(USE_THERMAL_REGULATION) // start a new ADC measurement every 4 ticks static uint8_t adc_trigger = 0; adc_trigger ++; if (adc_trigger > 3) { adc_trigger = 0; ADCSRA |= (1 << ADSC) | (1 << ADIE); } #endif } // TODO: implement? (or is it better done in main()?) ISR(ADC_vect) { static uint8_t adc_step = 0; #ifdef USE_LVP #ifdef USE_LVP_AVG #define NUM_VOLTAGE_VALUES 4 static int16_t voltage_values[NUM_VOLTAGE_VALUES]; #endif static uint8_t lvp_timer = 0; static uint8_t lvp_lowpass = 0; #define LVP_TIMER_START 50 // ticks between LVP warnings #define LVP_LOWPASS_STRENGTH 4 #endif #ifdef USE_THERMAL_REGULATION #define NUM_THERMAL_VALUES 4 #define ADC_STEPS 4 static int16_t temperature_values[NUM_THERMAL_VALUES]; #else #define ADC_STEPS 2 #endif uint16_t measurement = ADC; // latest 10-bit ADC reading adc_step = (adc_step + 1) & (ADC_STEPS-1); #ifdef USE_LVP // voltage if (adc_step == 1) { #ifdef USE_LVP_AVG // prime on first execution if (voltage == 0) { for(uint8_t i=0; i> 2; voltage = (uint16_t)(1.1*1024*10)/total + VOLTAGE_FUDGE_FACTOR; } #else // no USE_LVP_AVG // calculate actual voltage: volts * 10 // ADC = 1.1 * 1024 / volts // volts = 1.1 * 1024 / ADC voltage = (uint16_t)(1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR; #endif // if low, callback EV_voltage_low / EV_voltage_critical // (but only if it has been more than N ticks since last call) if (lvp_timer) { lvp_timer --; } else { // it has been long enough since the last warning if (voltage < VOLTAGE_LOW) { if (lvp_lowpass < LVP_LOWPASS_STRENGTH) { lvp_lowpass ++; } else { // try to send out a warning //uint8_t err = emit(EV_voltage_low, 0); //uint8_t err = emit_now(EV_voltage_low, 0); emit(EV_voltage_low, 0); //if (!err) { // on successful warning, reset counters lvp_timer = LVP_TIMER_START; lvp_lowpass = 0; //} } } else { // voltage not low? reset count lvp_lowpass = 0; } } } #endif // ifdef USE_LVP // TODO: temperature // start another measurement for next time #ifdef USE_THERMAL_REGULATION #ifdef USE_LVP if (adc_step < 2) ADMUX = ADMUX_VCC; else ADMUX = ADMUX_THERM; #else ADMUX = ADMUX_THERM; #endif #else #ifdef USE_LVP ADMUX = ADMUX_VCC; #endif #endif } inline void ADC_on() { // read voltage on VCC by default // disable digital input on VCC pin to reduce power consumption //DIDR0 |= (1 << ADC_DIDR); // FIXME: unsure how to handle for VCC pin // VCC / 1.1V reference ADMUX = ADMUX_VCC; // enable, start, prescale ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL; } inline void ADC_off() { ADCSRA &= ~(1<= 1 DDRB |= (1 << PWM1_PIN); TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) TCCR0A = PHASE; #elif PWM_CHANNELS >= 2 DDRB |= (1 << PWM2_PIN); #elif PWM_CHANNELS >= 3 DDRB |= (1 << PWM3_PIN); // Second PWM counter is ... weird TCCR1 = _BV (CS10); GTCCR = _BV (COM1B1) | _BV (PWM1B); OCR1C = 255; // Set ceiling value to maximum #elif PWM_CHANNELS == 4 // FIXME: How exactly do we do PWM on channel 4? DDRB |= (1 << PWM4_PIN); #endif // TODO: turn on ADC? // configure e-switch PORTB = (1 << SWITCH_PIN); // e-switch is the only input PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin // TODO: configure sleep mode set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Read config values and saved state // restore_state(); // TODO // TODO: handle long press vs short press (or even medium press)? #ifdef USE_DEBUG_BLINK //debug_blink(1); #endif // all booted -- turn interrupts back on PCINT_on(); WDT_on(); ADC_on(); sei(); // fallback for handling a few things push_state(default_state); // call recipe's setup setup(); // main loop while (1) { // TODO: update e-switch press state? // TODO: check voltage? // TODO: check temperature? // if event queue not empty, process and pop first item in queue? if (emissions[0].event != NULL) { emit_now(emissions[0].event, emissions[0].arg); delete_first_emission(); } } }