From 086aaabd39d3c6736ace56f2badac06b75567651 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 18 Aug 2017 23:31:11 -0600 Subject: First RoundTable example actually works (simple momentary 1-mode UI). Changed how tk-attiny.h detects whether a layout was defined. Changed how tk-attiny.h detects number of PWM channels for new-style layouts. Added no-underscore versions of delay functions. Lots of RoundTable refactoring and blank-filling. --- RoundTable/momentary.c | 72 ++++--- RoundTable/round-table.c | 489 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 404 insertions(+), 157 deletions(-) (limited to 'RoundTable') diff --git a/RoundTable/momentary.c b/RoundTable/momentary.c index 120406e..4394f46 100644 --- a/RoundTable/momentary.c +++ b/RoundTable/momentary.c @@ -20,7 +20,12 @@ */ #define RT_EMISAR_D4_LAYOUT +#define USE_LVP +#define USE_DEBUG_BLINK +#define OWN_DELAY +#define USE_DELAY_MS #include "round-table.c" +#include "tk-delay.h" volatile uint8_t brightness; @@ -34,35 +39,58 @@ void light_off() { PWM2_LVL = 0; } +//State momentary_state { uint8_t momentary_state(EventPtr event, uint16_t arg) { - switch(event) { - case EV_press: - brightness = 255; - light_on(); - // reset current event queue - empty_event_sequence(); - return 0; + if (event == EV_click1_press) { + brightness = 255; + light_on(); + // reset current event queue + empty_event_sequence(); + return 0; + } + + else if (event == EV_release) { + light_off(); + // reset current event queue + empty_event_sequence(); + return 0; + } - case EV_release: + /* + // LVP / low-voltage protection + //else if ((event == EV_voltage_low) || (event == EV_voltage_critical)) { + else if (event == EV_voltage_low) { + if (brightness > 0) brightness >>= 1; + else { light_off(); - // reset current event queue - empty_event_sequence(); - return 0; + standby_mode(); + } + return 0; + } + */ - // LVP / low-voltage protection - case EV_voltage_low: - case EV_voltage_critical: - if (brightness > 0) brightness >>= 1; - else { - light_off(); - standby_mode(); - } - return 0; + // event not handled + return 1; +} + +// LVP / low-voltage protection +void low_voltage() { + if (brightness > 0) brightness >>= 1; + else { + light_off(); + standby_mode(); } - return 1; // event not handled } void setup() { - set_state(momentary_state); + //debug_blink(1); + /* + brightness = 255; + light_on(); + delay_ms(10); + light_off(); + */ + + push_state(momentary_state); } diff --git a/RoundTable/round-table.c b/RoundTable/round-table.c index 6cd1b10..c6ffc08 100644 --- a/RoundTable/round-table.c +++ b/RoundTable/round-table.c @@ -1,23 +1,51 @@ #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); -// FIXME: Why does a state need a number? Why not just a function pointer? -// (I don't think it actually needs a number...) -typedef struct State { - uint8_t num; - EventCallback event_callback; -} State; -typedef struct State* StatePtr; +typedef uint8_t State(EventPtr event, uint16_t arg); +typedef State * StatePtr; volatile StatePtr current_state; #define EV_MAX_LEN 16 -volatile uint8_t current_event[EV_MAX_LEN]; +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 volatile int16_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 @@ -33,7 +61,7 @@ volatile int16_t temperature; #define A_UNDERHEATING 10 // TODO: add events for low voltage conditions #define A_VOLTAGE_LOW 11 -#define A_VOLTAGE_CRITICAL 12 +//#define A_VOLTAGE_CRITICAL 12 // TODO: maybe compare events by number instead of pointer? // (number = index in event types array) @@ -42,8 +70,6 @@ volatile int16_t temperature; // (also eliminates the need to duplicate single-entry events like for voltage or timer tick) // Event types -typedef PROGMEM const uint8_t Event; -typedef Event* EventPtr; Event EV_enter_state[] = { A_ENTER_STATE, 0 } ; @@ -53,7 +79,20 @@ Event EV_leave_state[] = { Event EV_tick[] = { A_TICK, 0 } ; -Event EV_press[] = { +#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 @@ -61,41 +100,66 @@ Event EV_press[] = { Event EV_release[] = { A_RELEASE, 0 }; -Event EV_press_release[] = { +Event EV_click1_release[] = { A_PRESS, A_RELEASE, 0 }; -#define EV_1click EV_press_release_timeout -Event EV_press_release_timeout[] = { +#define EV_1click EV_click1_complete +Event EV_click1_complete[] = { A_PRESS, A_RELEASE, A_RELEASE_TIMEOUT, 0 }; -#define EV_hold EV_press_hold +#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_press_hold[] = { +Event EV_click1_hold[] = { A_PRESS, - A_HOLD_TIMEOUT, + A_HOLD_START, 0 }; -Event EV_press_hold_release[] = { +Event EV_click1_hold_release[] = { A_PRESS, - A_HOLD_TIMEOUT, + A_HOLD_START, A_RELEASE, 0 }; -Event EV_press_release_press[] = { +Event EV_click2_press[] = { A_PRESS, A_RELEASE, A_PRESS, 0 }; -Event EV_press_release_press_release[] = { +Event EV_click2_release[] = { A_PRESS, A_RELEASE, A_PRESS, A_RELEASE, 0 }; -#define EV_2clicks EV_press_release_press_release_timeout -Event EV_press_release_press_release_timeout[] = { +#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, @@ -106,90 +170,41 @@ Event EV_press_release_press_release_timeout[] = { // A list of event types for easy iteration EventPtr event_sequences[] = { - EV_press, - EV_press_release, - EV_press_release_timeout, - EV_press_release_press, - EV_press_release_press_release, - EV_press_release_press_release_timeout, + 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, // ... }; -// TODO: move this to a separate UI-specific file -/* -State states[] = { - -}; -*/ - -// 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 uint8_t ticks_since_last_event = 0; // TODO: 16 bits? - -void WDT_tick() { - timer ++; - - //static uint8_t hold_ticks = 0; // TODO: 16 bits? - - // callback on each timer tick - emit(EV_tick, timer); - - // 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(hold_ticks); - // or - // //release_timeout() - // //emit(EV_press_release_timeout, 0); - // emit_current(0); - - // add 4-step voltage / temperature thing? - // with averaged values, - // once every N ticks? -} - -void button_change_interrupt() { - // TODO: debounce a little - - //ticks_since_last_event = 0; // something happened - - // TODO: add event to current sequence - - // check if sequence matches any defined sequences - // if so, send event to current state callback - emit_current(0); -} - -uint8_t emit_current(uint16_t arg) { - uint8_t err = 1; - 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 } -uint8_t push_state(State *new_state) { - // TODO: implement +// Search the pre-defined event list for one matching what the user just did, +// and emit it if one was found. +uint8_t 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; + } + } + return err; } -State * pop_state() { - // TODO: implement +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; + } } -uint8_t set_state(State *new_state) { +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); } -uint8_t _set_state(State *new_state) { - // call old state-exit hook (don't use stack) - current_state.callback(EV_leave_state, 0); - // set new state - current_state = new_state; - // call new state-enter hook (don't use stack) - current_state.callback(EV_enter_state, 0); +// 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? + + +uint8_t button_is_pressed() { + // debounce a little + uint8_t measurement = 0; + // measure for 8/64ths of a ms + for(uint8_t i=0; i<8; i++) { + // check current value + measurement = (measurement << 1) | ((PINB & (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? - // TODO: configure e-switch - //PORTB = (1 << SWITCH_PIN); // e-switch is the only input - //PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin + // 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); @@ -349,18 +560,26 @@ void main() { // TODO: handle long press vs short press (or even medium press)? - // TODO: call recipe's setup - setup(); + #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: if wait queue not empty, process and pop first item in queue + // TODO: update e-switch press state? + // TODO: if wait queue not empty, process and pop first item in queue? // TODO: check voltage? // TODO: check temperature? } -- cgit v1.2.3