aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spaghetti-monster/baton.c157
-rw-r--r--spaghetti-monster/spaghetti-monster.h162
-rw-r--r--tk-attiny.h16
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