diff options
| -rw-r--r-- | spaghetti-monster/baton.c | 157 | ||||
| -rw-r--r-- | spaghetti-monster/spaghetti-monster.h | 162 | ||||
| -rw-r--r-- | tk-attiny.h | 16 |
3 files changed, 267 insertions, 68 deletions
diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c new file mode 100644 index 0000000..de63914 --- /dev/null +++ b/spaghetti-monster/baton.c @@ -0,0 +1,157 @@ +/* + * Baton: Olight Baton-like UI for SpaghettiMonster. + * + * Copyright (C) 2017 Selene ToyKeeper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define FSM_EMISAR_D4_LAYOUT +#define USE_LVP +#define USE_DEBUG_BLINK +#define USE_DELAY_MS +#include "spaghetti-monster.h" + +// ../../bin/level_calc.py 2 7 7135 3 0.25 150 FET 1 10 1500 +uint8_t pwm1_modes[] = { 3, 27, 130, 255, 255, 255, 0, }; +uint8_t pwm2_modes[] = { 0, 0, 0, 12, 62, 141, 255, }; + +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t party_strobe_state(EventPtr event, uint16_t arg); + +uint8_t current_mode = 0; + +void set_mode(uint8_t mode) { + PWM1_LVL = pwm1_modes[mode]; + PWM2_LVL = pwm2_modes[mode]; +} + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + PWM1_LVL = 0; + PWM2_LVL = 0; + return 0; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, current_mode); + return 0; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_mode(current_mode); + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, sizeof(pwm1_modes)-1); + return 0; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(party_strobe_state, 0); + return 0; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_mode(0); + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + set_state(steady_state, 0); + } + return 1; +} + +uint8_t steady_state(EventPtr event, uint16_t arg) { + //static volatile uint8_t current_mode = 0; + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + current_mode = arg; + set_mode(arg); + return 0; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return 0; + } + // 2 clicks: go to strobe modes + else if (event == EV_2clicks) { + set_state(party_strobe_state, 2); + return 0; + } + // hold: change brightness + else if (event == EV_click1_hold) { + if ((arg % HOLD_TIMEOUT) == 0) { + current_mode = (current_mode+1) % sizeof(pwm1_modes); + set_mode(current_mode); + } + return 0; + } + return 1; +} + +uint8_t party_strobe_state(EventPtr event, uint16_t arg) { + static volatile uint8_t frames = 0; + static volatile uint8_t between = 0; + if (event == EV_enter_state) { + between = arg; + frames = 0; + return 0; + } + // strobe the emitter + else if (event == EV_tick) { + if (frames == 0) { + PWM1_LVL = 255; + PWM2_LVL = 0; + delay_ms(1); + PWM1_LVL = 0; + } + //frames = (frames + 1) % between; + frames++; + if (frames > between) frames = 0; + return 0; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return 0; + } + // 2 clicks: go back to regular modes + else if (event == EV_2clicks) { + set_state(steady_state, 1); + return 0; + } + // hold: change speed + else if (event == EV_click1_hold) { + if ((arg % HOLD_TIMEOUT) == 0) { + between = (between+1)%6; + frames = 0; + } + return 0; + } + return 1; +} + +void low_voltage() { + // FIXME: do something +} + +void setup() { + debug_blink(2); + + push_state(off_state, 0); +} diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index af6d828..e51416a 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -49,7 +49,7 @@ volatile StatePtr current_state; 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; +static volatile uint16_t ticks_since_last_event = 0; #ifdef USE_LVP // volts * 10 @@ -80,20 +80,23 @@ void debug_blink(uint8_t num) { } #endif +// timeout durations in ticks (each tick 1/60th s) +#define HOLD_TIMEOUT 24 +#define RELEASE_TIMEOUT 24 + #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 +#define A_HOLD 5 +#define A_RELEASE 6 +#define A_RELEASE_TIMEOUT 7 // TODO: add events for over/under-heat conditions (with parameter for severity) -#define A_OVERHEATING 9 -#define A_UNDERHEATING 10 +#define A_OVERHEATING 8 +#define A_UNDERHEATING 9 // TODO: add events for low voltage conditions -#define A_VOLTAGE_LOW 11 -//#define A_VOLTAGE_CRITICAL 12 +#define A_VOLTAGE_LOW 10 +//#define A_VOLTAGE_CRITICAL 11 #define A_DEBUG 255 // test event for debugging // TODO: maybe compare events by number instead of pointer? @@ -151,11 +154,11 @@ Event EV_click1_complete[] = { // Or "start+tick" with a tick number? Event EV_click1_hold[] = { A_PRESS, - A_HOLD_START, + A_HOLD, 0 }; Event EV_click1_hold_release[] = { A_PRESS, - A_HOLD_START, + A_HOLD, A_RELEASE, 0 }; Event EV_click2_press[] = { @@ -248,7 +251,25 @@ void push_event(uint8_t ev_type) { } } -#define EMISSION_QUEUE_LEN 8 +// find and return last action in the current event sequence +/* +uint8_t last_event(uint8_t offset) { + uint8_t i; + for(i=0; current_event[i] && (i<EV_MAX_LEN); i++); + if (i == EV_MAX_LEN) return current_event[EV_MAX_LEN-offset]; + else if (i >= offset) return current_event[i-offset]; + return 0; +} +*/ + +inline uint8_t last_event_num() { + uint8_t i; + for(i=0; current_event[i] && (i<EV_MAX_LEN); i++); + return i; +} + + +#define EMISSION_QUEUE_LEN 16 // no comment about "volatile emissions" volatile Emission emissions[EMISSION_QUEUE_LEN]; @@ -321,22 +342,22 @@ void emit_current_event(uint16_t arg) { //return err; } -void _set_state(StatePtr new_state) { +void _set_state(StatePtr new_state, uint16_t arg) { // call old state-exit hook (don't use stack) - if (current_state != NULL) current_state(EV_leave_state, 0); + if (current_state != NULL) current_state(EV_leave_state, arg); // 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); + if (new_state != NULL) current_state(EV_enter_state, arg); } -int8_t push_state(StatePtr new_state) { +int8_t push_state(StatePtr new_state, uint16_t arg) { 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); + _set_state(new_state, arg); return state_stack_len; } else { // TODO: um... how is a flashlight supposed to handle a recursion depth error? @@ -355,14 +376,16 @@ StatePtr pop_state() { if (state_stack_len > 0) { new_state = state_stack[state_stack_len-1]; } - _set_state(new_state); + // FIXME: what should 'arg' be? + // FIXME: do we need a EV_reenter_state? + _set_state(new_state, 0); return old_state; } -uint8_t set_state(StatePtr new_state) { +uint8_t set_state(StatePtr new_state, uint16_t arg) { // FIXME: this calls exit/enter hooks it shouldn't pop_state(); - return push_state(new_state); + return push_state(new_state, arg); } // TODO? add events to a queue when inside an interrupt @@ -412,36 +435,12 @@ ISR(PCINT0_vect) { emit_current_event(0); } -// TODO: implement +// clock tick -- this runs every 16ms (62.5 fps) 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 + // increment, but loop from max back to half ticks_since_last_event = (ticks_since_last_event + 1) \ - | (ticks_since_last_event & 0x80); - - //static uint8_t hold_ticks = 0; // TODO: 16 bits? + | (ticks_since_last_event & 0x8000); // callback on each timer tick emit(EV_tick, ticks_since_last_event); @@ -449,13 +448,42 @@ ISR(WDT_vect) { // 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); + + // preload recent events + uint8_t le_num = last_event_num(); + uint8_t last_event = 0; + uint8_t prev_event = 0; + if (le_num >= 1) last_event = current_event[le_num-1]; + if (le_num >= 2) prev_event = current_event[le_num-2]; + + // user held button long enough to count as a long click? + if (last_event == A_PRESS) { + if (ticks_since_last_event == HOLD_TIMEOUT) { + push_event(A_HOLD); + emit_current_event(0); + } + } + + // user is still holding button, so tick + else if (last_event == A_HOLD) { + emit_current_event(ticks_since_last_event); + } + + // detect completed button presses with expired timeout + else if (last_event == A_RELEASE) { + // no timeout required when releasing a long-press + // TODO? move this logic to PCINT() and simplify things here? + if (prev_event == A_HOLD) { + //emit_current_event(0); // should have been emitted by PCINT + empty_event_sequence(); + } + // end and clear event after release timeout + else if (ticks_since_last_event == RELEASE_TIMEOUT) { + push_event(A_RELEASE_TIMEOUT); + emit_current_event(0); + empty_event_sequence(); + } + } #if defined(USE_LVP) || defined(USE_THERMAL_REGULATION) // start a new ADC measurement every 4 ticks @@ -677,20 +705,36 @@ int main() { //PCINT_off(); // configure PWM channels - #if PWM_CHANNELS >= 1 + #if PWM_CHANNELS == 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 << PWM1_PIN); + DDRB |= (1 << PWM2_PIN); TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) TCCR0A = PHASE; - #elif PWM_CHANNELS >= 2 + #elif PWM_CHANNELS == 3 + DDRB |= (1 << PWM1_PIN); DDRB |= (1 << PWM2_PIN); - #elif PWM_CHANNELS >= 3 - DDRB |= (1 << PWM3_PIN); + TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) + TCCR0A = PHASE; // Second PWM counter is ... weird + DDRB |= (1 << PWM3_PIN); TCCR1 = _BV (CS10); GTCCR = _BV (COM1B1) | _BV (PWM1B); OCR1C = 255; // Set ceiling value to maximum #elif PWM_CHANNELS == 4 + DDRB |= (1 << PWM1_PIN); + DDRB |= (1 << PWM2_PIN); + TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) + TCCR0A = PHASE; + // Second PWM counter is ... weird + DDRB |= (1 << PWM3_PIN); // FIXME: How exactly do we do PWM on channel 4? + TCCR1 = _BV (CS10); + GTCCR = _BV (COM1B1) | _BV (PWM1B); + OCR1C = 255; // Set ceiling value to maximum DDRB |= (1 << PWM4_PIN); #endif @@ -718,7 +762,7 @@ int main() { sei(); // fallback for handling a few things - push_state(default_state); + push_state(default_state, 0); // call recipe's setup setup(); diff --git a/tk-attiny.h b/tk-attiny.h index e852109..9779eb0 100644 --- a/tk-attiny.h +++ b/tk-attiny.h @@ -178,17 +178,15 @@ #define PWM_CHANNELS 2 -#define AUXLED_PIN PB4 // pin 3 +#define AUXLED_PIN PB4 // pin 3 -#define SWITCH_PIN PB3 // pin 2 -#define SWITCH_PCINT PCINT3 // pin 2 pin change interrupt -#define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4) -#define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3 +#define SWITCH_PIN PB3 // pin 2 +#define SWITCH_PCINT PCINT3 // pin 2 pin change interrupt -#define PWM2_PIN PB1 // pin 6, FET PWM -#define PWM2_LVL OCR0B // OCR0B is the output compare register for PB1 -#define PWM1_PIN PB0 // pin 5, 1x7135 PWM -#define PWM1_LVL OCR0A // OCR0A is the output compare register for PB0 +#define PWM1_PIN PB0 // pin 5, 1x7135 PWM +#define PWM1_LVL OCR0A // OCR0A is the output compare register for PB0 +#define PWM2_PIN PB1 // pin 6, FET PWM +#define PWM2_LVL OCR0B // OCR0B is the output compare register for PB1 #define VOLTAGE_PIN PB2 // pin 7, voltage ADC #define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2 |
