From 65167c016935885552035bf983f4a5b739048f61 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 14:56:39 -0600 Subject: Added unfinished UI similar to Olight Baton series. Fixed 2nd PWM channel init. May have fixed 3rd and 4th too. Added handling for complete normal clicks, but it's kind of flaky so far. Added handling for sustained button holds. Added missing 'arg' to state change functions. --- spaghetti-monster/baton.c | 157 ++++++++++++++++++++++++++++++++ spaghetti-monster/spaghetti-monster.h | 162 +++++++++++++++++++++------------- tk-attiny.h | 16 ++-- 3 files changed, 267 insertions(+), 68 deletions(-) create mode 100644 spaghetti-monster/baton.c 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 . + */ + +#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= offset) return current_event[i-offset]; + return 0; +} +*/ + +inline uint8_t last_event_num() { + uint8_t i; + for(i=0; current_event[i] && (i 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 -- cgit v1.2.3