From 8d2f442317fd3678213a14307f95d2f5b54c5623 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 12:17:58 -0600 Subject: Renamed from RoundTable to SpaghettiMonster (FSM). --- spaghetti-monster/fsm-ramping.h | 26 ++ spaghetti-monster/momentary.c | 85 ++++ spaghetti-monster/spaghetti-monster.h | 737 ++++++++++++++++++++++++++++++++++ 3 files changed, 848 insertions(+) create mode 100644 spaghetti-monster/fsm-ramping.h create mode 100644 spaghetti-monster/momentary.c create mode 100644 spaghetti-monster/spaghetti-monster.h (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h new file mode 100644 index 0000000..fd4d40b --- /dev/null +++ b/spaghetti-monster/fsm-ramping.h @@ -0,0 +1,26 @@ +/* + * fsm-ramping.h: Ramping functions for SpaghettiMonster. + * Handles 1- to 4-channel smooth ramping on a single LED. + * + * 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 . + */ + +// TODO: ramp tables +// TODO: RAMP_SIZE / MAX_LVL +// TODO: actual_lvl +// TODO: target_lvl +// TODO: set_lvl +// TODO: set_lvl_smooth diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c new file mode 100644 index 0000000..bb74795 --- /dev/null +++ b/spaghetti-monster/momentary.c @@ -0,0 +1,85 @@ +/* + * Momentary: Very simple example UI for SpaghettiMonster. + * Is intended to be the simplest possible FSM e-switch UI. + * The light is on while the button is held; off otherwise. + * + * 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 +#include "spaghetti-monster.h" + +volatile uint8_t brightness; +volatile uint8_t on_now; + +void light_on() { + on_now = 1; + PWM1_LVL = brightness; + PWM2_LVL = 0; +} + +void light_off() { + on_now = 0; + PWM1_LVL = 0; + PWM2_LVL = 0; +} + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + + if (event == EV_click1_press) { + brightness = 255; + light_on(); + // don't attempt to parse multiple clicks + empty_event_sequence(); + return 0; + } + + else if (event == EV_release) { + light_off(); + // don't attempt to parse multiple clicks + empty_event_sequence(); + return 0; + } + + else if (event == EV_debug) { + //PWM1_LVL = arg&0xff; + DEBUG_FLASH; + return 0; + } + + // event not handled + return 1; +} + +// LVP / low-voltage protection +void low_voltage() { + debug_blink(3); + if (brightness > 0) { + brightness >>= 1; + if (on_now) light_on(); + } else { + light_off(); + standby_mode(); + } +} + +void setup() { + debug_blink(2); + + push_state(momentary_state); +} diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h new file mode 100644 index 0000000..af6d828 --- /dev/null +++ b/spaghetti-monster/spaghetti-monster.h @@ -0,0 +1,737 @@ +/* + * SpaghettiMonster: Generic foundation code for e-switch flashlights. + * Other possible names: + * - FSM + * - RoundTable + * - Mostly Harmless + * - ... + * + * 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 . + */ + +#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(); + } + } +} -- cgit v1.2.3 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 +++++++++++++++++++++------------- 2 files changed, 260 insertions(+), 59 deletions(-) create mode 100644 spaghetti-monster/baton.c (limited to 'spaghetti-monster') 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(); -- cgit v1.2.3 From ef05435261fac31790303dbff16bcc194e9e5cb5 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 15:33:33 -0600 Subject: Fixed unreliability of short-click detection. (it was doing stuff like "press, release, release, timeout" so it didn't match "press, release, timeout") (it may have also been missing the exact tick it needed, so I made it use >= instead of ==, but this is theoretical and harmless if I was wrong) Made baton mode memory work a bit better for both regular and strobe modes. Made baton fast strobe pulses shorter for better motion freezing. Added USE_DELAY_ZERO option as an alternate for USE_FINE_DELAY. --- spaghetti-monster/baton.c | 17 +++++++++++------ spaghetti-monster/spaghetti-monster.h | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index de63914..ed2b444 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -21,16 +21,19 @@ #define USE_LVP #define USE_DEBUG_BLINK #define USE_DELAY_MS +#define USE_DELAY_ZERO #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, }; +// FSM states 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); +// brightness control uint8_t current_mode = 0; void set_mode(uint8_t mode) { @@ -43,6 +46,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { PWM1_LVL = 0; PWM2_LVL = 0; + // TODO: standby_mode(); return 0; } // 1 click: regular mode @@ -90,7 +94,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 2 clicks: go to strobe modes else if (event == EV_2clicks) { - set_state(party_strobe_state, 2); + set_state(party_strobe_state, 0xff); return 0; } // hold: change brightness @@ -106,18 +110,19 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t party_strobe_state(EventPtr event, uint16_t arg) { static volatile uint8_t frames = 0; - static volatile uint8_t between = 0; + static volatile uint8_t between = 2; if (event == EV_enter_state) { - between = arg; + if (arg < 64) between = arg; frames = 0; return 0; } - // strobe the emitter + // tick: strobe the emitter else if (event == EV_tick) { if (frames == 0) { PWM1_LVL = 255; PWM2_LVL = 0; - delay_ms(1); + if (between < 3) delay_zero(); + else delay_ms(1); PWM1_LVL = 0; } //frames = (frames + 1) % between; @@ -132,7 +137,7 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg) { } // 2 clicks: go back to regular modes else if (event == EV_2clicks) { - set_state(steady_state, 1); + set_state(steady_state, current_mode); return 0; } // hold: change speed diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index e51416a..cfabea8 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -243,8 +243,10 @@ void empty_event_sequence() { void push_event(uint8_t ev_type) { ticks_since_last_event = 0; // something happened uint8_t i; - for(i=0; current_event[i] && (i= HOLD_TIMEOUT) { push_event(A_HOLD); emit_current_event(0); } @@ -478,7 +487,7 @@ ISR(WDT_vect) { empty_event_sequence(); } // end and clear event after release timeout - else if (ticks_since_last_event == RELEASE_TIMEOUT) { + else if (ticks_since_last_event >= RELEASE_TIMEOUT) { push_event(A_RELEASE_TIMEOUT); emit_current_event(0); empty_event_sequence(); -- cgit v1.2.3 From ed98882eace49e6482c78c6723fbcda9daca1510 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 15:57:10 -0600 Subject: Improved memory handling in FSM Baton. Improved brightness ramp. (lower low) Added double-click to/from turbo in steady modes. Moved strobes to triple-click. --- spaghetti-monster/baton.c | 58 +++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 22 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index ed2b444..ea8d49f 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -24,9 +24,9 @@ #define USE_DELAY_ZERO #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, }; +// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 +uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, }; +uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, }; // FSM states uint8_t off_state(EventPtr event, uint16_t arg); @@ -34,11 +34,13 @@ uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t party_strobe_state(EventPtr event, uint16_t arg); // brightness control -uint8_t current_mode = 0; +uint8_t memorized_level = 0; +uint8_t actual_level = 0; -void set_mode(uint8_t mode) { - PWM1_LVL = pwm1_modes[mode]; - PWM2_LVL = pwm2_modes[mode]; +void set_mode(uint8_t lvl) { + actual_level = lvl; + PWM1_LVL = pwm1_modes[lvl]; + PWM2_LVL = pwm2_modes[lvl]; } uint8_t off_state(EventPtr event, uint16_t arg) { @@ -49,14 +51,18 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // TODO: standby_mode(); return 0; } - // 1 click: regular mode - else if (event == EV_1click) { - set_state(steady_state, current_mode); - return 0; + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_mode(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); + set_mode(memorized_level); + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return 0; } // 2 clicks: highest mode else if (event == EV_2clicks) { @@ -68,10 +74,6 @@ uint8_t off_state(EventPtr event, uint16_t arg) { 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); @@ -80,10 +82,12 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } 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; + // remember this level, unless it's moon or turbo + if ((arg > 0) && (arg < sizeof(pwm1_modes)-1)) + memorized_level = arg; + // use the requested level even if not memorized set_mode(arg); return 0; } @@ -92,16 +96,26 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return 0; } - // 2 clicks: go to strobe modes + // 2 clicks: go to/from highest level else if (event == EV_2clicks) { + if (actual_level < sizeof(pwm1_modes)-1) { + memorized_level = actual_level; // in case we're on moon + set_mode(sizeof(pwm1_modes)-1); + } + else + set_mode(memorized_level); + return 0; + } + // 3 clicks: go to strobe modes + else if (event == EV_3clicks) { set_state(party_strobe_state, 0xff); 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); + memorized_level = (memorized_level+1) % sizeof(pwm1_modes); + set_mode(memorized_level); } return 0; } @@ -137,7 +151,7 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg) { } // 2 clicks: go back to regular modes else if (event == EV_2clicks) { - set_state(steady_state, current_mode); + set_state(steady_state, memorized_level); return 0; } // hold: change speed -- cgit v1.2.3 From c4e97f9c0b7f66f539e73983146c21e1c30311ac Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 16:00:29 -0600 Subject: Fixed momentary UI (API changed a little). --- spaghetti-monster/momentary.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c index bb74795..a585152 100644 --- a/spaghetti-monster/momentary.c +++ b/spaghetti-monster/momentary.c @@ -81,5 +81,5 @@ void low_voltage() { void setup() { debug_blink(2); - push_state(momentary_state); + push_state(momentary_state, 0); } -- cgit v1.2.3 From 9a6965d62578bc7260c7ba1d4860152721c12ea9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 16:25:39 -0600 Subject: Extra debouncing in PCINT (don't emit event if push was rejected). Fixed memory error in Baton -- long-press from off didn't restart at moon. Made Momentary and Baton go to sleep while light is off. --- spaghetti-monster/baton.c | 6 ++++-- spaghetti-monster/momentary.c | 20 ++++++-------------- spaghetti-monster/spaghetti-monster.h | 20 +++++++------------- 3 files changed, 17 insertions(+), 29 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index ea8d49f..02b7c00 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -48,7 +48,9 @@ uint8_t off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { PWM1_LVL = 0; PWM2_LVL = 0; - // TODO: standby_mode(); + // sleep while off (lower power use) + //empty_event_sequence(); // just in case (but shouldn't be needed) + standby_mode(); return 0; } // hold (initially): go to lowest level, but allow abort for regular click @@ -114,7 +116,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // hold: change brightness else if (event == EV_click1_hold) { if ((arg % HOLD_TIMEOUT) == 0) { - memorized_level = (memorized_level+1) % sizeof(pwm1_modes); + memorized_level = (actual_level+1) % sizeof(pwm1_modes); set_mode(memorized_level); } return 0; diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c index a585152..23cc359 100644 --- a/spaghetti-monster/momentary.c +++ b/spaghetti-monster/momentary.c @@ -44,35 +44,28 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { if (event == EV_click1_press) { brightness = 255; light_on(); - // don't attempt to parse multiple clicks - empty_event_sequence(); + empty_event_sequence(); // don't attempt to parse multiple clicks return 0; } else if (event == EV_release) { light_off(); - // don't attempt to parse multiple clicks - empty_event_sequence(); + empty_event_sequence(); // don't attempt to parse multiple clicks + standby_mode(); // sleep while light is off return 0; } - else if (event == EV_debug) { - //PWM1_LVL = arg&0xff; - DEBUG_FLASH; - return 0; - } - - // event not handled - return 1; + return 1; // event not handled } // LVP / low-voltage protection void low_voltage() { - debug_blink(3); if (brightness > 0) { + debug_blink(3); brightness >>= 1; if (on_now) light_on(); } else { + debug_blink(8); light_off(); standby_mode(); } @@ -80,6 +73,5 @@ void low_voltage() { void setup() { debug_blink(2); - push_state(momentary_state, 0); } diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index cfabea8..fe5e939 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -240,7 +240,7 @@ void empty_event_sequence() { for(uint8_t i=0; i. + */ + +#ifndef FSM_ADC_C +#define FSM_ADC_C + +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<> 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 +} + +#endif diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h new file mode 100644 index 0000000..ac42333 --- /dev/null +++ b/spaghetti-monster/fsm-adc.h @@ -0,0 +1,40 @@ +/* + * fsm-adc.h: ADC (voltage, temperature) functions 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 . + */ + +#ifndef FSM_ADC_H +#define FSM_ADC_H + +#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 + +inline void ADC_on(); +inline void ADC_off(); + +#endif diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c new file mode 100644 index 0000000..6dda236 --- /dev/null +++ b/spaghetti-monster/fsm-events.c @@ -0,0 +1,137 @@ +/* + * fsm-events.c: Event-handling functions 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 . + */ + +#ifndef FSM_EVENTS_C +#define FSM_EVENTS_C + +// 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) + +// 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= 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; 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; +} + +// TODO? add events to a queue when inside an interrupt +// instead of calling the event functions directly? +// (then empty the queue in main loop?) + +#endif diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h new file mode 100644 index 0000000..434fa10 --- /dev/null +++ b/spaghetti-monster/fsm-events.h @@ -0,0 +1,197 @@ +/* + * fsm-events.h: Event-handling functions 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 . + */ + +#ifndef FSM_EVENTS_H +#define FSM_EVENTS_H + +#include + +// typedefs +typedef PROGMEM const uint8_t Event; +typedef Event * EventPtr; +typedef struct Emission { + EventPtr event; + uint16_t arg; +} Emission; + +#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 uint16_t ticks_since_last_event = 0; + +// 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 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 8 +#define A_UNDERHEATING 9 +// TODO: add events for low voltage conditions +#define A_VOLTAGE_LOW 10 +//#define A_VOLTAGE_CRITICAL 11 +#define A_DEBUG 255 // test event for debugging + +// 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, + 0 }; +Event EV_click1_hold_release[] = { + A_PRESS, + A_HOLD, + 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 button 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); +void empty_event_sequence(); +uint8_t push_event(uint8_t ev_type); +// uint8_t last_event(uint8_t offset); +inline uint8_t last_event_num(); + + +#define EMISSION_QUEUE_LEN 16 +// no comment about "volatile emissions" +volatile Emission emissions[EMISSION_QUEUE_LEN]; + +void append_emission(EventPtr event, uint16_t arg); +void delete_first_emission(); +//#define emit_now emit +uint8_t emit_now(EventPtr event, uint16_t arg); +void emit(EventPtr event, uint16_t arg); +void emit_current_event(uint16_t arg); + +#endif diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c new file mode 100644 index 0000000..2faaeda --- /dev/null +++ b/spaghetti-monster/fsm-main.c @@ -0,0 +1,107 @@ +/* + * fsm-main.c: main() function 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 . + */ + +#ifndef FSM_MAIN_C +#define FSM_MAIN_C + +#include "fsm-main.h" + +int main() { + // Don't allow interrupts while booting + cli(); + //WDT_off(); + //PCINT_off(); + + // configure PWM channels + #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 == 3 + 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); + 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 + + // 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, 0); + + // 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(); + } + } +} + +#endif diff --git a/spaghetti-monster/fsm-main.h b/spaghetti-monster/fsm-main.h new file mode 100644 index 0000000..cc469d7 --- /dev/null +++ b/spaghetti-monster/fsm-main.h @@ -0,0 +1,25 @@ +/* + * fsm-main.h: main() function 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 . + */ + +#ifndef FSM_MAIN_H +#define FSM_MAIN_H + +int main(); + +#endif diff --git a/spaghetti-monster/fsm-pcint.c b/spaghetti-monster/fsm-pcint.c new file mode 100644 index 0000000..763c1fe --- /dev/null +++ b/spaghetti-monster/fsm-pcint.c @@ -0,0 +1,76 @@ +/* + * fsm-pcint.c: PCINT (Pin Change Interrupt) functions 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 . + */ + +#ifndef FSM_PCINT_C +#define FSM_PCINT_C + +#include +#include + +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; +} + +inline void PCINT_on() { + // enable pin change interrupt for pin N + GIMSK |= (1 << PCIE); + // only pay attention to the e-switch pin + //PCMSK = (1 << SWITCH_PCINT); + // set bits 1:0 to 0b01 (interrupt on rising *and* falling edge) (default) + // MCUCR &= 0b11111101; MCUCR |= 0b00000001; +} + +inline void PCINT_off() { + // disable all pin-change interrupts + GIMSK &= ~(1 << PCIE); +} + +//void button_change_interrupt() { +ISR(PCINT0_vect) { + + //DEBUG_FLASH; + + uint8_t pushed; + + // add event to current sequence + if (button_is_pressed()) { + pushed = push_event(A_PRESS); + } else { + pushed = push_event(A_RELEASE); + } + + // check if sequence matches any defined sequences + // if so, send event to current state callback + if (pushed) emit_current_event(0); +} + +#endif diff --git a/spaghetti-monster/fsm-pcint.h b/spaghetti-monster/fsm-pcint.h new file mode 100644 index 0000000..cda5ad7 --- /dev/null +++ b/spaghetti-monster/fsm-pcint.h @@ -0,0 +1,29 @@ +/* + * fsm-pcint.h: PCINT (Pin Change Interrupt) functions 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 . + */ + +#ifndef FSM_PCINT_H +#define FSM_PCINT_H + +//static volatile uint8_t button_was_pressed; +#define BP_SAMPLES 16 +uint8_t button_is_pressed(); +inline void PCINT_on(); +inline void PCINT_off(); + +#endif diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c new file mode 100644 index 0000000..bef0533 --- /dev/null +++ b/spaghetti-monster/fsm-standby.c @@ -0,0 +1,53 @@ +/* + * fsm-standby.c: standby mode functions 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 . + */ + +#ifndef FSM_STANDBY_C +#define FSM_STANDBY_C + +#include +#include + +#include "fsm-adc.h" +#include "fsm-wdt.h" +#include "fsm-pcint.h" + +// low-power standby mode used while off but power still connected +#define standby_mode sleep_until_eswitch_pressed +void sleep_until_eswitch_pressed() +{ + WDT_off(); + ADC_off(); + + // make sure switch isn't currently pressed + while (button_is_pressed()) {} + + PCINT_on(); // wake on e-switch event + + sleep_enable(); + sleep_bod_disable(); + sleep_cpu(); // wait here + + // something happened; wake up + sleep_disable(); + PCINT_on(); + ADC_on(); + WDT_on(); +} + +#endif diff --git a/spaghetti-monster/fsm-standby.h b/spaghetti-monster/fsm-standby.h new file mode 100644 index 0000000..baeb6af --- /dev/null +++ b/spaghetti-monster/fsm-standby.h @@ -0,0 +1,26 @@ +/* + * fsm-standby.h: standby mode functions 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 . + */ + +#ifndef FSM_STANDBY_H +#define FSM_STANDBY_H + +#define standby_mode sleep_until_eswitch_pressed +void sleep_until_eswitch_pressed(); + +#endif diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c new file mode 100644 index 0000000..652a9f2 --- /dev/null +++ b/spaghetti-monster/fsm-states.c @@ -0,0 +1,108 @@ +/* + * fsm-states.c: State-handling functions 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 . + */ + +#ifndef FSM_STATES_C +#define FSM_STATES_C + +#include "fsm-states.h" +#include "fsm-adc.h" + +// TODO: if callback doesn't handle current event, +// pass event to next state on stack? +// Callback return values: +// 0: event handled normally +// 1: event not handled +// 255: error (not sure what this would even mean though, or what difference it would make) +// TODO: function to call stacked callbacks until one returns "handled" + +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, 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, arg); +} + +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, arg); + 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]; + } + // 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, uint16_t arg) { + // FIXME: this calls exit/enter hooks it shouldn't + pop_state(); + return push_state(new_state, arg); +} + +// bottom state on stack +// handles default actions for LVP, thermal regulation, etc +uint8_t default_state(EventPtr event, uint16_t arg) { + if (0) {} + + #ifdef USE_LVP + else if (event == EV_voltage_low) { + low_voltage(); + return 0; + } + #endif + + #ifdef USE_THERMAL_REGULATION + else if (event == EV_temperature_high) { + high_temperature(); + return 0; + } + + else if (event == EV_temperature_low) { + low_temperature(); + return 0; + } + #endif + + // event not handled + return 1; +} + +#endif diff --git a/spaghetti-monster/fsm-states.h b/spaghetti-monster/fsm-states.h new file mode 100644 index 0000000..0daf915 --- /dev/null +++ b/spaghetti-monster/fsm-states.h @@ -0,0 +1,44 @@ +/* + * fsm-states.h: State-handling functions 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 . + */ + +#ifndef FSM_STATES_H +#define FSM_STATES_H + +#include "fsm-adc.h" + +// typedefs +typedef uint8_t State(EventPtr event, uint16_t arg); +typedef State * StatePtr; + +// top of the stack +volatile StatePtr current_state; + +// stack for states, to allow shared utility states like "input a number" +// and such, which return to the previous state after finishing +#define STATE_STACK_SIZE 8 +StatePtr state_stack[STATE_STACK_SIZE]; +uint8_t state_stack_len = 0; + +void _set_state(StatePtr new_state, uint16_t arg); +int8_t push_state(StatePtr new_state, uint16_t arg); +StatePtr pop_state(); +uint8_t set_state(StatePtr new_state, uint16_t arg); +uint8_t default_state(EventPtr event, uint16_t arg); + +#endif diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c new file mode 100644 index 0000000..afcf467 --- /dev/null +++ b/spaghetti-monster/fsm-wdt.c @@ -0,0 +1,107 @@ +/* + * fsm-wdt.c: WDT (Watch Dog Timer) functions 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 . + */ + +#ifndef FSM_WDT_C +#define FSM_WDT_C + +#include +#include + +void WDT_on() +{ + // interrupt every 16ms + //cli(); // Disable interrupts + wdt_reset(); // Reset the WDT + WDTCR |= (1<= 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 + static uint8_t adc_trigger = 0; + adc_trigger ++; + if (adc_trigger > 3) { + adc_trigger = 0; + ADCSRA |= (1 << ADSC) | (1 << ADIE); + } + #endif +} + +#endif diff --git a/spaghetti-monster/fsm-wdt.h b/spaghetti-monster/fsm-wdt.h new file mode 100644 index 0000000..d7c64f5 --- /dev/null +++ b/spaghetti-monster/fsm-wdt.h @@ -0,0 +1,26 @@ +/* + * fsm-wdt.h: WDT (Watch Dog Timer) functions 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 . + */ + +#ifndef FSM_WDT_H +#define FSM_WDT_H + +void WDT_on(); +inline void WDT_off(); + +#endif diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index fe5e939..4eeb7de 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -24,46 +24,16 @@ #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 uint16_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 +// include project definitions to help with recognizing symbols +#include "fsm-events.h" +#include "fsm-states.h" +#include "fsm-adc.h" +#include "fsm-wdt.h" +#include "fsm-pcint.h" +#include "fsm-standby.h" +#include "fsm-main.h" #ifdef USE_DEBUG_BLINK #define OWN_DELAY @@ -80,705 +50,18 @@ 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 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 8 -#define A_UNDERHEATING 9 -// TODO: add events for low voltage conditions -#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? -// (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, - 0 }; -Event EV_click1_hold_release[] = { - A_PRESS, - A_HOLD, - 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= 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; 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, uint16_t arg) { - // call old state-exit hook (don't use stack) - 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, arg); -} - -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, arg); - 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]; - } - // 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, uint16_t arg) { - // FIXME: this calls exit/enter hooks it shouldn't - pop_state(); - return push_state(new_state, arg); -} - -// 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; - - uint8_t pushed; - - // add event to current sequence - if (button_is_pressed()) { - pushed = push_event(A_PRESS); - } else { - pushed = push_event(A_RELEASE); - } - - // check if sequence matches any defined sequences - // if so, send event to current state callback - if (pushed) emit_current_event(0); -} - -// clock tick -- this runs every 16ms (62.5 fps) -ISR(WDT_vect) { - //if (ticks_since_last_event < 0xff) ticks_since_last_event ++; - // increment, but loop from max back to half - ticks_since_last_event = (ticks_since_last_event + 1) \ - | (ticks_since_last_event & 0x8000); - - // 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 - - // 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 - 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, 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 == 3 - 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); - 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 - - // 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, 0); - - // 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(); - } - } -} +// include executable functions too, for easier compiling +#include "fsm-states.c" +#include "fsm-events.c" +#include "fsm-adc.c" +#include "fsm-wdt.c" +#include "fsm-pcint.c" +#include "fsm-standby.c" +#include "fsm-main.c" -- cgit v1.2.3 From 5ebd512ef3d91cd26e147e1dd7a44f6f227498dd Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 17:30:00 -0600 Subject: Baton: Strobe should be full-power. --- spaghetti-monster/baton.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 02b7c00..67ca3b9 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -135,11 +135,11 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg) { // tick: strobe the emitter else if (event == EV_tick) { if (frames == 0) { - PWM1_LVL = 255; - PWM2_LVL = 0; + PWM1_LVL = 0; + PWM2_LVL = 255; if (between < 3) delay_zero(); else delay_ms(1); - PWM1_LVL = 0; + PWM2_LVL = 0; } //frames = (frames + 1) % between; frames++; -- cgit v1.2.3 From f1476a7b8e010dd27eb413c5f8ede19aa6f45b49 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 19 Aug 2017 18:05:08 -0600 Subject: Added missing returns, made strobe-from-off use memorized strobe speed, set initial brightness to ~10lm after battery change. --- spaghetti-monster/baton.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 67ca3b9..194dbec 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -34,7 +34,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t party_strobe_state(EventPtr event, uint16_t arg); // brightness control -uint8_t memorized_level = 0; +uint8_t memorized_level = 1; uint8_t actual_level = 0; void set_mode(uint8_t lvl) { @@ -56,10 +56,12 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // hold (initially): go to lowest level, but allow abort for regular click else if (event == EV_click1_press) { set_mode(0); + return 0; } // 1 click (before timeout): go to memorized level, but allow abort for double click else if (event == EV_click1_release) { set_mode(memorized_level); + return 0; } // 1 click: regular mode else if (event == EV_1click) { @@ -73,12 +75,13 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // 3 clicks: strobe mode else if (event == EV_3clicks) { - set_state(party_strobe_state, 0); + set_state(party_strobe_state, 255); return 0; } // hold: go to lowest level else if (event == EV_click1_hold) { set_state(steady_state, 0); + return 0; } return 1; } -- cgit v1.2.3 From eabfc98408e6541c2e2bcce29e2cef4214d63a56 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 23 Aug 2017 17:11:50 -0600 Subject: Added LVP to Baton UI. Was super easy. --- spaghetti-monster/baton.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 194dbec..559eb48 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -171,7 +171,19 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg) { } void low_voltage() { - // FIXME: do something + // "step down" from strobe to level 2 + if (current_state == party_strobe_state) { + set_state(steady_state, 1); + } + // in normal mode, step down by one level or turn off + else if (current_state == steady_state) { + if (actual_level > 0) { + set_mode(actual_level - 1); + } + else { + set_state(off_state, 0); + } + } } void setup() { -- cgit v1.2.3 From e765199a20e14ca6226a33062ea14182df896dfc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 23 Aug 2017 17:14:57 -0600 Subject: Made Baton a little easier to read: Renamed set_mode() to set_level(). Replaced sizeof() thingy with a MAX_LEVEL define. --- spaghetti-monster/baton.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 559eb48..5ab35be 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -27,6 +27,7 @@ // moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, }; uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, }; +#define MAX_LEVEL (sizeof(pwm1_modes)-1) // FSM states uint8_t off_state(EventPtr event, uint16_t arg); @@ -37,7 +38,7 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg); uint8_t memorized_level = 1; uint8_t actual_level = 0; -void set_mode(uint8_t lvl) { +void set_level(uint8_t lvl) { actual_level = lvl; PWM1_LVL = pwm1_modes[lvl]; PWM2_LVL = pwm2_modes[lvl]; @@ -55,12 +56,12 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // hold (initially): go to lowest level, but allow abort for regular click else if (event == EV_click1_press) { - set_mode(0); + set_level(0); return 0; } // 1 click (before timeout): go to memorized level, but allow abort for double click else if (event == EV_click1_release) { - set_mode(memorized_level); + set_level(memorized_level); return 0; } // 1 click: regular mode @@ -70,7 +71,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // 2 clicks: highest mode else if (event == EV_2clicks) { - set_state(steady_state, sizeof(pwm1_modes)-1); + set_state(steady_state, MAX_LEVEL); return 0; } // 3 clicks: strobe mode @@ -90,10 +91,10 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // turn LED on when we first enter the mode if (event == EV_enter_state) { // remember this level, unless it's moon or turbo - if ((arg > 0) && (arg < sizeof(pwm1_modes)-1)) + if ((arg > 0) && (arg < MAX_LEVEL)) memorized_level = arg; // use the requested level even if not memorized - set_mode(arg); + set_level(arg); return 0; } // 1 click: off @@ -103,12 +104,12 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 2 clicks: go to/from highest level else if (event == EV_2clicks) { - if (actual_level < sizeof(pwm1_modes)-1) { + if (actual_level < MAX_LEVEL) { memorized_level = actual_level; // in case we're on moon - set_mode(sizeof(pwm1_modes)-1); + set_level(MAX_LEVEL); } else - set_mode(memorized_level); + set_level(memorized_level); return 0; } // 3 clicks: go to strobe modes @@ -119,8 +120,8 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // hold: change brightness else if (event == EV_click1_hold) { if ((arg % HOLD_TIMEOUT) == 0) { - memorized_level = (actual_level+1) % sizeof(pwm1_modes); - set_mode(memorized_level); + memorized_level = (actual_level+1) % (MAX_LEVEL+1); + set_level(memorized_level); } return 0; } @@ -178,7 +179,7 @@ void low_voltage() { // in normal mode, step down by one level or turn off else if (current_state == steady_state) { if (actual_level > 0) { - set_mode(actual_level - 1); + set_level(actual_level - 1); } else { set_state(off_state, 0); -- cgit v1.2.3 From 5631564b329d0445fb282e5e387217ba4e4ff191 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 23 Aug 2017 19:22:22 -0600 Subject: Added thermal regulation to SpaghettiMonster / Baton. Made some LVP values configurable. Removed high_temperature() / low_temperature() shortcuts for now. --- spaghetti-monster/baton.c | 36 ++++++++++- spaghetti-monster/fsm-adc.c | 136 +++++++++++++++++++++++++++++++++++++++-- spaghetti-monster/fsm-adc.h | 41 +++++++++++-- spaghetti-monster/fsm-states.c | 2 + 4 files changed, 206 insertions(+), 9 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 5ab35be..6b694e4 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -19,6 +19,8 @@ #define FSM_EMISAR_D4_LAYOUT #define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 #define USE_DEBUG_BLINK #define USE_DELAY_MS #define USE_DELAY_ZERO @@ -37,6 +39,9 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg); // brightness control uint8_t memorized_level = 1; uint8_t actual_level = 0; +#ifdef USE_THERMAL_REGULATION +uint8_t target_level = 0; +#endif void set_level(uint8_t lvl) { actual_level = lvl; @@ -94,6 +99,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if ((arg > 0) && (arg < MAX_LEVEL)) memorized_level = arg; // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif set_level(arg); return 0; } @@ -106,10 +114,17 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { else if (event == EV_2clicks) { if (actual_level < MAX_LEVEL) { memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif set_level(MAX_LEVEL); } - else + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif set_level(memorized_level); + } return 0; } // 3 clicks: go to strobe modes @@ -121,10 +136,29 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { else if (event == EV_click1_hold) { if ((arg % HOLD_TIMEOUT) == 0) { memorized_level = (actual_level+1) % (MAX_LEVEL+1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif set_level(memorized_level); } return 0; } + #ifdef USE_THERMAL_REGULATION + // overheating: drop by 1 level + else if (event == EV_temperature_high) { + if (actual_level > 1) { + set_level(actual_level - 1); + } + return 0; + } + // underheating: increase by 1 level if we're lower than the target + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + set_level(actual_level + 1); + } + return 0; + } + #endif return 1; } diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 11468b9..8af3487 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -35,9 +35,18 @@ inline void ADC_off() { ADCSRA &= ~(1<> 2; + // More precise method: use noise as extra precision + // (values are now basically fixed-point, signed 13.2) + temperature = total; + } + + // guess what the temperature will be in a few seconds + { + uint8_t i; + int16_t diff; + + // algorithm tweaking; not really intended to be modified + // how far ahead should we predict? + #define THERM_PREDICTION_STRENGTH 4 + // how proportional should the adjustments be? + #define THERM_DIFF_ATTENUATION 4 + // acceptable temperature window size in C + #define THERM_WINDOW_SIZE 8 + // highest temperature allowed + // (convert configured value to 13.2 fixed-point) + #define THERM_CEIL (therm_ceil<<2) + // bottom of target temperature window (13.2 fixed-point) + #define THERM_FLOOR (THERM_CEIL - (THERM_WINDOW_SIZE<<2)) + + // rotate measurements and add a new one + for (i=0; i THERM_FLOOR) { + underheat_lowpass = 0; // we're definitely not too cold + } else if (projected_temperature < THERM_CEIL) { + overheat_lowpass = 0; // we're definitely not too hot + } + + if (temperature_timer) { + temperature_timer --; + } else { // it has been long enough since the last warning + + // Too hot? + if (projected_temperature > THERM_CEIL) { + if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) { + overheat_lowpass ++; + } else { + // how far above the ceiling? + int16_t howmuch = (projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION; + if (howmuch < 1) howmuch = 1; + // try to send out a warning + emit(EV_temperature_high, howmuch); + // reset counters + temperature_timer = TEMPERATURE_TIMER_START; + overheat_lowpass = 0; + } + } + + // Too cold? + else if (projected_temperature < THERM_FLOOR) { + if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) { + underheat_lowpass ++; + } else { + // how far below the floor? + int16_t howmuch = (THERM_FLOOR - projected_temperature) >> THERM_DIFF_ATTENUATION; + if (howmuch < 1) howmuch = 1; + // try to send out a warning + emit(EV_temperature_low, howmuch); + // reset counters + temperature_timer = TEMPERATURE_TIMER_START; + underheat_lowpass = 0; + } + } + } + } + #endif // ifdef USE_THERMAL_REGULATION + // start another measurement for next time #ifdef USE_THERMAL_REGULATION diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index ac42333..43d52a6 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -20,21 +20,54 @@ #ifndef FSM_ADC_H #define FSM_ADC_H + #ifdef USE_LVP -// volts * 10 -#define VOLTAGE_LOW 30 +// default 5 seconds between low-voltage warning events +#ifndef VOLTAGE_WARNING_SECONDS +#define VOLTAGE_WARNING_SECONDS 5 +#endif +// low-battery threshold in volts * 10 +#ifndef VOLTAGE_LOW +#define VOLTAGE_LOW 29 +#endif // MCU sees voltage 0.X volts lower than actual, add X to readings +#ifndef VOLTAGE_FUDGE_FACTOR #define VOLTAGE_FUDGE_FACTOR 2 +#endif volatile uint8_t voltage; void low_voltage(); #endif + + #ifdef USE_THERMAL_REGULATION +// default 5 seconds between thermal regulation events +#ifndef THERMAL_WARNING_SECONDS +#define THERMAL_WARNING_SECONDS 5 +#endif +// try to keep temperature below 45 C +#ifndef DEFAULT_THERM_CEIL +#define DEFAULT_THERM_CEIL 45 +#endif +// don't allow user to set ceiling above 70 C +#ifndef MAX_THERM_CEIL +#define MAX_THERM_CEIL 70 +#endif +// Local per-MCU calibration value +#ifndef THERM_CAL_OFFSET +#define THERM_CAL_OFFSET 0 +#endif +// temperature now, in C (ish) volatile int16_t temperature; -void low_temperature(); -void high_temperature(); +// temperature in a few seconds, in C (ish) * 4 (13.2 fixed-point) +volatile int16_t projected_temperature; // Fight the future! +uint8_t therm_ceil = DEFAULT_THERM_CEIL; +//void low_temperature(); +//void high_temperature(); #endif + inline void ADC_on(); inline void ADC_off(); + #endif diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c index 652a9f2..ec24dc8 100644 --- a/spaghetti-monster/fsm-states.c +++ b/spaghetti-monster/fsm-states.c @@ -89,6 +89,7 @@ uint8_t default_state(EventPtr event, uint16_t arg) { } #endif + #if 0 #ifdef USE_THERMAL_REGULATION else if (event == EV_temperature_high) { high_temperature(); @@ -100,6 +101,7 @@ uint8_t default_state(EventPtr event, uint16_t arg) { return 0; } #endif + #endif // event not handled return 1; -- cgit v1.2.3 From badf37072988156a4cee753b922306195ee45916 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 02:09:33 -0600 Subject: Added a ramping UI example. Added ramping support in general. --- spaghetti-monster/fsm-adc.c | 2 + spaghetti-monster/fsm-events.h | 7 + spaghetti-monster/fsm-ramping.c | 62 +++++++++ spaghetti-monster/fsm-ramping.h | 85 +++++++++++- spaghetti-monster/ramping-ui.c | 252 ++++++++++++++++++++++++++++++++++ spaghetti-monster/spaghetti-monster.h | 2 + 6 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 spaghetti-monster/fsm-ramping.c create mode 100644 spaghetti-monster/ramping-ui.c (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 8af3487..b3ae4e9 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -231,6 +231,8 @@ ISR(ADC_vect) { if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) { underheat_lowpass ++; } else { + // FIXME: don't warn about underheating when voltage is low + // (LVP and underheat warnings fight each other) // how far below the floor? int16_t howmuch = (THERM_FLOOR - projected_temperature) >> THERM_DIFF_ATTENUATION; if (howmuch < 1) howmuch = 1; diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 434fa10..420baf1 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -116,6 +116,12 @@ Event EV_click2_press[] = { A_RELEASE, A_PRESS, 0 }; +Event EV_click2_hold[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_HOLD, + 0 }; Event EV_click2_release[] = { A_PRESS, A_RELEASE, @@ -166,6 +172,7 @@ EventPtr event_sequences[] = { EV_click1_hold, EV_click1_hold_release, EV_click2_press, + EV_click2_hold, EV_click2_release, EV_click2_complete, EV_click3_press, diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c new file mode 100644 index 0000000..ab7bd3c --- /dev/null +++ b/spaghetti-monster/fsm-ramping.c @@ -0,0 +1,62 @@ +/* + * fsm-ramping.c: Ramping functions for SpaghettiMonster. + * Handles 1- to 4-channel smooth ramping on a single LED. + * + * 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 . + */ + +#ifndef FSM_RAMPING_C +#define FSM_RAMPING_C + +#ifdef USE_RAMPING + +void set_level(uint8_t level) { + actual_level = level; + //TCCR0A = PHASE; + if (level == 0) { + #if PWM_CHANNELS >= 1 + PWM1_LVL = 0; + #endif + #if PWM_CHANNELS >= 2 + PWM2_LVL = 0; + #endif + #if PWM_CHANNELS >= 3 + PWM3_LVL = 0; + #endif + #if PWM_CHANNELS >= 4 + PWM4_LVL = 0; + #endif + } else { + level --; + #if PWM_CHANNELS >= 1 + PWM1_LVL = pgm_read_byte(pwm1_levels + level); + #endif + #if PWM_CHANNELS >= 2 + PWM2_LVL = pgm_read_byte(pwm2_levels + level); + #endif + #if PWM_CHANNELS >= 3 + PWM3_LVL = pgm_read_byte(pwm3_levels + level); + #endif + #if PWM_CHANNELS >= 4 + PWM4_LVL = pgm_read_byte(pwm4_levels + level); + #endif + } +} + +// TODO: set_lvl_smooth? + +#endif // ifdef USE_RAMPING +#endif diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index fd4d40b..a25ff8b 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -18,9 +18,82 @@ * along with this program. If not, see . */ -// TODO: ramp tables -// TODO: RAMP_SIZE / MAX_LVL -// TODO: actual_lvl -// TODO: target_lvl -// TODO: set_lvl -// TODO: set_lvl_smooth +#ifndef FSM_RAMPING_H +#define FSM_RAMPING_H + +#ifdef USE_RAMPING + +// actual_level: last ramp level set by set_level() +volatile uint8_t actual_level = 0; + +// ramp tables +#if PWM_CHANNELS == 1 + #if RAMP_LENGTH == 50 + // FIXME: These values are just an example + // ../../bin/level_calc.py 1 50 7135 3 0.25 980 + PROGMEM const uint8_t pwm1_levels[] = { 3,3,3,3,4,4,4,5,5,6,7,8,9,11,12,14,16,18,20,23,25,28,32,35,39,43,47,52,57,62,68,74,80,87,94,102,110,118,127,136,146,156,167,178,189,201,214,227,241,255 }; + #elif RAMP_LENGTH == 75 + // FIXME: These values are just an example + // ../../bin/level_calc.py 1 75 7135 3 0.25 980 + PROGMEM const uint8_t pwm1_levels[] = { 3,3,3,3,3,3,4,4,4,4,5,5,5,6,6,7,8,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,29,31,33,36,38,41,44,47,50,53,56,59,63,67,71,75,79,83,88,93,98,103,108,113,119,125,131,137,143,150,157,164,171,178,186,194,202,210,219,227,236,246,255 }; + #elif RAMP_LENGTH == 150 + // FIXME: These values are just an example + // ../../bin/level_calc.py 1 150 7135 3 0.25 980 + PROGMEM const uint8_t pwm1_levels[] = { 3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,5,5,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,10,10,11,11,12,12,13,13,14,15,15,16,17,17,18,19,19,20,21,22,23,24,24,25,26,27,28,29,31,32,33,34,35,36,38,39,40,42,43,44,46,47,49,50,52,53,55,57,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,89,91,93,96,98,101,103,106,109,111,114,117,120,123,125,128,131,134,138,141,144,147,151,154,157,161,164,168,171,175,179,183,186,190,194,198,202,206,210,215,219,223,228,232,236,241,246,250,255 }; + #endif +#elif PWM_CHANNELS == 2 + #if RAMP_LENGTH == 50 + // ../../bin/level_calc.py 2 50 7135 4 0.33 150 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 4,5,6,8,10,13,17,22,28,35,44,54,65,78,93,109,128,149,171,197,224,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,7,11,15,20,26,31,37,44,51,58,65,73,82,91,100,110,121,132,143,155,168,181,194,209,224,239,255 }; + #define MAX_1x7135 22 + #elif RAMP_LENGTH == 75 + // ../../bin/level_calc.py 2 75 7135 4 0.33 150 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 4,4,5,6,7,8,10,12,14,17,20,24,28,32,37,43,49,56,64,72,82,91,102,114,126,139,153,168,184,202,220,239,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,5,7,10,13,16,19,23,26,30,34,38,42,47,51,56,61,66,72,77,83,89,95,101,108,115,122,129,136,144,152,160,168,177,186,195,204,214,224,234,244,255 }; + #define MAX_1x7135 33 + #elif RAMP_LENGTH == 150 + // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,30,32,34,37,40,43,46,49,52,56,59,63,67,71,76,80,85,90,95,100,106,112,118,124,130,137,144,151,158,166,173,181,190,198,207,216,225,235,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; + #define MAX_1x7135 65 + #endif +#elif PWM_CHANNELS == 3 + #if RAMP_LENGTH == 50 + // FIXME: These values aren't tweaked or tested at all + // ../../bin/level_calc.py 3 50 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000 + PROGMEM const uint8_t pwm1_levels[] = { 4,5,6,8,11,15,20,26,34,43,54,67,82,99,118,140,165,192,221,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,17,25,33,42,52,62,73,85,97,111,125,140,157,174,192,210,230,251,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,34,54,76,98,122,146,172,198,226,255 }; + #define MAX_1x7135 20 + #define MAX_Nx7135 39 + #elif RAMP_LENGTH == 75 + // FIXME: These values aren't tweaked or tested at all + // ../../bin/level_calc.py 3 75 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000 + PROGMEM const uint8_t pwm1_levels[] = { 4,4,5,6,7,9,11,14,16,20,24,28,34,40,46,54,62,71,81,92,104,117,130,146,162,179,198,218,239,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,9,14,18,23,29,34,40,47,53,60,67,75,83,91,99,108,117,127,137,148,158,170,181,193,206,219,232,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,15,28,42,55,70,84,99,115,131,147,164,181,199,217,236,255 }; + #define MAX_1x7135 30 + #define MAX_Nx7135 59 + #elif RAMP_LENGTH == 150 + // FIXME: These values aren't tweaked or tested at all + // ../../bin/level_calc.py 3 150 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000 + PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,6,6,7,7,8,9,10,11,12,13,15,16,18,20,22,24,26,28,31,33,36,39,42,46,49,53,57,61,65,70,75,80,85,90,96,102,108,115,121,128,136,143,151,159,167,176,185,194,204,214,224,235,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,6,8,10,13,15,17,20,22,25,28,30,33,36,39,42,45,48,51,55,58,62,65,69,73,76,80,84,88,92,97,101,105,110,115,119,124,129,134,139,144,149,155,160,166,171,177,183,189,195,201,207,214,220,227,234,241,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,10,17,23,30,36,43,50,57,64,71,78,85,93,100,108,115,123,131,139,148,156,164,173,182,190,199,208,217,227,236,245,255 }; + #define MAX_1x7135 59 + #define MAX_Nx7135 117 + #endif +#elif PWM_CHANNELS == 4 + 4-channel PWM not really supported yet, sorry. +#endif + +// RAMP_SIZE / MAX_LVL +#define RAMP_SIZE sizeof(pwm1_levels) +#define MAX_LEVEL RAMP_SIZE + +void set_level(uint8_t level); +//void set_level_smooth(uint8_t level); + +#endif // ifdef USE_RAMPING +#endif diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c new file mode 100644 index 0000000..b51d2f4 --- /dev/null +++ b/spaghetti-monster/ramping-ui.c @@ -0,0 +1,252 @@ +/* + * Ramping-UI: Ramping 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_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 32 +#define USE_DEBUG_BLINK +#define USE_DELAY_MS +#define USE_DELAY_ZERO +#define USE_RAMPING +#define RAMP_LENGTH 150 +#include "spaghetti-monster.h" + +// FSM states +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); + +// brightness control +uint8_t memorized_level = 1; +#ifdef USE_THERMAL_REGULATION +uint8_t target_level = 0; +#endif + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + //empty_event_sequence(); // just in case (but shouldn't be needed) + standby_mode(); + return 0; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(1); + return 0; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return 0; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return 0; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, MAX_LEVEL); + return 0; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(party_strobe_state, 255); + return 0; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at moon level + if (arg >= HOLD_TIMEOUT) + set_state(steady_state, 1); + return 0; + } + // hold, release quickly: go to lowest level + else if (event == EV_click1_hold_release) { + set_state(steady_state, 1); + return 0; + } + // click-release-hold: go to highest level (for ramping down) + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return 0; + } + return 1; +} + +uint8_t steady_state(EventPtr event, uint16_t arg) { + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > 1) && (arg < MAX_LEVEL)) + memorized_level = arg; + // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return 0; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return 0; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + set_level(MAX_LEVEL); + } + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return 0; + } + // 3 clicks: go to strobe modes + else if (event == EV_3clicks) { + set_state(party_strobe_state, 0xff); + return 0; + } + // hold: change brightness (brighter) + else if (event == EV_click1_hold) { + // FIXME: make it ramp down instead, if already at max + if (actual_level < MAX_LEVEL) + memorized_level = (actual_level+1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // FIXME: only blink once + if ((memorized_level == MAX_1x7135) || (memorized_level == MAX_LEVEL)) { + set_level(0); + delay_ms(7); + } + set_level(memorized_level); + return 0; + } + // click-release-hold: change brightness (dimmer) + else if (event == EV_click2_hold) { + // FIXME: make it ramp up instead, if already at min + if (actual_level > 1) + memorized_level = (actual_level-1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // FIXME: only blink once + if ((memorized_level == MAX_1x7135) || (memorized_level == 1)) { + set_level(0); + delay_ms(7); + } + set_level(memorized_level); + return 0; + } + #ifdef USE_THERMAL_REGULATION + // FIXME: make thermal regulation work with ramping + // overheating: drop by 1 level + else if (event == EV_temperature_high) { + if (actual_level > 1) { + set_level(actual_level - 1); + } + return 0; + } + // underheating: increase by 1 level if we're lower than the target + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + set_level(actual_level + 1); + } + return 0; + } + #endif + return 1; +} + +uint8_t party_strobe_state(EventPtr event, uint16_t arg) { + static volatile uint8_t frames = 0; + static volatile uint8_t between = 2; + if (event == EV_enter_state) { + if (arg < 64) between = arg; + frames = 0; + return 0; + } + // tick: strobe the emitter + else if (event == EV_tick) { + if (frames == 0) { + PWM1_LVL = 0; + PWM2_LVL = 255; + if (between < 3) delay_zero(); + else delay_ms(1); + PWM2_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, memorized_level); + 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() { + // "step down" from strobe to something low + if (current_state == party_strobe_state) { + set_state(steady_state, RAMP_SIZE/6); + } + // in normal mode, step down by half or turn off + else if (current_state == steady_state) { + if (actual_level > 1) { + set_level(actual_level >> 1); + } + else { + set_state(off_state, 0); + } + } +} + +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 4eeb7de..2ba3208 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -33,6 +33,7 @@ #include "fsm-wdt.h" #include "fsm-pcint.h" #include "fsm-standby.h" +#include "fsm-ramping.h" #include "fsm-main.h" #ifdef USE_DEBUG_BLINK @@ -64,4 +65,5 @@ void setup(); #include "fsm-wdt.c" #include "fsm-pcint.c" #include "fsm-standby.c" +#include "fsm-ramping.c" #include "fsm-main.c" -- cgit v1.2.3 From 939a13fe9949c576e21914939e1d847641f215c9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 02:25:58 -0600 Subject: Made ramping UI able to toggle between smooth and discrete ramping with 4 clicks. --- spaghetti-monster/fsm-events.h | 13 +++++++++++++ spaghetti-monster/ramping-ui.c | 32 +++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 420baf1..246a4ee 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -161,6 +161,18 @@ Event EV_click3_complete[] = { A_RELEASE, A_RELEASE_TIMEOUT, 0 }; +#define EV_4clicks EV_click4_complete +Event EV_click4_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; // ... and so on // A list of button event types for easy iteration @@ -178,6 +190,7 @@ EventPtr event_sequences[] = { EV_click3_press, EV_click3_release, EV_click3_complete, + EV_click4_complete, // ... }; diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index b51d2f4..bd5a016 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -19,7 +19,7 @@ #define FSM_EMISAR_D4_LAYOUT #define USE_LVP -#define USE_THERMAL_REGULATION +//#define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 32 #define USE_DEBUG_BLINK #define USE_DELAY_MS @@ -35,7 +35,11 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg); // brightness control uint8_t memorized_level = 1; +// smooth vs discrete ramping +uint8_t ramp_step_size = 1; + #ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down uint8_t target_level = 0; #endif @@ -134,11 +138,24 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_state(party_strobe_state, 0xff); return 0; } + // 4 clicks: toggle smooth vs discrete ramping + else if (event == EV_4clicks) { + if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6; + else ramp_step_size = 1; + set_level(0); + delay_ms(20); + set_level(memorized_level); + return 0; + } // hold: change brightness (brighter) else if (event == EV_click1_hold) { + if (arg % ramp_step_size != 0) { + return 0; + } // FIXME: make it ramp down instead, if already at max - if (actual_level < MAX_LEVEL) - memorized_level = (actual_level+1); + if (actual_level + ramp_step_size < MAX_LEVEL) + memorized_level = actual_level + ramp_step_size; + else memorized_level = MAX_LEVEL; #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -152,9 +169,14 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // click-release-hold: change brightness (dimmer) else if (event == EV_click2_hold) { + if (arg % ramp_step_size != 0) { + return 0; + } // FIXME: make it ramp up instead, if already at min - if (actual_level > 1) - memorized_level = (actual_level-1); + if (actual_level > ramp_step_size) + memorized_level = (actual_level-ramp_step_size); + else + memorized_level = 1; #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif -- cgit v1.2.3 From 5f20a52bfa35903ece415486423ccc020200ac8c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 02:55:14 -0600 Subject: Set default brightness to max 7135 level. Made thermal regulation adjust proportional to how far above/below the target range the light is. Replaced turn-on blinks with single shorter/dimmer blip. --- spaghetti-monster/ramping-ui.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index bd5a016..c85804d 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -19,7 +19,7 @@ #define FSM_EMISAR_D4_LAYOUT #define USE_LVP -//#define USE_THERMAL_REGULATION +#define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 32 #define USE_DEBUG_BLINK #define USE_DELAY_MS @@ -34,7 +34,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t party_strobe_state(EventPtr event, uint16_t arg); // brightness control -uint8_t memorized_level = 1; +uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping uint8_t ramp_step_size = 1; @@ -189,18 +189,23 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { return 0; } #ifdef USE_THERMAL_REGULATION - // FIXME: make thermal regulation work with ramping - // overheating: drop by 1 level + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling else if (event == EV_temperature_high) { - if (actual_level > 1) { - set_level(actual_level - 1); + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); } return 0; } - // underheating: increase by 1 level if we're lower than the target + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) else if (event == EV_temperature_low) { if (actual_level < target_level) { - set_level(actual_level + 1); + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); } return 0; } @@ -268,7 +273,9 @@ void low_voltage() { } void setup() { - debug_blink(2); + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); push_state(off_state, 0); } -- cgit v1.2.3 From 2249f9913058c534fba51fbc6285e49c67bce726 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 03:03:55 -0600 Subject: Don't send underheat warnings when LVP is active. The signals conflict. Also, avoid immediate thermal step-down after battery change. (init array with correctly-scaled values) --- spaghetti-monster/fsm-adc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index b3ae4e9..9a6e2e7 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -148,7 +148,7 @@ ISR(ADC_vect) { for(uint8_t i=0; i> THERM_DIFF_ATTENUATION; if (howmuch < 1) howmuch = 1; - // try to send out a warning - emit(EV_temperature_low, howmuch); + // try to send out a warning (unless voltage is low) + // (LVP and underheat warnings fight each other) + if (voltage > VOLTAGE_LOW) + emit(EV_temperature_low, howmuch); // reset counters temperature_timer = TEMPERATURE_TIMER_START; underheat_lowpass = 0; -- cgit v1.2.3 From aa7e15ca8e13867b4edcbb1cbde2bca496fc764f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 03:20:06 -0600 Subject: Fixed repeating blinks at ends of ramp -- only blinks once now. --- spaghetti-monster/ramping-ui.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index c85804d..6eeb3b0 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -149,6 +149,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // hold: change brightness (brighter) else if (event == EV_click1_hold) { + // ramp slower in discrete mode if (arg % ramp_step_size != 0) { return 0; } @@ -159,8 +160,10 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif - // FIXME: only blink once - if ((memorized_level == MAX_1x7135) || (memorized_level == MAX_LEVEL)) { + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == MAX_LEVEL))) { set_level(0); delay_ms(7); } @@ -169,6 +172,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // click-release-hold: change brightness (dimmer) else if (event == EV_click2_hold) { + // ramp slower in discrete mode if (arg % ramp_step_size != 0) { return 0; } @@ -180,8 +184,10 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif - // FIXME: only blink once - if ((memorized_level == MAX_1x7135) || (memorized_level == 1)) { + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == 1))) { set_level(0); delay_ms(7); } -- cgit v1.2.3 From 32eaeddee34c76dda5456ed960be6278ed68e48d Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 16:21:45 -0600 Subject: Added loop() to API, executes constantly. Added nice_delay_ms() to process events while waiting, and abort on state change. Converted ramping-ui strobe to smoothly variable with party and tactical modes. --- spaghetti-monster/baton.c | 2 + spaghetti-monster/fsm-events.c | 23 +++++++++++ spaghetti-monster/fsm-events.h | 3 ++ spaghetti-monster/fsm-main.c | 7 ++-- spaghetti-monster/momentary.c | 3 ++ spaghetti-monster/ramping-ui.c | 77 +++++++++++++++++++++-------------- spaghetti-monster/spaghetti-monster.h | 4 +- 7 files changed, 84 insertions(+), 35 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 6b694e4..2489f4e 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -226,3 +226,5 @@ void setup() { push_state(off_state, 0); } + +void loop() { } diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 6dda236..4831df6 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -99,6 +99,29 @@ void delete_first_emission() { emissions[i].arg = 0; } +void process_emissions() { + while (emissions[0].event != NULL) { + emit_now(emissions[0].event, emissions[0].arg); + delete_first_emission(); + } +} + +// like delay_ms, except it aborts on state change +// return value: +// 0: state changed +// 1: normal completion +uint8_t nice_delay_ms(uint16_t ms) { + StatePtr old_state = current_state; + while(ms-- > 0) { + _delay_loop_2(BOGOMIPS*98/100); + process_emissions(); + if (old_state != current_state) { + return 0; // state changed; abort + } + } + return 1; +} + // Call stacked callbacks for the given event until one handles it. uint8_t emit_now(EventPtr event, uint16_t arg) { for(int8_t i=state_stack_len-1; i>=0; i--) { diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 246a4ee..a14d7aa 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -209,9 +209,12 @@ volatile Emission emissions[EMISSION_QUEUE_LEN]; void append_emission(EventPtr event, uint16_t arg); void delete_first_emission(); +void process_emissions(); //#define emit_now emit uint8_t emit_now(EventPtr event, uint16_t arg); void emit(EventPtr event, uint16_t arg); void emit_current_event(uint16_t arg); +uint8_t nice_delay_ms(uint16_t ms); + #endif diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index 2faaeda..1a0c425 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -97,10 +97,9 @@ int main() { // 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(); - } + process_emissions(); + + loop(); } } diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c index 23cc359..fc26db3 100644 --- a/spaghetti-monster/momentary.c +++ b/spaghetti-monster/momentary.c @@ -75,3 +75,6 @@ void setup() { debug_blink(2); push_state(momentary_state, 0); } + +void loop() { } + diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 6eeb3b0..256c4cf 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -31,7 +31,7 @@ // FSM states 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 strobe_state(EventPtr event, uint16_t arg); // brightness control uint8_t memorized_level = MAX_1x7135; @@ -43,6 +43,11 @@ uint8_t ramp_step_size = 1; uint8_t target_level = 0; #endif +// strobe timing +volatile uint8_t strobe_delay = 67; +volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe + + uint8_t off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { @@ -74,7 +79,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // 3 clicks: strobe mode else if (event == EV_3clicks) { - set_state(party_strobe_state, 255); + set_state(strobe_state, 0); return 0; } // hold: go to lowest level @@ -90,7 +95,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(steady_state, 1); return 0; } - // click-release-hold: go to highest level (for ramping down) + // click, hold: go to highest level (for ramping down) else if (event == EV_click2_hold) { set_state(steady_state, MAX_LEVEL); return 0; @@ -98,6 +103,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return 1; } + uint8_t steady_state(EventPtr event, uint16_t arg) { // turn LED on when we first enter the mode if (event == EV_enter_state) { @@ -135,7 +141,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 3 clicks: go to strobe modes else if (event == EV_3clicks) { - set_state(party_strobe_state, 0xff); + set_state(strobe_state, 0); return 0; } // 4 clicks: toggle smooth vs discrete ramping @@ -170,7 +176,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return 0; } - // click-release-hold: change brightness (dimmer) + // click, hold: change brightness (dimmer) else if (event == EV_click2_hold) { // ramp slower in discrete mode if (arg % ramp_step_size != 0) { @@ -219,26 +225,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { return 1; } -uint8_t party_strobe_state(EventPtr event, uint16_t arg) { - static volatile uint8_t frames = 0; - static volatile uint8_t between = 2; + +uint8_t strobe_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { - if (arg < 64) between = arg; - frames = 0; - return 0; - } - // tick: strobe the emitter - else if (event == EV_tick) { - if (frames == 0) { - PWM1_LVL = 0; - PWM2_LVL = 255; - if (between < 3) delay_zero(); - else delay_ms(1); - PWM2_LVL = 0; - } - //frames = (frames + 1) % between; - frames++; - if (frames > between) frames = 0; return 0; } // 1 click: off @@ -246,25 +235,37 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return 0; } - // 2 clicks: go back to regular modes + // 2 clicks: toggle party strobe vs tactical strobe else if (event == EV_2clicks) { + strobe_type ^= 1; + return 0; + } + // 3 clicks: go back to regular modes + else if (event == EV_3clicks) { set_state(steady_state, memorized_level); return 0; } - // hold: change speed + // hold: change speed (go faster) else if (event == EV_click1_hold) { - if ((arg % HOLD_TIMEOUT) == 0) { - between = (between+1)%6; - frames = 0; + if ((arg & 1) == 0) { + if (strobe_delay > 8) strobe_delay --; + } + return 0; + } + // click, hold: change speed (go slower) + else if (event == EV_click2_hold) { + if ((arg & 1) == 0) { + if (strobe_delay < 255) strobe_delay ++; } return 0; } return 1; } + void low_voltage() { // "step down" from strobe to something low - if (current_state == party_strobe_state) { + if (current_state == strobe_state) { set_state(steady_state, RAMP_SIZE/6); } // in normal mode, step down by half or turn off @@ -278,6 +279,7 @@ void low_voltage() { } } + void setup() { set_level(RAMP_SIZE/8); delay_4ms(3); @@ -285,3 +287,18 @@ void setup() { push_state(off_state, 0); } + + +void loop() { + if (current_state == strobe_state) { + set_level(MAX_LEVEL); + if (strobe_type == 0) { // party strobe + if (strobe_delay < 30) delay_zero(); + else delay_ms(1); + } else { //tactical strobe + nice_delay_ms(strobe_delay >> 1); + } + set_level(0); + nice_delay_ms(strobe_delay); + } +} diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index 2ba3208..7d26390 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -54,9 +54,11 @@ void debug_blink(uint8_t num) { // TODO? new delay() functions which handle queue consumption? // TODO? new interruptible delay() functions? +// Define these in your SpaghettiMonster recipe // boot-time tasks -// Define this in your SpaghettiMonster recipe void setup(); +// single loop iteration, runs continuously +void loop(); // include executable functions too, for easier compiling #include "fsm-states.c" -- cgit v1.2.3 From 5184aad41fdb501f05ff7b0d7131011657ed8275 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 24 Aug 2017 19:22:10 -0600 Subject: Started on some documentation, spaghetti-monster.txt. Added #defines for State return values: EVENT_HANDLED, EVENT_NOT_HANDLED Improved handling of delay includes. Managed mischief. --- spaghetti-monster/fsm-events.h | 5 + spaghetti-monster/ramping-ui.c | 60 ++++---- spaghetti-monster/spaghetti-monster.h | 6 +- spaghetti-monster/spaghetti-monster.txt | 246 ++++++++++++++++++++++++++++++++ 4 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 spaghetti-monster/spaghetti-monster.txt (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index a14d7aa..a3ef130 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -30,6 +30,11 @@ typedef struct Emission { uint16_t arg; } Emission; +#define EVENT_HANDLED 0 +#define EVENT_NOT_HANDLED 1 +#define MISCHIEF_MANAGED EVENT_HANDLED +#define MISCHIEF_NOT_MANAGED EVENT_NOT_HANDLED + #define EV_MAX_LEN 16 uint8_t current_event[EV_MAX_LEN]; // at 0.016 ms per tick, 255 ticks = 4.08 s diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 256c4cf..562cd1b 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -21,8 +21,8 @@ #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 32 -#define USE_DEBUG_BLINK #define USE_DELAY_MS +#define USE_DELAY_4MS #define USE_DELAY_ZERO #define USE_RAMPING #define RAMP_LENGTH 150 @@ -55,32 +55,32 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // sleep while off (lower power use) //empty_event_sequence(); // just in case (but shouldn't be needed) standby_mode(); - return 0; + return MISCHIEF_MANAGED; } // hold (initially): go to lowest level, but allow abort for regular click else if (event == EV_click1_press) { set_level(1); - return 0; + return MISCHIEF_MANAGED; } // 1 click (before timeout): go to memorized level, but allow abort for double click else if (event == EV_click1_release) { set_level(memorized_level); - return 0; + return MISCHIEF_MANAGED; } // 1 click: regular mode else if (event == EV_1click) { set_state(steady_state, memorized_level); - return 0; + return MISCHIEF_MANAGED; } // 2 clicks: highest mode else if (event == EV_2clicks) { set_state(steady_state, MAX_LEVEL); - return 0; + return MISCHIEF_MANAGED; } // 3 clicks: strobe mode else if (event == EV_3clicks) { set_state(strobe_state, 0); - return 0; + return MISCHIEF_MANAGED; } // hold: go to lowest level else if (event == EV_click1_hold) { @@ -88,19 +88,19 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // give the user time to release at moon level if (arg >= HOLD_TIMEOUT) set_state(steady_state, 1); - return 0; + return MISCHIEF_MANAGED; } // hold, release quickly: go to lowest level else if (event == EV_click1_hold_release) { set_state(steady_state, 1); - return 0; + return MISCHIEF_MANAGED; } // click, hold: go to highest level (for ramping down) else if (event == EV_click2_hold) { set_state(steady_state, MAX_LEVEL); - return 0; + return MISCHIEF_MANAGED; } - return 1; + return EVENT_NOT_HANDLED; } @@ -115,12 +115,12 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { target_level = arg; #endif set_level(arg); - return 0; + return MISCHIEF_MANAGED; } // 1 click: off else if (event == EV_1click) { set_state(off_state, 0); - return 0; + return MISCHIEF_MANAGED; } // 2 clicks: go to/from highest level else if (event == EV_2clicks) { @@ -137,12 +137,12 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #endif set_level(memorized_level); } - return 0; + return MISCHIEF_MANAGED; } // 3 clicks: go to strobe modes else if (event == EV_3clicks) { set_state(strobe_state, 0); - return 0; + return MISCHIEF_MANAGED; } // 4 clicks: toggle smooth vs discrete ramping else if (event == EV_4clicks) { @@ -151,13 +151,13 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(0); delay_ms(20); set_level(memorized_level); - return 0; + return MISCHIEF_MANAGED; } // hold: change brightness (brighter) else if (event == EV_click1_hold) { // ramp slower in discrete mode if (arg % ramp_step_size != 0) { - return 0; + return MISCHIEF_MANAGED; } // FIXME: make it ramp down instead, if already at max if (actual_level + ramp_step_size < MAX_LEVEL) @@ -174,13 +174,13 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { delay_ms(7); } set_level(memorized_level); - return 0; + return MISCHIEF_MANAGED; } // click, hold: change brightness (dimmer) else if (event == EV_click2_hold) { // ramp slower in discrete mode if (arg % ramp_step_size != 0) { - return 0; + return MISCHIEF_MANAGED; } // FIXME: make it ramp up instead, if already at min if (actual_level > ramp_step_size) @@ -198,7 +198,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { delay_ms(7); } set_level(memorized_level); - return 0; + return MISCHIEF_MANAGED; } #ifdef USE_THERMAL_REGULATION // TODO: test this on a real light @@ -209,7 +209,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; set_level(stepdown); } - return 0; + return MISCHIEF_MANAGED; } // underheating: increase slowly if we're lower than the target // (proportional to how low we are) @@ -219,47 +219,47 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (stepup > target_level) stepup = target_level; set_level(stepup); } - return 0; + return MISCHIEF_MANAGED; } #endif - return 1; + return EVENT_NOT_HANDLED; } uint8_t strobe_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { - return 0; + return MISCHIEF_MANAGED; } // 1 click: off else if (event == EV_1click) { set_state(off_state, 0); - return 0; + return MISCHIEF_MANAGED; } // 2 clicks: toggle party strobe vs tactical strobe else if (event == EV_2clicks) { strobe_type ^= 1; - return 0; + return MISCHIEF_MANAGED; } // 3 clicks: go back to regular modes else if (event == EV_3clicks) { set_state(steady_state, memorized_level); - return 0; + return MISCHIEF_MANAGED; } // hold: change speed (go faster) else if (event == EV_click1_hold) { if ((arg & 1) == 0) { if (strobe_delay > 8) strobe_delay --; } - return 0; + return MISCHIEF_MANAGED; } // click, hold: change speed (go slower) else if (event == EV_click2_hold) { if ((arg & 1) == 0) { if (strobe_delay < 255) strobe_delay ++; } - return 0; + return MISCHIEF_MANAGED; } - return 1; + return EVENT_NOT_HANDLED; } diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index 7d26390..56d03a3 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -36,10 +36,12 @@ #include "fsm-ramping.h" #include "fsm-main.h" -#ifdef USE_DEBUG_BLINK +#if defined(USE_DELAY_MS) || defined(USE_DELAY_4MS) || defined(USE_DELAY_ZERO) || defined(USE_DEBUG_BLINK) #define OWN_DELAY -#define USE_DELAY_4MS #include "tk-delay.h" +#endif + +#ifdef USE_DEBUG_BLINK #define DEBUG_FLASH PWM1_LVL = 64; delay_4ms(2); PWM1_LVL = 0; void debug_blink(uint8_t num) { for(; num>0; num--) { diff --git a/spaghetti-monster/spaghetti-monster.txt b/spaghetti-monster/spaghetti-monster.txt new file mode 100644 index 0000000..8393652 --- /dev/null +++ b/spaghetti-monster/spaghetti-monster.txt @@ -0,0 +1,246 @@ +Spaghetti Monster: A UI toolkit library for flashlights +------------------------------------------------------- + +This toolkit takes care of most of the obnoxious parts of dealing with +tiny embedded chips and flashlight hardware, leaving you to focus on the +interface and user-visible features. + +For a quick start, look at the example UIs provided to see how things +are done. They are probably the most useful reference. However, other +details can be found here or in the FSM source code. + + +Why is it called Spaghetti Monster? + + This toolkit is a finite state machine, or FSM. Another thing FSM + stands for is Flying Spaghetti Monster. Source code tends to weave + into intricate knots like spaghetti, called spaghetti code, + particularly when the code isn't using appropriate abstractions for + the task it implements. + + Prior e-switch light code had a tendency to get pretty spaghetti-like, + and it made the code difficult to write, understand, and modify. So I + started from scratch and logically separated the hardware details from + the UI. This effectively put the spaghetti monster in a box, put it + on a leash, to make it behave and stay out of the way while we focus + on the user interface. + + Also, it's just kind of a fun name. :) + + +General concept: + + Spaghetti Monster (FSM) implements a stack-based finite state machine + with an event-handling system. + + Each FSM program should have a setup() function, a loop() function, + and at least one State: + + - The setup() function runs once each time power is connected. + + - The loop() function is called repeatedly whenever the system is + otherwise idle. Put your long-running tasks here, preferably with + consideration taken to allow for cooperative multitasking. + + - The States on the stack will be called whenever an event happens. + States are called in top-to-bottom order until a state returns an + "EVENT_HANDLED" signal. Only do quick tasks here. + + +Finite State Machine: + + Each "State" is simply a callback function which handles events. It + should return EVENT_HANDLED for each event type it does something + with, or EVENT_NOT_HANDLED otherwise. + + Transitions between states typically involve mapping an Event to a new + State, such as this: + + // 3 clicks: go to strobe modes + else if (event == EV_3clicks) { + set_state(strobe_state, 0); + return EVENT_HANDLED; + } + + It is strongly recommended that your State functions never do anything + which takes more than a few milliseconds... and certainly not longer + than 16ms. If you do this, the pending events may pile up to the + point where new events get thrown away. So, do only quick tasks in + the event handler, and do your longer-running tasks in the loop() + function instead. Preferably with precautions taken to allow for + cooperative multitasking. + + Several state management functions are provided: + + - set_state(new_state, arg): Replace the current state on the stack. + Send 'arg' to the new state for its init event. + + - push_state(new_state, arg): Add a new state to the stack, leaving + the current state below it. Send 'arg' to the new state for its + init event. + + - pop_state(): Get rid of (and return) the top-most state. Re-enter + the state below. + +Event types: + + Event types are defined in fsm-events.h. You may want to adjust these + to fit your program, but the defaults are: + + State transitions: + + - EV_enter_state: Sent to each new State once when it goes onto + the stack. The 'arg' is whatever you define it to be. + + - EV_leave_state: Sent to a state immediately before it is removed + from the stack. + + Time passing: + + - EV_tick: This happens once per clock tick, which is 16ms or + 62.5Hz by default. The 'arg' is the number of ticks since + entering the state. When 'arg' exceeds 65535, it wraps around + to 32768. + + LVP and thermal regulation: + + - EV_voltage_low: Sent whenever the input power drops below the + VOLTAGE_LOW threshold. Minimum of VOLTAGE_WARNING_SECONDS + between events. + + - EV_temperature_high: Sent whenever the MCU's projected temperature + is higher than therm_ceil. Minimum of THERMAL_WARNING_SECONDS + between events. The 'arg' indicates how far the temperature + exceeds the limit. + + - EV_temperature_low: Sent whenever the MCU's projected temperature + is lower than (therm_ceil - THERMAL_WINDOW_SIZE). Minimum of + THERMAL_WARNING_SECONDS between events. The 'arg' indicates how + far the temperature exceeds the limit. + + Button presses: + + - EV_1click: The user clicked the e-switch, released it, and + enough time passed that no more clicks were detected. + + - EV_2clicks: The user clicked and released the e-switch twice, then + enough time passed that no more clicks were detected. + + - EV_3clicks: The user clicked and released the e-switch three + times, then enough time passed that no more clicks were detected. + + - EV_4clicks: The user clicked and released the e-switch four times, + then enough time passed that no more clicks were detected. + + - EV_click1_hold: The user pressed the button and is still holding + it. The 'arg' indicates how many clock ticks since the "hold" + state started. + + - EV_click1_hold_release: The user pressed the button, held it for a + while, and then released it. No timeout is attempted after this. + + - EV_click2_hold: The user clicked once, then pressed the button and + is still holding it. The 'arg' indicates how many clock ticks + since the "hold" state started. + + - EV_click2_hold_release: The user clicked once, then pressed the + button, held it for a while, and released it. No timeout is + attempted after this. + + - EV_click1_press: The user pressed the button and it's still down. + No time has yet passed. + + - EV_click1_release: The user quickly pressed and released the + button. The click timeout has not yet expired, so they might + still click again. + + - EV_click2_press: The user pressed the button, released it, pressed + again, and it's still down. No time has yet passed since then. + + - EV_click2_release: The quickly pressed and released the button + twice. The click timeout has not yet expired, so they might still + click again. + + In theory, you could also define your own arbitrary event types, and + emit() them as necessary, and handle them in State functions the same + as any other event. + + +Cooperative multitasking: + + Since we don't have true preemptive multitasking, the best we can do + is cooperative multitasking. In practice, this means: + + - Declare global variables as volatile if they can be changed by an + event handler. This keeps the compiler from caching the value and + causing incorrect behavior. + + - Don't put long-running tasks into State functions. Each State + will get called at least once every 16ms for a clock tick, so they + should not run for longer than 16ms. + + - Put long-running tasks into loop() instead. + + - For long delay() calls, use nice_delay_ms(). This allows the MCU + to process events while we wait. It also automatically aborts if + it detects a state change, and returns a different value. + + In many cases, it shouldn't be necessary to do anything more than + this, but sometimes it will also be a good idea to check the + return value and abort the current task: + + if (! nice_delay_ms(mydelay)) break; + + - In general, try to do small amounts of work and then return + control to other parts of the program. Keep doing small amounts + and yielding until a task is done, instead of trying to do it all + at once. + + +Useful #defines: + + A variety of things can be #defined before including + spaghetti-monster.h in your program. This allows you to tweak the + behavior and set options to fit your needs: + + - FSM_something_LAYOUT: Select a driver type from tk-attiny.h. This + controls how many power channels there are, which pins they're on, + and what other driver features are available. + + - USE_LVP: Enable low-voltage protection. + + - VOLTAGE_LOW: What voltage should LVP trigger at? Defaults to 29 (2.9V). + + - VOLTAGE_FUDGE_FACTOR: Add this much to the voltage measurements, + to compensate for voltage drop across the reverse-polarity + diode. + + - VOLTAGE_WARNING_SECONDS: How long to wait between LVP events. + + - USE_THERMAL_REGULATION: Enable thermal regulation + + - DEFAULT_THERM_CEIL: Set the temperature limit to use by default + when the user hasn't configured anything. + + - THERMAL_WARNING_SECONDS: How long to wait between temperature + events. + + - USE_RAMPING: Enable smooth ramping helpers. + + - RAMP_LENGTH: Pick a pre-defined ramp by length. Defined sizes + are 50, 75, and 150 levels. + + - USE_DELAY_4MS, USE_DELAY_MS, USE_DELAY_ZERO: Enable the delay_4ms, + delay_ms(), and delay_zero() functions. Useful for timing-related + activities. + + - HOLD_TIMEOUT: How many clock ticks before a "press" event becomes + a "hold" event? + + - RELEASE_TIMEOUT: How many clock ticks before a "release" event + becomes a "click" event? Basically, the maximum time between + clicks in a double-click or triple-click. + + - ... and many others. Will try to document them over time, but + they can be found by searching for pretty much anything in + all-caps in the fsm-*.[ch] files. -- cgit v1.2.3 From 39b30b41f92978a3e05a8de0a416279fb35b35b1 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 25 Aug 2017 02:14:31 -0600 Subject: Added battcheck mode to ramping-ui. It's bigger than I had hoped. :( Added fsm-misc.*, which currently only has interruptible blink functions in it. (for blinking out numbers and such) --- spaghetti-monster/fsm-adc.c | 24 +++++++++ spaghetti-monster/fsm-adc.h | 6 +++ spaghetti-monster/fsm-events.c | 12 +++++ spaghetti-monster/fsm-events.h | 2 + spaghetti-monster/fsm-misc.c | 91 +++++++++++++++++++++++++++++++++++ spaghetti-monster/fsm-misc.h | 35 ++++++++++++++ spaghetti-monster/ramping-ui.c | 40 +++++++++++++-- spaghetti-monster/spaghetti-monster.h | 2 + 8 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 spaghetti-monster/fsm-misc.c create mode 100644 spaghetti-monster/fsm-misc.h (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 9a6e2e7..70e3bb3 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -263,4 +263,28 @@ ISR(ADC_vect) { #endif } +#ifdef USE_BATTCHECK +#ifdef BATTCHECK_4bars +PROGMEM const uint8_t voltage_blinks[] = { + 30, 35, 38, 40, 42, 99, +}; +#endif +#ifdef BATTCHECK_8bars +PROGMEM const uint8_t voltage_blinks[] = { + 30, 33, 35, 37, 38, 39 40, 41, 42, 99, +}; +#endif +void battcheck() { + #ifdef BATTCHECK_VpT + blink_num(voltage); + #else + uint8_t i; + for(i=0; + voltage >= pgm_read_byte(voltage_blinks + i); + i++) {} + blink_num(i); + #endif +} +#endif + #endif diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index 43d52a6..f1b4477 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -36,6 +36,12 @@ #endif volatile uint8_t voltage; void low_voltage(); +#ifdef USE_BATTCHECK +void battcheck(); +#ifdef BATTCHECK_VpT +#define USE_BLINK_NUM +#endif +#endif #endif diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 4831df6..29ef415 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -122,6 +122,18 @@ uint8_t nice_delay_ms(uint16_t ms) { return 1; } +/* +uint8_t nice_delay_4ms(uint8_t ms) { + return nice_delay_ms((uint16_t)ms << 2); +} +*/ + +/* +uint8_t nice_delay_s() { + return nice_delay_4ms(250); +} +*/ + // Call stacked callbacks for the given event until one handles it. uint8_t emit_now(EventPtr event, uint16_t arg) { for(int8_t i=state_stack_len-1; i>=0; i--) { diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index a3ef130..141a0dc 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -221,5 +221,7 @@ void emit(EventPtr event, uint16_t arg); void emit_current_event(uint16_t arg); uint8_t nice_delay_ms(uint16_t ms); +//uint8_t nice_delay_4ms(uint8_t ms); +//uint8_t nice_delay_s(); #endif diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c new file mode 100644 index 0000000..46325ef --- /dev/null +++ b/spaghetti-monster/fsm-misc.c @@ -0,0 +1,91 @@ +/* + * fsm-misc.c: Miscellaneous function 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 . + */ + +#ifndef FSM_MISC_C +#define FSM_MISC_C + +#ifdef USE_BLINK_NUM +uint8_t blink_digit(uint8_t num) { + //StatePtr old_state = current_state; + + // "zero" digit gets a single short blink + uint8_t ontime = 200; + if (!num) { ontime = 8; num ++; } + + for (; num>0; num--) { + set_level(BLINK_BRIGHTNESS); + if (! nice_delay_ms(ontime)) { set_level(0); return 0; } + set_level(0); + //if (current_state != old_state) return 0; + if (! nice_delay_ms(400)) return 0; + //if (current_state != old_state) return 0; + } + return nice_delay_ms(600); +} + +uint8_t blink_num(uint8_t num) { + //StatePtr old_state = current_state; + #if 0 + uint8_t hundreds = num / 100; + num = num % 100; + uint8_t tens = num / 10; + num = num % 10; + #else // 8 bytes smaller + uint8_t hundreds = 0; + uint8_t tens = 0; + for(; num >= 100; hundreds ++, num -= 100); + for(; num >= 10; tens ++, num -= 10); + #endif + + #if 0 + // wait a moment in the dark before starting + set_level(0); + if (! nice_delay_ms(200)) return 0; + #endif + + #if 0 + if (hundreds) { + if (! blink_digit(hundreds)) return 0; + if (! blink_digit(tens)) return 0; + } + else if (tens) { + if (! blink_digit(tens)) return 0; + } + if (! blink_digit(num)) return 0; + return nice_delay_ms(1000); + #else // same size :( + if (hundreds) if (! blink_digit(hundreds)) return 0; + if (hundreds || tens) if (! blink_digit(tens)) return 0; + if (! blink_digit(num)) return 0; + return nice_delay_ms(1000); + #endif + + /* + uint8_t volts, tenths; + volts = voltage / 10; + tenths = voltage % 10; + if (! blink(volts)) return; + if (! nice_delay_ms(200)) return; + if (! blink(tenths)) return; + nice_delay_ms(200); + */ +} +#endif + +#endif diff --git a/spaghetti-monster/fsm-misc.h b/spaghetti-monster/fsm-misc.h new file mode 100644 index 0000000..7533849 --- /dev/null +++ b/spaghetti-monster/fsm-misc.h @@ -0,0 +1,35 @@ +/* + * fsm-misc.h: Miscellaneous function 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 . + */ + +#ifndef FSM_MISC_H +#define FSM_MISC_H + +#ifdef USE_BLINK_NUM +#define USE_BLINK +#ifndef BLINK_BRIGHTNESS +#define BLINK_BRIGHTNESS (MAX_LEVEL/6) +#endif +uint8_t blink_num(uint8_t num); +#endif + +#ifdef USE_BLINK +uint8_t blink(uint8_t num, uint8_t speed); +#endif + +#endif diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 562cd1b..0b5bbda 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -25,6 +25,8 @@ #define USE_DELAY_4MS #define USE_DELAY_ZERO #define USE_RAMPING +#define USE_BATTCHECK +#define BATTCHECK_VpT #define RAMP_LENGTH 150 #include "spaghetti-monster.h" @@ -32,6 +34,9 @@ uint8_t off_state(EventPtr event, uint16_t arg); uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t strobe_state(EventPtr event, uint16_t arg); +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +#endif // brightness control uint8_t memorized_level = MAX_1x7135; @@ -72,6 +77,11 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(steady_state, memorized_level); return MISCHIEF_MANAGED; } + // 2 clicks (initial press): off, to prep for later events + else if (event == EV_click2_press) { + set_level(0); + return MISCHIEF_MANAGED; + } // 2 clicks: highest mode else if (event == EV_2clicks) { set_state(steady_state, MAX_LEVEL); @@ -82,6 +92,13 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(strobe_state, 0); return MISCHIEF_MANAGED; } + #ifdef USE_BATTCHECK + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + #endif // hold: go to lowest level else if (event == EV_click1_hold) { // don't start ramping immediately; @@ -149,7 +166,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6; else ramp_step_size = 1; set_level(0); - delay_ms(20); + delay_4ms(20/4); set_level(memorized_level); return MISCHIEF_MANAGED; } @@ -171,7 +188,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { && ((memorized_level == MAX_1x7135) || (memorized_level == MAX_LEVEL))) { set_level(0); - delay_ms(7); + delay_4ms(8/4); } set_level(memorized_level); return MISCHIEF_MANAGED; @@ -195,7 +212,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { && ((memorized_level == MAX_1x7135) || (memorized_level == 1))) { set_level(0); - delay_ms(7); + delay_4ms(8/4); } set_level(memorized_level); return MISCHIEF_MANAGED; @@ -263,6 +280,18 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { } +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} +#endif + + void low_voltage() { // "step down" from strobe to something low if (current_state == strobe_state) { @@ -301,4 +330,9 @@ void loop() { set_level(0); nice_delay_ms(strobe_delay); } + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + battcheck(); + } + #endif } diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index 56d03a3..3727930 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -34,6 +34,7 @@ #include "fsm-pcint.h" #include "fsm-standby.h" #include "fsm-ramping.h" +#include "fsm-misc.h" #include "fsm-main.h" #if defined(USE_DELAY_MS) || defined(USE_DELAY_4MS) || defined(USE_DELAY_ZERO) || defined(USE_DEBUG_BLINK) @@ -70,4 +71,5 @@ void loop(); #include "fsm-pcint.c" #include "fsm-standby.c" #include "fsm-ramping.c" +#include "fsm-misc.c" #include "fsm-main.c" -- cgit v1.2.3 From 4052efbf6d7993c6b846105e870b1fcbcdb761e7 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 25 Aug 2017 02:32:43 -0600 Subject: Made 4bar and 8bar battcheck styles work. Added LVP handling for other modes, including battcheck. --- spaghetti-monster/fsm-adc.c | 5 +++-- spaghetti-monster/fsm-adc.h | 3 +++ spaghetti-monster/fsm-misc.c | 4 +++- spaghetti-monster/fsm-misc.h | 10 ++++++++-- spaghetti-monster/ramping-ui.c | 4 ++++ 5 files changed, 21 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 70e3bb3..622ef0f 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -271,7 +271,7 @@ PROGMEM const uint8_t voltage_blinks[] = { #endif #ifdef BATTCHECK_8bars PROGMEM const uint8_t voltage_blinks[] = { - 30, 33, 35, 37, 38, 39 40, 41, 42, 99, + 30, 33, 35, 37, 38, 39, 40, 41, 42, 99, }; #endif void battcheck() { @@ -282,7 +282,8 @@ void battcheck() { for(i=0; voltage >= pgm_read_byte(voltage_blinks + i); i++) {} - blink_num(i); + if (blink_digit(i)) + nice_delay_ms(1000); #endif } #endif diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index f1b4477..c12405c 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -41,6 +41,9 @@ void battcheck(); #ifdef BATTCHECK_VpT #define USE_BLINK_NUM #endif +#if defined(BATTCHECK_8bars) || defined(BATTCHECK_4bars) +#define USE_BLINK_DIGIT +#endif #endif #endif diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c index 46325ef..7322a59 100644 --- a/spaghetti-monster/fsm-misc.c +++ b/spaghetti-monster/fsm-misc.c @@ -20,7 +20,7 @@ #ifndef FSM_MISC_C #define FSM_MISC_C -#ifdef USE_BLINK_NUM +#if defined(USE_BLINK_NUM) || defined(USE_BLINK_DIGIT) uint8_t blink_digit(uint8_t num) { //StatePtr old_state = current_state; @@ -38,7 +38,9 @@ uint8_t blink_digit(uint8_t num) { } return nice_delay_ms(600); } +#endif +#ifdef USE_BLINK_NUM uint8_t blink_num(uint8_t num) { //StatePtr old_state = current_state; #if 0 diff --git a/spaghetti-monster/fsm-misc.h b/spaghetti-monster/fsm-misc.h index 7533849..58667a1 100644 --- a/spaghetti-monster/fsm-misc.h +++ b/spaghetti-monster/fsm-misc.h @@ -20,16 +20,22 @@ #ifndef FSM_MISC_H #define FSM_MISC_H -#ifdef USE_BLINK_NUM -#define USE_BLINK +#if defined(USE_BLINK_NUM) || defined(USE_BLINK_DIGIT) #ifndef BLINK_BRIGHTNESS #define BLINK_BRIGHTNESS (MAX_LEVEL/6) #endif +uint8_t blink_digit(uint8_t num); +#endif + +#ifdef USE_BLINK_NUM +//#define USE_BLINK uint8_t blink_num(uint8_t num); #endif +/* #ifdef USE_BLINK uint8_t blink(uint8_t num, uint8_t speed); #endif +*/ #endif diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 0b5bbda..003f3de 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -306,6 +306,10 @@ void low_voltage() { set_state(off_state, 0); } } + // all other modes, just turn off when voltage is low + else { + set_state(off_state, 0); + } } -- cgit v1.2.3 From 0fe75816cb002c442be774a420bc0563344dd4c6 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 02:36:10 -0600 Subject: Added a temperature check mode to ramping-ui, mostly for testing purposes. --- spaghetti-monster/ramping-ui.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 003f3de..2369686 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -36,6 +36,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t strobe_state(EventPtr event, uint16_t arg); #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t tempcheck_state(EventPtr event, uint16_t arg); #endif // brightness control @@ -282,6 +283,20 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: tempcheck mode + else if (event == EV_2clicks) { + set_state(tempcheck_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t tempcheck_state(EventPtr event, uint16_t arg) { // 1 click: off if (event == EV_1click) { set_state(off_state, 0); @@ -338,5 +353,9 @@ void loop() { else if (current_state == battcheck_state) { battcheck(); } + else if (current_state == tempcheck_state) { + blink_num(projected_temperature>>2); + nice_delay_ms(1000); + } #endif } -- cgit v1.2.3 From 5bd3a4edd1e0b324118c9d875b33994bfc85c0dc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 02:37:33 -0600 Subject: Added DarkHorse, a clone of the ZebraLight UI. Still needs strobe, beacon, and memory which lasts across battery changes. --- spaghetti-monster/darkhorse.c | 290 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 spaghetti-monster/darkhorse.c (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c new file mode 100644 index 0000000..b77f541 --- /dev/null +++ b/spaghetti-monster/darkhorse.c @@ -0,0 +1,290 @@ +/* + * DarkHorse: Improved ZebraLight clone 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_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_4MS +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define USE_BATTCHECK +#define BATTCHECK_4bars +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t low_mode_state(EventPtr event, uint16_t arg); +uint8_t med_mode_state(EventPtr event, uint16_t arg); +uint8_t hi_mode_state(EventPtr event, uint16_t arg); +//uint8_t strobe_state(EventPtr event, uint16_t arg); +//uint8_t beacon_state(EventPtr event, uint16_t arg); +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +#endif +// Not a FSM state, just handles stuff common to all low/med/hi states +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); + +// toggle between L1/L2, M1/M2, H1/H2 +uint8_t L1 = 1; +uint8_t M1 = 1; +uint8_t H1 = 1; +// brightness for L2, M2, H2 (valid range 1 to 3 inclusive) +uint8_t L2 = 1; +uint8_t M2 = 1; +uint8_t H2 = 1; +// mode groups, ish +uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm +uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm +uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) { + // primary (H1/M1/L1) + if (primary) { + set_level(modes[0]); + } + // secondary (H2/M2/L2) + else { + set_level(modes[secondary]); + } + target_level = actual_level; +} + +inline void set_low_mode() { set_any_mode(L1, L2, low_modes); } +inline void set_med_mode() { set_any_mode(M1, M2, med_modes); } +inline void set_hi_mode() { set_any_mode(H1, H2, hi_modes); } + + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + standby_mode(); + return 0; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_low_mode(); + return 0; + } + // 1 click (before timeout): go to high level, but allow abort for double click + else if (event == EV_click1_release) { + set_hi_mode(); + return 0; + } + // 1 click: high mode + else if (event == EV_1click) { + set_state(hi_mode_state, 0); + return 0; + } + // click, hold (initially): go to medium mode, but allow abort + else if (event == EV_click2_press) { + set_med_mode(); + return 0; + } + // 2 clicks: medium mode + else if (event == EV_2clicks) { + set_state(med_mode_state, 0); + return 0; + } + // click, click, hold (initially): light off, prep for blinkies + else if (event == EV_click3_press) { + set_level(0); + return 0; + } + // 3 clicks: strobe mode + /* TODO: implement + else if (event == EV_3clicks) { + set_state(party_strobe_state, 255); + return 0; + } + */ + #ifdef USE_BATTCHECK + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + #endif + // hold: go to low mode, but allow ramping up + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at low mode + if (arg >= HOLD_TIMEOUT) + set_state(low_mode_state, 0); + return 0; + } + // hold, release quickly: go to low mode + else if (event == EV_click1_hold_release) { + set_state(low_mode_state, 0); + return 0; + } + /* TODO: implement + // click-release-hold: discrete ramp through all levels + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return 0; + } + */ + return 1; +} + + +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes) { + // turn on LED when entering the mode + if (event == EV_enter_state) { + set_any_mode(*primary, *secondary, modes); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: toggle primary/secondary level + else if (event == EV_2clicks) { + *primary ^= 1; + set_any_mode(*primary, *secondary, modes); + return MISCHIEF_MANAGED; + } + // click-release-hold: change secondary level + if (event == EV_click2_hold) { + if (arg % HOLD_TIMEOUT == 0) { + *secondary = (*secondary + 1) & 3; + if (! *secondary) *secondary = 1; + *primary = 0; + set_any_mode(*primary, *secondary, modes); + } + return MISCHIEF_MANAGED; + } + #ifdef USE_THERMAL_REGULATION + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return 0; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return 0; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t low_mode_state(EventPtr event, uint16_t arg) { + // hold: change brightness (brighter) + if (event == EV_click1_hold) { + if (arg % HOLD_TIMEOUT == 0) { + set_state(med_mode_state, 0); + } + return MISCHIEF_MANAGED; + } + else return any_mode_state(event, arg, &L1, &L2, low_modes); +} + +uint8_t med_mode_state(EventPtr event, uint16_t arg) { + // hold: change brightness (brighter) + if (event == EV_click1_hold) { + if (arg % HOLD_TIMEOUT == 0) { + set_state(hi_mode_state, 0); + } + return MISCHIEF_MANAGED; + } + else return any_mode_state(event, arg, &M1, &M2, med_modes); +} + +uint8_t hi_mode_state(EventPtr event, uint16_t arg) { + // hold: change brightness (brighter) + if (event == EV_click1_hold) { + if (arg % HOLD_TIMEOUT == 0) { + set_state(low_mode_state, 0); + } + return MISCHIEF_MANAGED; + } + else return any_mode_state(event, arg, &H1, &H2, hi_modes); +} + + +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} +#endif + + +void low_voltage() { + // TODO: rewrite this + /* + // "step down" from strobe to something low + if (current_state == party_strobe_state) { + set_state(steady_state, RAMP_SIZE/6); + } + */ + if (current_state == hi_mode_state) { + set_state(med_mode_state, 0); + } + else if (current_state == med_mode_state) { + set_state(low_mode_state, 0); + } + else if (current_state == low_mode_state) { + set_state(off_state, 0); + } +} + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + push_state(off_state, 0); +} + +void loop() { + #ifdef USE_BATTCHECK + if (current_state == battcheck_state) { + nice_delay_ms(500); // wait a moment to measure voltage + battcheck(); + set_state(off_state, 0); + } + #endif +} + + -- cgit v1.2.3 From 2a13e91628ab830aa9ce67ece69a177544085a18 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 02:46:34 -0600 Subject: Forgot to wrap one line for making thermal regulation optional. --- spaghetti-monster/darkhorse.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index b77f541..2bfa2e9 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -68,7 +68,9 @@ void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) { else { set_level(modes[secondary]); } + #ifdef USE_THERMAL_REGULATION target_level = actual_level; + #endif } inline void set_low_mode() { set_any_mode(L1, L2, low_modes); } -- cgit v1.2.3 From 674259407ace91bef354059c842b4114e95eb23c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 03:06:12 -0600 Subject: Added beacons/strobes to DarkHorse. Added a way to explicitly cancel the current "nice" delay without changing state. --- spaghetti-monster/darkhorse.c | 63 ++++++++++++++++++++++++++++++++++++++---- spaghetti-monster/fsm-events.c | 7 ++++- spaghetti-monster/fsm-events.h | 1 + 3 files changed, 64 insertions(+), 7 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 2bfa2e9..5a522a4 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -33,8 +33,7 @@ uint8_t off_state(EventPtr event, uint16_t arg); uint8_t low_mode_state(EventPtr event, uint16_t arg); uint8_t med_mode_state(EventPtr event, uint16_t arg); uint8_t hi_mode_state(EventPtr event, uint16_t arg); -//uint8_t strobe_state(EventPtr event, uint16_t arg); -//uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t strobe_beacon_state(EventPtr event, uint16_t arg); #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); #endif @@ -53,6 +52,12 @@ uint8_t H2 = 1; uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm +// strobe/beacon modes: +// 0: 0.2 Hz beacon at L1 +// 1: 0.2 Hz beacon at H1 +// 2: 4 Hz strobe at H1 +// 3: 19 Hz strobe at H1 +uint8_t strobe_beacon_mode = 0; #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down @@ -117,12 +122,10 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return 0; } // 3 clicks: strobe mode - /* TODO: implement else if (event == EV_3clicks) { - set_state(party_strobe_state, 255); + set_state(strobe_beacon_state, 0); return 0; } - */ #ifdef USE_BATTCHECK // 4 clicks: battcheck mode else if (event == EV_4clicks) { @@ -252,6 +255,22 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg) { #endif +uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: rotate through blinky modes + else if (event == EV_2clicks) { + strobe_beacon_mode = (strobe_beacon_mode + 1) & 3; + interrupt_nice_delays(); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + void low_voltage() { // TODO: rewrite this /* @@ -280,8 +299,40 @@ void setup() { } void loop() { + if (current_state == strobe_beacon_state) { + switch(strobe_beacon_mode) { + // 0.2 Hz beacon at L1 + case 0: + set_level(low_modes[0]); + nice_delay_ms(500); + set_level(0); + nice_delay_ms(4500); + break; + // 0.2 Hz beacon at H1 + case 1: + set_level(hi_modes[0]); + nice_delay_ms(500); + set_level(0); + nice_delay_ms(4500); + break; + // 4 Hz tactical strobe at H1 + case 2: + set_level(hi_modes[0]); + nice_delay_ms(83); + set_level(0); + nice_delay_ms(167); + break; + // 19 Hz tactical strobe at H1 + case 3: + set_level(hi_modes[0]); + nice_delay_ms(17); + set_level(0); + nice_delay_ms(35); + break; + } + } #ifdef USE_BATTCHECK - if (current_state == battcheck_state) { + else if (current_state == battcheck_state) { nice_delay_ms(500); // wait a moment to measure voltage battcheck(); set_state(off_state, 0); diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 29ef415..90d0ffa 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -106,6 +106,10 @@ void process_emissions() { } } +// explicitly interrupt these "nice" delays +volatile uint8_t nice_delay_interrupt = 0; +inline void interrupt_nice_delays() { nice_delay_interrupt = 1; } + // like delay_ms, except it aborts on state change // return value: // 0: state changed @@ -115,7 +119,8 @@ uint8_t nice_delay_ms(uint16_t ms) { while(ms-- > 0) { _delay_loop_2(BOGOMIPS*98/100); process_emissions(); - if (old_state != current_state) { + if ((nice_delay_interrupt) || (old_state != current_state)) { + nice_delay_interrupt = 0; return 0; // state changed; abort } } diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 141a0dc..f2a0181 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -223,5 +223,6 @@ void emit_current_event(uint16_t arg); uint8_t nice_delay_ms(uint16_t ms); //uint8_t nice_delay_4ms(uint8_t ms); //uint8_t nice_delay_s(); +inline void interrupt_nice_delays(); #endif -- cgit v1.2.3 From 530ce37a5aeb29a9c31f8f37d0ce5e561e80995c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 03:37:52 -0600 Subject: Forgot to handle beacon/strobe in LVP earlier. --- spaghetti-monster/darkhorse.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 5a522a4..03cd5e7 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -272,13 +272,6 @@ uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { void low_voltage() { - // TODO: rewrite this - /* - // "step down" from strobe to something low - if (current_state == party_strobe_state) { - set_state(steady_state, RAMP_SIZE/6); - } - */ if (current_state == hi_mode_state) { set_state(med_mode_state, 0); } @@ -288,6 +281,10 @@ void low_voltage() { else if (current_state == low_mode_state) { set_state(off_state, 0); } + // "step down" from blinkies to low + else if (current_state == strobe_beacon_state) { + set_state(low_mode_state, 0); + } } void setup() { -- cgit v1.2.3 From 4b8fef64d036336e329b8b7871a42440cdc38766 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 03:47:44 -0600 Subject: Made DarkHorse always start at low when holding the button, like a real ZebraLight does. (previously ramped up one mode instead of resetting to low each time) --- spaghetti-monster/darkhorse.c | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 03cd5e7..1244864 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -168,6 +168,22 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * set_state(off_state, 0); return MISCHIEF_MANAGED; } + // hold: change brightness (low, med, hi, always starting at low) + else if (event == EV_click1_hold) { + uint8_t which = arg % (HOLD_TIMEOUT * 3) / HOLD_TIMEOUT; + switch(which) { + case 0: + set_state(low_mode_state, 0); + break; + case 1: + set_state(med_mode_state, 0); + break; + case 2: + set_state(hi_mode_state, 0); + break; + } + return MISCHIEF_MANAGED; + } // 2 clicks: toggle primary/secondary level else if (event == EV_2clicks) { *primary ^= 1; @@ -210,36 +226,15 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * } uint8_t low_mode_state(EventPtr event, uint16_t arg) { - // hold: change brightness (brighter) - if (event == EV_click1_hold) { - if (arg % HOLD_TIMEOUT == 0) { - set_state(med_mode_state, 0); - } - return MISCHIEF_MANAGED; - } - else return any_mode_state(event, arg, &L1, &L2, low_modes); + return any_mode_state(event, arg, &L1, &L2, low_modes); } uint8_t med_mode_state(EventPtr event, uint16_t arg) { - // hold: change brightness (brighter) - if (event == EV_click1_hold) { - if (arg % HOLD_TIMEOUT == 0) { - set_state(hi_mode_state, 0); - } - return MISCHIEF_MANAGED; - } - else return any_mode_state(event, arg, &M1, &M2, med_modes); + return any_mode_state(event, arg, &M1, &M2, med_modes); } uint8_t hi_mode_state(EventPtr event, uint16_t arg) { - // hold: change brightness (brighter) - if (event == EV_click1_hold) { - if (arg % HOLD_TIMEOUT == 0) { - set_state(low_mode_state, 0); - } - return MISCHIEF_MANAGED; - } - else return any_mode_state(event, arg, &H1, &H2, hi_modes); + return any_mode_state(event, arg, &H1, &H2, hi_modes); } -- cgit v1.2.3 From 02d371e769585b2e9cebdb9346b2f11fc4c0a40e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 04:57:37 -0600 Subject: Fixed bug: strobes could interfere with hold-from-off next time light was used. (because it'd enter standby mode during the strobe pulse, then continue the flow at next activation by turning the light off) Replaced bare return values with ones defined in the FSM library. --- spaghetti-monster/darkhorse.c | 44 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 1244864..6a17943 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -87,50 +87,51 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { set_level(0); + empty_event_sequence(); // sleep while off (lower power use) standby_mode(); - return 0; + return EVENT_HANDLED; } // hold (initially): go to lowest level, but allow abort for regular click else if (event == EV_click1_press) { set_low_mode(); - return 0; + return EVENT_HANDLED; } // 1 click (before timeout): go to high level, but allow abort for double click else if (event == EV_click1_release) { set_hi_mode(); - return 0; + return EVENT_HANDLED; } // 1 click: high mode else if (event == EV_1click) { set_state(hi_mode_state, 0); - return 0; + return EVENT_HANDLED; } // click, hold (initially): go to medium mode, but allow abort else if (event == EV_click2_press) { set_med_mode(); - return 0; + return EVENT_HANDLED; } // 2 clicks: medium mode else if (event == EV_2clicks) { set_state(med_mode_state, 0); - return 0; + return EVENT_HANDLED; } // click, click, hold (initially): light off, prep for blinkies else if (event == EV_click3_press) { set_level(0); - return 0; + return EVENT_HANDLED; } // 3 clicks: strobe mode else if (event == EV_3clicks) { set_state(strobe_beacon_state, 0); - return 0; + return EVENT_HANDLED; } #ifdef USE_BATTCHECK // 4 clicks: battcheck mode else if (event == EV_4clicks) { set_state(battcheck_state, 0); - return MISCHIEF_MANAGED; + return EVENT_HANDLED; } #endif // hold: go to low mode, but allow ramping up @@ -139,21 +140,21 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // give the user time to release at low mode if (arg >= HOLD_TIMEOUT) set_state(low_mode_state, 0); - return 0; + return EVENT_HANDLED; } // hold, release quickly: go to low mode else if (event == EV_click1_hold_release) { set_state(low_mode_state, 0); - return 0; + return EVENT_HANDLED; } /* TODO: implement // click-release-hold: discrete ramp through all levels else if (event == EV_click2_hold) { set_state(steady_state, MAX_LEVEL); - return 0; + return EVENT_HANDLED; } */ - return 1; + return EVENT_NOT_HANDLED; } @@ -209,7 +210,7 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; set_level(stepdown); } - return 0; + return EVENT_HANDLED; } // underheating: increase slowly if we're lower than the target // (proportional to how low we are) @@ -219,7 +220,7 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * if (stepup > target_level) stepup = target_level; set_level(stepup); } - return 0; + return EVENT_HANDLED; } #endif return EVENT_NOT_HANDLED; @@ -240,11 +241,6 @@ uint8_t hi_mode_state(EventPtr event, uint16_t arg) { #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } return EVENT_NOT_HANDLED; } #endif @@ -253,9 +249,16 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg) { uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { // 1 click: off if (event == EV_1click) { + interrupt_nice_delays(); set_state(off_state, 0); return MISCHIEF_MANAGED; } + // 1 click (initially): cancel current blink + if (event == EV_click1_release) { + // only cancel if the light is actually on + if (actual_level) interrupt_nice_delays(); + return MISCHIEF_MANAGED; + } // 2 clicks: rotate through blinky modes else if (event == EV_2clicks) { strobe_beacon_mode = (strobe_beacon_mode + 1) & 3; @@ -327,6 +330,7 @@ void loop() { else if (current_state == battcheck_state) { nice_delay_ms(500); // wait a moment to measure voltage battcheck(); + empty_event_sequence(); set_state(off_state, 0); } #endif -- cgit v1.2.3 From d32ae87d50e92cb59fdc28cf58622b32aede2f3e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 13:52:52 -0600 Subject: Avoid suspending at weird times by deferring the call to standby mode. Turned strobe into a function to avoid repeating code, and made it tolerate interruption better. Got rid of delay after battcheck, since it caused problems if the user didn't wait long enough before trying to turn the light back on. --- spaghetti-monster/darkhorse.c | 52 +++++++++++++++++++++++-------------------- spaghetti-monster/fsm-adc.c | 4 ++++ 2 files changed, 32 insertions(+), 24 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 6a17943..a7937e6 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -26,6 +26,7 @@ #define RAMP_LENGTH 150 #define USE_BATTCHECK #define BATTCHECK_4bars +#define DONT_DELAY_AFTER_BATTCHECK #include "spaghetti-monster.h" // FSM states @@ -59,6 +60,9 @@ uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 // 3: 19 Hz strobe at H1 uint8_t strobe_beacon_mode = 0; +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down uint8_t target_level = 0; @@ -87,9 +91,8 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { set_level(0); - empty_event_sequence(); // sleep while off (lower power use) - standby_mode(); + go_to_standby = 1; return EVENT_HANDLED; } // hold (initially): go to lowest level, but allow abort for regular click @@ -107,7 +110,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(hi_mode_state, 0); return EVENT_HANDLED; } - // click, hold (initially): go to medium mode, but allow abort + // click, press (initially): go to medium mode, but allow abort else if (event == EV_click2_press) { set_med_mode(); return EVENT_HANDLED; @@ -117,7 +120,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(med_mode_state, 0); return EVENT_HANDLED; } - // click, click, hold (initially): light off, prep for blinkies + // click, click, press (initially): light off, prep for blinkies else if (event == EV_click3_press) { set_level(0); return EVENT_HANDLED; @@ -249,14 +252,12 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg) { uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { // 1 click: off if (event == EV_1click) { - interrupt_nice_delays(); set_state(off_state, 0); return MISCHIEF_MANAGED; } // 1 click (initially): cancel current blink if (event == EV_click1_release) { - // only cancel if the light is actually on - if (actual_level) interrupt_nice_delays(); + interrupt_nice_delays(); return MISCHIEF_MANAGED; } // 2 clicks: rotate through blinky modes @@ -285,6 +286,13 @@ void low_voltage() { } } +void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { + set_level(level); + if (! nice_delay_ms(ontime)) return; + set_level(0); + nice_delay_ms(offtime); +} + void setup() { set_level(RAMP_SIZE/8); delay_4ms(3); @@ -294,43 +302,39 @@ void setup() { } void loop() { + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + if (current_state == strobe_beacon_state) { switch(strobe_beacon_mode) { // 0.2 Hz beacon at L1 case 0: - set_level(low_modes[0]); - nice_delay_ms(500); - set_level(0); - nice_delay_ms(4500); + strobe(low_modes[0], 500, 4500); break; // 0.2 Hz beacon at H1 case 1: - set_level(hi_modes[0]); - nice_delay_ms(500); - set_level(0); - nice_delay_ms(4500); + strobe(hi_modes[0], 500, 4500); break; // 4 Hz tactical strobe at H1 case 2: - set_level(hi_modes[0]); - nice_delay_ms(83); - set_level(0); - nice_delay_ms(167); + strobe(hi_modes[0], 83, 167); break; // 19 Hz tactical strobe at H1 case 3: - set_level(hi_modes[0]); - nice_delay_ms(17); - set_level(0); - nice_delay_ms(35); + strobe(hi_modes[0], 17, 35); break; } } + #ifdef USE_BATTCHECK else if (current_state == battcheck_state) { nice_delay_ms(500); // wait a moment to measure voltage battcheck(); - empty_event_sequence(); set_state(off_state, 0); } #endif diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 622ef0f..541f9a4 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -282,9 +282,13 @@ void battcheck() { for(i=0; voltage >= pgm_read_byte(voltage_blinks + i); i++) {} + #ifdef DONT_DELAY_AFTER_BATTCHECK + blink_digit(i); + #else if (blink_digit(i)) nice_delay_ms(1000); #endif + #endif } #endif -- cgit v1.2.3 From 6d9ceae8eab62ba33011a82c8fad8e55d37fe7ba Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 16:48:25 -0600 Subject: Added eeprom load/save API (no wear levelling yet), verified it works in DarkHorse. --- spaghetti-monster/darkhorse.c | 37 +++++++++++++++++- spaghetti-monster/fsm-eeprom.c | 71 +++++++++++++++++++++++++++++++++++ spaghetti-monster/fsm-eeprom.h | 58 ++++++++++++++++++++++++++++ spaghetti-monster/fsm-events.h | 8 ++++ spaghetti-monster/spaghetti-monster.h | 6 +++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 spaghetti-monster/fsm-eeprom.c create mode 100644 spaghetti-monster/fsm-eeprom.h (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index a7937e6..2aa1b7b 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -27,6 +27,8 @@ #define USE_BATTCHECK #define BATTCHECK_4bars #define DONT_DELAY_AFTER_BATTCHECK +#define USE_EEPROM +#define EEPROM_BYTES 5 #include "spaghetti-monster.h" // FSM states @@ -41,6 +43,9 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg); // Not a FSM state, just handles stuff common to all low/med/hi states uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); +void load_config(); +void save_config(); + // toggle between L1/L2, M1/M2, H1/H2 uint8_t L1 = 1; uint8_t M1 = 1; @@ -192,10 +197,11 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * else if (event == EV_2clicks) { *primary ^= 1; set_any_mode(*primary, *secondary, modes); + save_config(); return MISCHIEF_MANAGED; } // click-release-hold: change secondary level - if (event == EV_click2_hold) { + else if (event == EV_click2_hold) { if (arg % HOLD_TIMEOUT == 0) { *secondary = (*secondary + 1) & 3; if (! *secondary) *secondary = 1; @@ -204,6 +210,10 @@ uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t * } return MISCHIEF_MANAGED; } + // click, hold, release: save secondary level + else if (event == EV_click2_hold_release) { + save_config(); + } #ifdef USE_THERMAL_REGULATION // TODO: test this on a real light // overheating: drop by an amount proportional to how far we are above the ceiling @@ -263,6 +273,7 @@ uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { // 2 clicks: rotate through blinky modes else if (event == EV_2clicks) { strobe_beacon_mode = (strobe_beacon_mode + 1) & 3; + save_config(); interrupt_nice_delays(); return MISCHIEF_MANAGED; } @@ -293,11 +304,35 @@ void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { nice_delay_ms(offtime); } +void load_config() { + if (load_eeprom()) { + H1 = !(!(eeprom[0] & 0b00000100)); + M1 = !(!(eeprom[0] & 0b00000010)); + L1 = !(!(eeprom[0] & 0b00000001)); + H2 = eeprom[1]; + M2 = eeprom[2]; + L2 = eeprom[3]; + strobe_beacon_mode = eeprom[4]; + } +} + +void save_config() { + eeprom[0] = (H1<<2) | (M1<<1) | (L1); + eeprom[1] = H2; + eeprom[2] = M2; + eeprom[3] = L2; + eeprom[4] = strobe_beacon_mode; + + save_eeprom(); +} + void setup() { set_level(RAMP_SIZE/8); delay_4ms(3); set_level(0); + load_config(); + push_state(off_state, 0); } diff --git a/spaghetti-monster/fsm-eeprom.c b/spaghetti-monster/fsm-eeprom.c new file mode 100644 index 0000000..5451baf --- /dev/null +++ b/spaghetti-monster/fsm-eeprom.c @@ -0,0 +1,71 @@ +/* + * fsm-eeprom.c: EEPROM API 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 . + */ + +#ifndef FSM_EEPROM_C +#define FSM_EEPROM_C + +#include "fsm-eeprom.h" + +#if EEPROM_BYTES > 0 +#if EEPROM_BYTES >= (EEPSIZE/2) +#error Requested EEPROM_BYTES too big. +#endif +uint8_t eeprom[EEPROM_BYTES]; + +uint8_t load_eeprom() { + cli(); + // check if eeprom has been initialized; abort if it hasn't + uint8_t marker = eeprom_read_byte((uint8_t *)EEP_START); + if (marker != EEP_MARKER) { sei(); return 0; } + + // load the actual data + for(uint8_t i=0; i 0 +#if EEPROM_WL_BYTES >= (EEPSIZE/4) +#error Requested EEPROM_WL_BYTES too big. +#endif +uint8_t eeprom_wl[EEPROM_WL_BYTES]; + +uint8_t load_wl_eeprom() { +} + +void save_wl_eeprom() { +} +#endif + + +#endif diff --git a/spaghetti-monster/fsm-eeprom.h b/spaghetti-monster/fsm-eeprom.h new file mode 100644 index 0000000..3d34f23 --- /dev/null +++ b/spaghetti-monster/fsm-eeprom.h @@ -0,0 +1,58 @@ +/* + * fsm-eeprom.h: EEPROM API 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 . + */ + +#ifndef FSM_EEPROM_H +#define FSM_EEPROM_H + +#include + +// set this higher to enable normal eeprom functions +#ifndef EEPROM_BYTES +#define EEPROM_BYTES 0 +#endif + +// set this higher to enable wear-levelled eeprom functions +#ifndef EEPROM_WL_BYTES +#define EEPROM_WL_BYTES 0 +#endif + +#if EEPROM_BYTES > 0 +uint8_t eeprom[EEPROM_BYTES]; +uint8_t load_eeprom(); // returns 1 for success, 0 for no data found +void save_eeprom(); +#define EEP_START (EEPSIZE/2) +#endif + +#if EEPROM_WL_BYTES > 0 +uint8_t eeprom_wl[EEPROM_WL_BYTES]; +uint8_t load_wl_eeprom(); // returns 1 for success, 0 for no data found +void save_wl_eeprom(); +#define EEP_WL_SIZE (EEPSIZE/2) +#endif + +#if EEPSIZE > 256 +#define EEP_OFFSET_T uint16_t +#else +#define EEP_OFFSET_T uint8_t +#endif + +// if this marker isn't found, the eeprom is assumed to be blank +#define EEP_MARKER 0b10100101 + +#endif diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index f2a0181..90dcbd6 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -127,6 +127,13 @@ Event EV_click2_hold[] = { A_PRESS, A_HOLD, 0 }; +Event EV_click2_hold_release[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_HOLD, + A_RELEASE, + 0 }; Event EV_click2_release[] = { A_PRESS, A_RELEASE, @@ -190,6 +197,7 @@ EventPtr event_sequences[] = { EV_click1_hold_release, EV_click2_press, EV_click2_hold, + EV_click2_hold_release, EV_click2_release, EV_click2_complete, EV_click3_press, diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index 3727930..82800b4 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -34,6 +34,9 @@ #include "fsm-pcint.h" #include "fsm-standby.h" #include "fsm-ramping.h" +#ifdef USE_EEPROM +#include "fsm-eeprom.h" +#endif #include "fsm-misc.h" #include "fsm-main.h" @@ -71,5 +74,8 @@ void loop(); #include "fsm-pcint.c" #include "fsm-standby.c" #include "fsm-ramping.c" +#ifdef USE_EEPROM +#include "fsm-eeprom.c" +#endif #include "fsm-misc.c" #include "fsm-main.c" -- cgit v1.2.3 From 2370e0a3ce0a3ab928ac19af074a3ef67d533ca6 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 17:12:31 -0600 Subject: Made wear-levelling work. Takes a bunch of extra ROM though. Also, I've only tested it a tiny amount. --- spaghetti-monster/darkhorse.c | 30 +++++++++++++------------- spaghetti-monster/fsm-eeprom.c | 48 ++++++++++++++++++++++++++++++++++++------ spaghetti-monster/fsm-eeprom.h | 6 ++++++ 3 files changed, 63 insertions(+), 21 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 2aa1b7b..0d02481 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -28,7 +28,7 @@ #define BATTCHECK_4bars #define DONT_DELAY_AFTER_BATTCHECK #define USE_EEPROM -#define EEPROM_BYTES 5 +#define EEPROM_WL_BYTES 5 #include "spaghetti-monster.h" // FSM states @@ -305,25 +305,25 @@ void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { } void load_config() { - if (load_eeprom()) { - H1 = !(!(eeprom[0] & 0b00000100)); - M1 = !(!(eeprom[0] & 0b00000010)); - L1 = !(!(eeprom[0] & 0b00000001)); - H2 = eeprom[1]; - M2 = eeprom[2]; - L2 = eeprom[3]; - strobe_beacon_mode = eeprom[4]; + if (load_wl_eeprom()) { + H1 = !(!(eeprom_wl[0] & 0b00000100)); + M1 = !(!(eeprom_wl[0] & 0b00000010)); + L1 = !(!(eeprom_wl[0] & 0b00000001)); + H2 = eeprom_wl[1]; + M2 = eeprom_wl[2]; + L2 = eeprom_wl[3]; + strobe_beacon_mode = eeprom_wl[4]; } } void save_config() { - eeprom[0] = (H1<<2) | (M1<<1) | (L1); - eeprom[1] = H2; - eeprom[2] = M2; - eeprom[3] = L2; - eeprom[4] = strobe_beacon_mode; + eeprom_wl[0] = (H1<<2) | (M1<<1) | (L1); + eeprom_wl[1] = H2; + eeprom_wl[2] = M2; + eeprom_wl[3] = L2; + eeprom_wl[4] = strobe_beacon_mode; - save_eeprom(); + save_wl_eeprom(); } void setup() { diff --git a/spaghetti-monster/fsm-eeprom.c b/spaghetti-monster/fsm-eeprom.c index 5451baf..e464785 100644 --- a/spaghetti-monster/fsm-eeprom.c +++ b/spaghetti-monster/fsm-eeprom.c @@ -23,9 +23,6 @@ #include "fsm-eeprom.h" #if EEPROM_BYTES > 0 -#if EEPROM_BYTES >= (EEPSIZE/2) -#error Requested EEPROM_BYTES too big. -#endif uint8_t eeprom[EEPROM_BYTES]; uint8_t load_eeprom() { @@ -55,15 +52,54 @@ void save_eeprom() { #endif #if EEPROM_WL_BYTES > 0 -#if EEPROM_WL_BYTES >= (EEPSIZE/4) -#error Requested EEPROM_WL_BYTES too big. -#endif uint8_t eeprom_wl[EEPROM_WL_BYTES]; +EEP_OFFSET_T eep_wl_prev_offset; uint8_t load_wl_eeprom() { + cli(); + // check if eeprom has been initialized; abort if it hasn't + uint8_t found = 0; + EEP_OFFSET_T offset; + for(offset = 0; + offset < EEP_WL_SIZE - EEPROM_WL_BYTES - 1; + offset += (EEPROM_WL_BYTES + 1)) { + if (eeprom_read_byte((uint8_t *)offset) == EEP_MARKER) { + found = 1; + eep_wl_prev_offset = offset; + break; + } + } + + if (found) { + // load the actual data + for(uint8_t i=0; i EEP_WL_SIZE-EEPROM_WL_BYTES-1) offset = 0; + eep_wl_prev_offset = offset; + // marker byte + eeprom_update_byte((uint8_t *)offset, EEP_MARKER); + offset ++; + // user data + for(uint8_t i=0; i 0 +#if EEPROM_BYTES >= (EEPSIZE/2) +#error Requested EEPROM_BYTES too big. +#endif uint8_t eeprom[EEPROM_BYTES]; uint8_t load_eeprom(); // returns 1 for success, 0 for no data found void save_eeprom(); @@ -40,6 +43,9 @@ void save_eeprom(); #endif #if EEPROM_WL_BYTES > 0 +#if EEPROM_WL_BYTES >= (EEPSIZE/4) +#error Requested EEPROM_WL_BYTES too big. +#endif uint8_t eeprom_wl[EEPROM_WL_BYTES]; uint8_t load_wl_eeprom(); // returns 1 for success, 0 for no data found void save_wl_eeprom(); -- cgit v1.2.3 From eba8d84cfb29f5c7209e2a162497d965bea2ff23 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2017 17:15:28 -0600 Subject: Revert DarkHorse back to no wear-levelling. It's smaller, and WL is probably not needed for this UI. I only converted it as a test, and it worked, so ... woot. --- spaghetti-monster/darkhorse.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 0d02481..2aa1b7b 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -28,7 +28,7 @@ #define BATTCHECK_4bars #define DONT_DELAY_AFTER_BATTCHECK #define USE_EEPROM -#define EEPROM_WL_BYTES 5 +#define EEPROM_BYTES 5 #include "spaghetti-monster.h" // FSM states @@ -305,25 +305,25 @@ void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { } void load_config() { - if (load_wl_eeprom()) { - H1 = !(!(eeprom_wl[0] & 0b00000100)); - M1 = !(!(eeprom_wl[0] & 0b00000010)); - L1 = !(!(eeprom_wl[0] & 0b00000001)); - H2 = eeprom_wl[1]; - M2 = eeprom_wl[2]; - L2 = eeprom_wl[3]; - strobe_beacon_mode = eeprom_wl[4]; + if (load_eeprom()) { + H1 = !(!(eeprom[0] & 0b00000100)); + M1 = !(!(eeprom[0] & 0b00000010)); + L1 = !(!(eeprom[0] & 0b00000001)); + H2 = eeprom[1]; + M2 = eeprom[2]; + L2 = eeprom[3]; + strobe_beacon_mode = eeprom[4]; } } void save_config() { - eeprom_wl[0] = (H1<<2) | (M1<<1) | (L1); - eeprom_wl[1] = H2; - eeprom_wl[2] = M2; - eeprom_wl[3] = L2; - eeprom_wl[4] = strobe_beacon_mode; + eeprom[0] = (H1<<2) | (M1<<1) | (L1); + eeprom[1] = H2; + eeprom[2] = M2; + eeprom[3] = L2; + eeprom[4] = strobe_beacon_mode; - save_wl_eeprom(); + save_eeprom(); } void setup() { -- cgit v1.2.3 From c2b995167d2f523f13187d5ca39c9343cf62a702 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 01:58:07 -0600 Subject: Made it easier to configure the maximum number of clicks it'll try to count in one sequence. (use #define MAX_CLICKS 5, for example) Keeps data sizes from being excessively large without having to edit FSM sources per UI. --- spaghetti-monster/fsm-events.h | 58 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 90dcbd6..114ccc4 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -35,7 +35,11 @@ typedef struct Emission { #define MISCHIEF_MANAGED EVENT_HANDLED #define MISCHIEF_NOT_MANAGED EVENT_NOT_HANDLED -#define EV_MAX_LEN 16 +#ifndef MAX_CLICKS +#define MAX_CLICKS 4 +#endif + +#define EV_MAX_LEN ((MAX_CLICKS*2)+3) uint8_t current_event[EV_MAX_LEN]; // at 0.016 ms per tick, 255 ticks = 4.08 s // TODO: 16 bits? @@ -116,6 +120,7 @@ Event EV_click1_hold_release[] = { A_HOLD, A_RELEASE, 0 }; +#if MAX_CLICKS >= 2 Event EV_click2_press[] = { A_PRESS, A_RELEASE, @@ -148,6 +153,8 @@ Event EV_click2_complete[] = { A_RELEASE, A_RELEASE_TIMEOUT, 0 }; +#endif // MAX_CLICKS >= 2 +#if MAX_CLICKS >= 3 Event EV_click3_press[] = { A_PRESS, A_RELEASE, @@ -173,6 +180,8 @@ Event EV_click3_complete[] = { A_RELEASE, A_RELEASE_TIMEOUT, 0 }; +#endif // MAX_CLICKS >= 3 +#if MAX_CLICKS >= 4 #define EV_4clicks EV_click4_complete Event EV_click4_complete[] = { A_PRESS, @@ -185,6 +194,41 @@ Event EV_click4_complete[] = { A_RELEASE, A_RELEASE_TIMEOUT, 0 }; +#endif +#if MAX_CLICKS >= 5 +#define EV_5clicks EV_click5_complete +Event EV_click5_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 6 +#define EV_6clicks EV_click6_complete +Event EV_click6_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif // ... and so on // A list of button event types for easy iteration @@ -195,15 +239,27 @@ EventPtr event_sequences[] = { EV_click1_complete, EV_click1_hold, EV_click1_hold_release, + #if MAX_CLICKS >= 2 EV_click2_press, EV_click2_hold, EV_click2_hold_release, EV_click2_release, EV_click2_complete, + #endif + #if MAX_CLICKS >= 3 EV_click3_press, EV_click3_release, EV_click3_complete, + #endif + #if MAX_CLICKS >= 4 EV_click4_complete, + #endif + #if MAX_CLICKS >= 5 + EV_click5_complete, + #endif + #if MAX_CLICKS >= 6 + EV_click6_complete, + #endif // ... }; -- cgit v1.2.3 From 904f94d70c77aa4f66f53870c08d0672c278ed29 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 01:58:53 -0600 Subject: Started on Anduril, a Narsil-inspired UI. --- spaghetti-monster/anduril.c | 465 ++++++++++++++++++++++++++++++++++++++++++ spaghetti-monster/anduril.txt | 67 ++++++ 2 files changed, 532 insertions(+) create mode 100644 spaghetti-monster/anduril.c create mode 100644 spaghetti-monster/anduril.txt (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c new file mode 100644 index 0000000..96e45db --- /dev/null +++ b/spaghetti-monster/anduril.c @@ -0,0 +1,465 @@ +/* + * Anduril: Narsil-inspired UI for SpaghettiMonster. + * (Anduril is Aragorn's sword, the blade Narsil reforged) + * + * 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_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 32 +#define USE_DELAY_MS +#define USE_DELAY_4MS +#define USE_DELAY_ZERO +#define USE_RAMPING +#define USE_BATTCHECK +#define BATTCHECK_VpT +#define RAMP_LENGTH 150 +#define MAX_CLICKS 5 +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t strobe_state(EventPtr event, uint16_t arg); +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t tempcheck_state(EventPtr event, uint16_t arg); +#endif +uint8_t lockout_state(EventPtr event, uint16_t arg); + +void blink_confirm(uint8_t num); + +// brightness control +uint8_t memorized_level = MAX_1x7135; +// smooth vs discrete ramping +uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete +uint8_t ramp_smooth_floor = 1; +uint8_t ramp_smooth_ceil = MAX_LEVEL; +uint8_t ramp_discrete_floor = 20; +uint8_t ramp_discrete_ceil = MAX_LEVEL-50; +uint8_t ramp_discrete_steps = 7; +uint8_t ramp_discrete_step_size; + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +// strobe timing +volatile uint8_t strobe_delay = 67; +volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + go_to_standby = 1; + return MISCHIEF_MANAGED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + if (ramp_style == 0) { + set_level(ramp_smooth_floor); + } else { + set_level(ramp_discrete_floor); + } + return MISCHIEF_MANAGED; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } + // 2 clicks (initial press): off, to prep for later events + else if (event == EV_click2_press) { + set_level(0); + return MISCHIEF_MANAGED; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + if (ramp_style == 0) { + set_state(steady_state, ramp_smooth_ceil); + } else { + set_state(steady_state, ramp_discrete_ceil); + } + return MISCHIEF_MANAGED; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } + #ifdef USE_BATTCHECK + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + #endif + // 5 clicks: soft lockout + else if (event == EV_5clicks) { + blink_confirm(5); + set_state(lockout_state, 0); + return MISCHIEF_MANAGED; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at moon level + if (arg >= HOLD_TIMEOUT) { + if (ramp_style == 0) { + set_state(steady_state, ramp_smooth_floor); + } else { + set_state(steady_state, ramp_discrete_floor); + } + } + return MISCHIEF_MANAGED; + } + // hold, release quickly: go to lowest level + else if (event == EV_click1_hold_release) { + if (ramp_style == 0) { + set_state(steady_state, ramp_smooth_floor); + } else { + set_state(steady_state, ramp_discrete_floor); + } + return MISCHIEF_MANAGED; + } + // click, hold: go to highest level (for ramping down) + else if (event == EV_click2_hold) { + if (ramp_style == 0) { + set_state(steady_state, ramp_smooth_ceil); + } else { + set_state(steady_state, ramp_discrete_ceil); + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t steady_state(EventPtr event, uint16_t arg) { + uint8_t mode_min = ramp_smooth_floor; + uint8_t mode_max = ramp_smooth_ceil; + uint8_t ramp_step_size = 1; + if (ramp_style) { + mode_min = ramp_discrete_floor; + mode_max = ramp_discrete_ceil; + // TODO: Fixed-point for better precision + //ramp_step_size = ((1 + mode_max - mode_min)<<4) / (ramp_discrete_steps-1); + ramp_step_size = (1 + mode_max - mode_min) / (ramp_discrete_steps-1); + } + + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > mode_min) && (arg < mode_max)) + memorized_level = arg; + // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + // true turbo, not the mode-specific ceiling + set_level(MAX_LEVEL); + } + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return MISCHIEF_MANAGED; + } + // 3 clicks: toggle smooth vs discrete ramping + else if (event == EV_3clicks) { + ramp_style ^= 1; + if (ramp_style) { + mode_min = ramp_discrete_floor; + mode_max = ramp_discrete_ceil; + } else { + mode_min = ramp_smooth_floor; + mode_max = ramp_smooth_ceil; + } + if (memorized_level < mode_min) memorized_level = mode_min; + if (memorized_level > mode_max) memorized_level = mode_max; + //save_config(); + set_level(0); + delay_4ms(20/4); + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // 4 clicks: configure this ramp mode + else if (event == EV_4clicks) { + // TODO: implement this + return MISCHIEF_MANAGED; + } + // hold: change brightness (brighter) + else if (event == EV_click1_hold) { + // ramp slower in discrete mode + if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { + return MISCHIEF_MANAGED; + } + // TODO: make it ramp down instead, if already at max? + if (actual_level + ramp_step_size < mode_max) + memorized_level = actual_level + ramp_step_size; + else memorized_level = mode_max; + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == mode_max))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // click, hold: change brightness (dimmer) + else if (event == EV_click2_hold) { + // ramp slower in discrete mode + if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { + return MISCHIEF_MANAGED; + } + // TODO: make it ramp up instead, if already at min? + // TODO: test what happens if I go to moon, switch to discrete mode + // (with min configured for like 10), then try to ramp down + if (actual_level - mode_min > ramp_step_size) + memorized_level = (actual_level-ramp_step_size); + else + memorized_level = mode_min; + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == mode_min))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + #ifdef USE_THERMAL_REGULATION + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return MISCHIEF_MANAGED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return MISCHIEF_MANAGED; + } + #endif + return EVENT_NOT_HANDLED; +} + + +uint8_t strobe_state(EventPtr event, uint16_t arg) { + if (event == EV_enter_state) { + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: toggle party strobe vs tactical strobe + else if (event == EV_2clicks) { + strobe_type ^= 1; + return MISCHIEF_MANAGED; + } + // 3 clicks: go back to regular modes + else if (event == EV_3clicks) { + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } + // hold: change speed (go faster) + else if (event == EV_click1_hold) { + if ((arg & 1) == 0) { + if (strobe_delay > 8) strobe_delay --; + } + return MISCHIEF_MANAGED; + } + // click, hold: change speed (go slower) + else if (event == EV_click2_hold) { + if ((arg & 1) == 0) { + if (strobe_delay < 255) strobe_delay ++; + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: tempcheck mode + else if (event == EV_2clicks) { + set_state(tempcheck_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t tempcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} +#endif + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + // conserve power while locked out + // (allow staying awake long enough to exit, but otherwise + // be persistent about going back to sleep every few seconds + // even if the user keeps pressing the button) + if (event == EV_tick) { + static uint8_t ticks_spent_awake = 0; + ticks_spent_awake ++; + if (ticks_spent_awake > 180) { + ticks_spent_awake = 0; + go_to_standby = 1; + } + return MISCHIEF_MANAGED; + } + // 5 clicks: exit + else if (event == EV_5clicks) { + blink_confirm(2); + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +void blink_confirm(uint8_t num) { + for (; num>0; num--) { + set_level(MAX_LEVEL/4); + delay_4ms(10/4); + set_level(0); + delay_4ms(100/4); + } +} + + +void low_voltage() { + // "step down" from strobe to something low + if (current_state == strobe_state) { + set_state(steady_state, RAMP_SIZE/6); + } + // in normal mode, step down by half or turn off + else if (current_state == steady_state) { + if (actual_level > 1) { + set_level(actual_level >> 1); + } + else { + set_state(off_state, 0); + } + } + // all other modes, just turn off when voltage is low + else { + set_state(off_state, 0); + } +} + + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + push_state(off_state, 0); +} + + +void loop() { + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + + if (current_state == strobe_state) { + set_level(MAX_LEVEL); + if (strobe_type == 0) { // party strobe + if (strobe_delay < 30) delay_zero(); + else delay_ms(1); + } else { //tactical strobe + nice_delay_ms(strobe_delay >> 1); + } + set_level(0); + nice_delay_ms(strobe_delay); + } + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + battcheck(); + } + else if (current_state == tempcheck_state) { + blink_num(projected_temperature>>2); + nice_delay_ms(1000); + } + #endif +} diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt new file mode 100644 index 0000000..b046034 --- /dev/null +++ b/spaghetti-monster/anduril.txt @@ -0,0 +1,67 @@ +From off: + * 1 click: memorized level + * Hold: Lowest level then ramp up + * 2 clicks: turbo + * Click, hold: highest level then ramp down + * 3 clicks: strobe mode + * 4 clicks: battcheck mode + * 5 clicks: lock-out + +In steady mode: + * 1 click: off + * Hold: ramp up + * Click, hold: ramp down + * 2 clicks: to/from turbo + * 3 clicks: toggle smooth vs discrete ramping + - 4 clicks: configure current ramp + +Smooth ramp config mode: + - Setting 1: memory on/off + - Setting 2: low end + - Setting 3: high end + +Discrete ramp config mode: + - Setting 1: memory on/off + - Setting 2: low end + - Setting 3: high end + - Setting 4: number of levels + +Strobe mode: + * 1 click: off + * Hold: change speed (faster) + * Click, hold: change speed (slower) + + 2 clicks: next strobe mode + (party strobe, tactical strobe, random/police strobe?, bike flasher) + +Bike flasher: + - 1 click: off + - 2 clicks: party strobe + - Hold: brighter + - Click, hold: dimmer + +Battcheck mode: + * 1 click: off + * 2 clicks: tempcheck mode + +Tempcheck mode: + * 1 click: off + - 2 clicks: beacon mode + - Hold: thermal calibration + +Beacon mode: + - 1 click: off + - 2 clicks: battcheck mode + - Hold: brighter + - Click, hold: dimmer + - 3 clicks: configure time between pulses + +TODO: + - save settings in eeprom + - make memory toggle-able (discrete ramping) + - make lowest level configurable (smooth ramping) + - make highest level configurable (smooth ramping) + - make memory toggle-able (discrete ramping) + - make lowest level configurable (discrete ramping) + - make highest level configurable (discrete ramping) + - a way to blink out the firmware version? + - a way to configure indicator LED behavior? -- cgit v1.2.3 From 6b77dc4fd594b174a578654ab1b3861953f47eb5 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 17:54:56 -0600 Subject: Added TICKS_PER_SECOND because it's handy to avoid hardcoding the WDT speed in UI code. --- spaghetti-monster/fsm-wdt.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-wdt.h b/spaghetti-monster/fsm-wdt.h index d7c64f5..74851af 100644 --- a/spaghetti-monster/fsm-wdt.h +++ b/spaghetti-monster/fsm-wdt.h @@ -20,6 +20,8 @@ #ifndef FSM_WDT_H #define FSM_WDT_H +#define TICKS_PER_SECOND 62 + void WDT_on(); inline void WDT_off(); -- cgit v1.2.3 From 827c580b82d6bd6652ca9a1a839bc8bd0b6ece91 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 17:58:06 -0600 Subject: Made discrete ramping to work significantly better. Added momentary mode. Can only be exited by disconnecting power. Rearranged some mappings from off to other states. --- spaghetti-monster/anduril.c | 99 ++++++++++++++++++++++++++++++++++--------- spaghetti-monster/anduril.txt | 12 ++++-- 2 files changed, 89 insertions(+), 22 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 96e45db..48cd3d9 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -29,7 +29,7 @@ #define USE_BATTCHECK #define BATTCHECK_VpT #define RAMP_LENGTH 150 -#define MAX_CLICKS 5 +#define MAX_CLICKS 6 #include "spaghetti-monster.h" // FSM states @@ -41,6 +41,7 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg); uint8_t tempcheck_state(EventPtr event, uint16_t arg); #endif uint8_t lockout_state(EventPtr event, uint16_t arg); +uint8_t momentary_state(EventPtr event, uint16_t arg); void blink_confirm(uint8_t num); @@ -51,9 +52,14 @@ uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete uint8_t ramp_smooth_floor = 1; uint8_t ramp_smooth_ceil = MAX_LEVEL; uint8_t ramp_discrete_floor = 20; -uint8_t ramp_discrete_ceil = MAX_LEVEL-50; +uint8_t ramp_discrete_ceil = MAX_LEVEL - 50; uint8_t ramp_discrete_steps = 7; -uint8_t ramp_discrete_step_size; +uint8_t ramp_discrete_step_size; // don't set this + +// calculate the nearest ramp level which would be valid at the moment +// (is a no-op for smooth ramp, but limits discrete ramp to only the +// correct levels for the user's config) +uint8_t nearest_level(uint8_t target); #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down @@ -109,24 +115,30 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } return MISCHIEF_MANAGED; } - // 3 clicks: strobe mode - else if (event == EV_3clicks) { - set_state(strobe_state, 0); - return MISCHIEF_MANAGED; - } #ifdef USE_BATTCHECK - // 4 clicks: battcheck mode - else if (event == EV_4clicks) { + // 3 clicks: battcheck mode + else if (event == EV_3clicks) { set_state(battcheck_state, 0); return MISCHIEF_MANAGED; } #endif - // 5 clicks: soft lockout - else if (event == EV_5clicks) { + // 4 clicks: soft lockout + else if (event == EV_4clicks) { blink_confirm(5); set_state(lockout_state, 0); return MISCHIEF_MANAGED; } + // 5 clicks: strobe mode + else if (event == EV_5clicks) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } + // 6 clicks: momentary mode + else if (event == EV_6clicks) { + blink_confirm(1); + set_state(momentary_state, 0); + return MISCHIEF_MANAGED; + } // hold: go to lowest level else if (event == EV_click1_hold) { // don't start ramping immediately; @@ -169,9 +181,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (ramp_style) { mode_min = ramp_discrete_floor; mode_max = ramp_discrete_ceil; - // TODO: Fixed-point for better precision - //ramp_step_size = ((1 + mode_max - mode_min)<<4) / (ramp_discrete_steps-1); - ramp_step_size = (1 + mode_max - mode_min) / (ramp_discrete_steps-1); + ramp_step_size = ramp_discrete_step_size; } // turn LED on when we first enter the mode @@ -183,7 +193,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION target_level = arg; #endif - set_level(arg); + set_level(nearest_level(arg)); return MISCHIEF_MANAGED; } // 1 click: off @@ -224,7 +234,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { //save_config(); set_level(0); delay_4ms(20/4); - set_level(memorized_level); + set_level(nearest_level(memorized_level)); return MISCHIEF_MANAGED; } // 4 clicks: configure this ramp mode @@ -242,6 +252,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (actual_level + ramp_step_size < mode_max) memorized_level = actual_level + ramp_step_size; else memorized_level = mode_max; + memorized_level = nearest_level(memorized_level); #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -268,6 +279,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { memorized_level = (actual_level-ramp_step_size); else memorized_level = mode_min; + memorized_level = nearest_level(memorized_level); #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -383,8 +395,8 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { } return MISCHIEF_MANAGED; } - // 5 clicks: exit - else if (event == EV_5clicks) { + // 4 clicks: exit + else if (event == EV_4clicks) { blink_confirm(2); set_state(off_state, 0); return MISCHIEF_MANAGED; @@ -393,6 +405,55 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { } +uint8_t momentary_state(EventPtr event, uint16_t arg) { + if (event == EV_click1_press) { + set_level(memorized_level); + empty_event_sequence(); // don't attempt to parse multiple clicks + return MISCHIEF_MANAGED; + } + + else if (event == EV_release) { + set_level(0); + empty_event_sequence(); // don't attempt to parse multiple clicks + //go_to_standby = 1; // sleep while light is off + return MISCHIEF_MANAGED; + } + + // Sleep, dammit! (but wait a few seconds first) + // (because standby mode uses such little power that it can interfere + // with exiting via tailcap loosen+tighten unless you leave power + // disconnected for several seconds, so we want to be awake when that + // happens to speed up the process) + else if ((event == EV_tick) && (actual_level == 0)) { + if (arg > TICKS_PER_SECOND*15) { // sleep after 15 seconds + go_to_standby = 1; // sleep while light is off + } + return MISCHIEF_MANAGED; + } + + return EVENT_NOT_HANDLED; +} + + +uint8_t nearest_level(uint8_t target) { + if (! ramp_style) return target; + if (target < ramp_discrete_floor) return ramp_discrete_floor; + + uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor; + ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1); + uint8_t this_level = ramp_discrete_floor; + + for(uint8_t i=0; i>1)) + return this_level; + } + return this_level; +} + + void blink_confirm(uint8_t num) { for (; num>0; num--) { set_level(MAX_LEVEL/4); diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index b046034..c391691 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -3,9 +3,10 @@ From off: * Hold: Lowest level then ramp up * 2 clicks: turbo * Click, hold: highest level then ramp down - * 3 clicks: strobe mode - * 4 clicks: battcheck mode - * 5 clicks: lock-out + * 3 clicks: battcheck mode + * 4 clicks: lock-out + * 5 clicks: strobe mode + + 6 clicks: momentary mode (disconnect power to exit) In steady mode: * 1 click: off @@ -55,6 +56,11 @@ Beacon mode: - Click, hold: dimmer - 3 clicks: configure time between pulses +Momentary mode: + * Press button: Light on (at memorized level). + * Release button: Light off. + * To exit, disconnect power. (loosen/tighten the tailcap) + TODO: - save settings in eeprom - make memory toggle-able (discrete ramping) -- cgit v1.2.3 From 58c7c6d1f490d6a8934e50130d791e692866b07c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 18:22:28 -0600 Subject: Reduced ROM size, simplified logic, made sure discrete ramp won't overflow outside uint8_t range. --- spaghetti-monster/anduril.c | 80 +++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 53 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 48cd3d9..94b3139 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -53,13 +53,13 @@ uint8_t ramp_smooth_floor = 1; uint8_t ramp_smooth_ceil = MAX_LEVEL; uint8_t ramp_discrete_floor = 20; uint8_t ramp_discrete_ceil = MAX_LEVEL - 50; -uint8_t ramp_discrete_steps = 7; +uint8_t ramp_discrete_steps = 3; uint8_t ramp_discrete_step_size; // don't set this // calculate the nearest ramp level which would be valid at the moment // (is a no-op for smooth ramp, but limits discrete ramp to only the // correct levels for the user's config) -uint8_t nearest_level(uint8_t target); +uint8_t nearest_level(int16_t target); #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down @@ -84,16 +84,12 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // hold (initially): go to lowest level, but allow abort for regular click else if (event == EV_click1_press) { - if (ramp_style == 0) { - set_level(ramp_smooth_floor); - } else { - set_level(ramp_discrete_floor); - } + set_level(nearest_level(1)); return MISCHIEF_MANAGED; } // 1 click (before timeout): go to memorized level, but allow abort for double click else if (event == EV_click1_release) { - set_level(memorized_level); + set_level(nearest_level(memorized_level)); return MISCHIEF_MANAGED; } // 1 click: regular mode @@ -108,11 +104,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // 2 clicks: highest mode else if (event == EV_2clicks) { - if (ramp_style == 0) { - set_state(steady_state, ramp_smooth_ceil); - } else { - set_state(steady_state, ramp_discrete_ceil); - } + set_level(nearest_level(MAX_LEVEL)); return MISCHIEF_MANAGED; } #ifdef USE_BATTCHECK @@ -144,30 +136,18 @@ uint8_t off_state(EventPtr event, uint16_t arg) { // don't start ramping immediately; // give the user time to release at moon level if (arg >= HOLD_TIMEOUT) { - if (ramp_style == 0) { - set_state(steady_state, ramp_smooth_floor); - } else { - set_state(steady_state, ramp_discrete_floor); - } + set_state(steady_state, 1); } return MISCHIEF_MANAGED; } // hold, release quickly: go to lowest level else if (event == EV_click1_hold_release) { - if (ramp_style == 0) { - set_state(steady_state, ramp_smooth_floor); - } else { - set_state(steady_state, ramp_discrete_floor); - } + set_state(steady_state, 1); return MISCHIEF_MANAGED; } // click, hold: go to highest level (for ramping down) else if (event == EV_click2_hold) { - if (ramp_style == 0) { - set_state(steady_state, ramp_smooth_ceil); - } else { - set_state(steady_state, ramp_discrete_ceil); - } + set_state(steady_state, MAX_LEVEL); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -222,19 +202,11 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // 3 clicks: toggle smooth vs discrete ramping else if (event == EV_3clicks) { ramp_style ^= 1; - if (ramp_style) { - mode_min = ramp_discrete_floor; - mode_max = ramp_discrete_ceil; - } else { - mode_min = ramp_smooth_floor; - mode_max = ramp_smooth_ceil; - } - if (memorized_level < mode_min) memorized_level = mode_min; - if (memorized_level > mode_max) memorized_level = mode_max; + memorized_level = nearest_level(memorized_level); //save_config(); set_level(0); delay_4ms(20/4); - set_level(nearest_level(memorized_level)); + set_level(memorized_level); return MISCHIEF_MANAGED; } // 4 clicks: configure this ramp mode @@ -248,11 +220,8 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { return MISCHIEF_MANAGED; } - // TODO: make it ramp down instead, if already at max? - if (actual_level + ramp_step_size < mode_max) - memorized_level = actual_level + ramp_step_size; - else memorized_level = mode_max; - memorized_level = nearest_level(memorized_level); + // TODO? make it ramp down instead, if already at max? + memorized_level = nearest_level((int16_t)actual_level + ramp_step_size); #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -272,14 +241,8 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { return MISCHIEF_MANAGED; } - // TODO: make it ramp up instead, if already at min? - // TODO: test what happens if I go to moon, switch to discrete mode - // (with min configured for like 10), then try to ramp down - if (actual_level - mode_min > ramp_step_size) - memorized_level = (actual_level-ramp_step_size); - else - memorized_level = mode_min; - memorized_level = nearest_level(memorized_level); + // TODO? make it ramp up instead, if already at min? + memorized_level = nearest_level((int16_t)actual_level - ramp_step_size); #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -435,9 +398,20 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { } -uint8_t nearest_level(uint8_t target) { +uint8_t nearest_level(int16_t target) { + // bounds check + // using int16_t here saves us a bunch of logic elsewhere, + // by allowing us to correct for numbers < 0 or > 255 in one central place + uint8_t mode_min = ramp_smooth_floor; + uint8_t mode_max = ramp_smooth_ceil; + if (ramp_style) { + mode_min = ramp_discrete_floor; + mode_max = ramp_discrete_ceil; + } + if (target < mode_min) return mode_min; + if (target > mode_max) return mode_max; + // the rest isn't relevant for smooth ramping if (! ramp_style) return target; - if (target < ramp_discrete_floor) return ramp_discrete_floor; uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor; ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1); -- cgit v1.2.3 From dce497bf15799133bf336ab46c3e39d7b0d92839 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 19:40:40 -0600 Subject: Ramp config mode actually works now... Added EV_reenter_state event to indicate an obscuring state was popped off the stack and the underlying one is now on top again. --- spaghetti-monster/anduril.c | 181 +++++++++++++++++++++++++++++++++++++++-- spaghetti-monster/fsm-events.h | 22 +++-- spaghetti-monster/fsm-states.c | 13 +-- spaghetti-monster/fsm-states.h | 3 +- 4 files changed, 196 insertions(+), 23 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 94b3139..0123ce0 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -42,18 +42,23 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg); #endif uint8_t lockout_state(EventPtr event, uint16_t arg); uint8_t momentary_state(EventPtr event, uint16_t arg); +uint8_t ramp_config_mode(EventPtr event, uint16_t arg); +uint8_t number_entry_state(EventPtr event, uint16_t arg); + +// return value from number_entry_state() +volatile uint8_t number_entry_value; void blink_confirm(uint8_t num); // brightness control uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping -uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete -uint8_t ramp_smooth_floor = 1; -uint8_t ramp_smooth_ceil = MAX_LEVEL; -uint8_t ramp_discrete_floor = 20; -uint8_t ramp_discrete_ceil = MAX_LEVEL - 50; -uint8_t ramp_discrete_steps = 3; +volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete +volatile uint8_t ramp_smooth_floor = 1; +volatile uint8_t ramp_smooth_ceil = MAX_LEVEL; +volatile uint8_t ramp_discrete_floor = 20; +volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 50; +volatile uint8_t ramp_discrete_steps = 3; uint8_t ramp_discrete_step_size; // don't set this // calculate the nearest ramp level which would be valid at the moment @@ -211,7 +216,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 4 clicks: configure this ramp mode else if (event == EV_4clicks) { - // TODO: implement this + set_state(ramp_config_mode, 0); return MISCHIEF_MANAGED; } // hold: change brightness (brighter) @@ -398,6 +403,168 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { } +uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { + //static uint8_t new_floor; + //static uint8_t new_ceil; + //static uint8_t new_num_steps; + static uint8_t config_step; + static uint8_t num_config_steps; + if (event == EV_enter_state) { + config_step = 0; + if (ramp_style) { + //new_floor = ramp_discrete_floor; + //new_ceil = ramp_discrete_ceil; + //new_num_steps = ramp_discrete_steps; + num_config_steps = 3; + } + else { + //new_floor = ramp_smooth_floor; + //new_ceil = ramp_smooth_ceil; + num_config_steps = 2; + } + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (config_step < num_config_steps) { + //config_step ++; + push_state(number_entry_state, config_step + 1); + /* + switch (config_step) { + // set the floor value + case 0: + push_state(number_entry_state, 1); + break; + // set the ceiling value + case 1: + push_state(number_entry_state, 2); + break; + // set the number of steps (discrete ramp only) + case 2: + push_state(number_entry_state, 3); + break; + } + */ + } + else { + // TODO: save_config(); + // TODO: blink out some sort of success pattern + // return to steady mode + set_state(steady_state, memorized_level); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + #if 0 + // FIXME? this is a kludge which relies on the vars being consecutive + // in RAM and in the same order as declared + // ... and it doesn't work; it seems they're not consecutive :( + volatile uint8_t *dest; + if (ramp_style) dest = (&ramp_discrete_floor) + config_step; + else dest = (&ramp_smooth_floor) + config_step; + if (number_entry_value) + *dest = number_entry_value; + #else + switch (config_step) { + case 0: + if (number_entry_value) { + if (ramp_style) ramp_discrete_floor = number_entry_value; + else ramp_smooth_floor = number_entry_value; + } + break; + case 1: + if (number_entry_value) { + if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value; + else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value; + } + break; + case 2: + if (number_entry_value) + ramp_discrete_steps = number_entry_value; + break; + } + #endif + config_step ++; + /* handled at next EV_tick + if (config_step > num_config_steps) { + // TODO: save_config(); + // exit config mode + set_state(steady_state, memorized_level); + } + */ + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t number_entry_state(EventPtr event, uint16_t arg) { + static uint8_t value; + static uint8_t blinks_left; + static uint8_t init_completed; + static uint16_t wait_ticks; + if (event == EV_enter_state) { + value = 0; + blinks_left = arg; + init_completed = 0; + wait_ticks = 0; + // TODO: blink out the 'arg' to show which option this is + return MISCHIEF_MANAGED; + } + else if (event == EV_tick) { + // blink out the option number + if (! init_completed) { + if (blinks_left) { + if ((arg & 31) == 10) { + set_level(RAMP_SIZE/4); + } + else if ((arg & 31) == 20) { + set_level(0); + } + else if ((arg & 31) == 31) { + blinks_left --; + } + } + else { + init_completed = 1; + wait_ticks = 0; + } + } + else { // buzz while waiting for a number to be entered + wait_ticks ++; + // buzz for N seconds after last event + if ((arg & 3) == 0) { + set_level(RAMP_SIZE/6); + } + else if ((arg & 3) == 2) { + set_level(RAMP_SIZE/8); + } + // time out after 4 seconds + if (wait_ticks > TICKS_PER_SECOND*4) { + number_entry_value = value; + set_level(0); + pop_state(); + } + } + return MISCHIEF_MANAGED; + } + // count clicks + else if (event == EV_click1_release) { + value ++; + //number_entry_value = value; + wait_ticks = 0; + empty_event_sequence(); + // flash briefly + set_level(RAMP_SIZE/2); + delay_4ms(8/2); + set_level(0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + uint8_t nearest_level(int16_t target) { // bounds check // using int16_t here saves us a bunch of logic elsewhere, diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 114ccc4..e3edc77 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -51,17 +51,18 @@ static volatile uint16_t ticks_since_last_event = 0; #define A_ENTER_STATE 1 #define A_LEAVE_STATE 2 -#define A_TICK 3 -#define A_PRESS 4 -#define A_HOLD 5 -#define A_RELEASE 6 -#define A_RELEASE_TIMEOUT 7 +#define A_REENTER_STATE 3 +#define A_TICK 4 +#define A_PRESS 5 +#define A_HOLD 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 8 -#define A_UNDERHEATING 9 +#define A_OVERHEATING 9 +#define A_UNDERHEATING 10 // TODO: add events for low voltage conditions -#define A_VOLTAGE_LOW 10 -//#define A_VOLTAGE_CRITICAL 11 +#define A_VOLTAGE_LOW 11 +//#define A_VOLTAGE_CRITICAL 12 #define A_DEBUG 255 // test event for debugging // Event types @@ -74,6 +75,9 @@ Event EV_enter_state[] = { Event EV_leave_state[] = { A_LEAVE_STATE, 0 } ; +Event EV_reenter_state[] = { + A_REENTER_STATE, + 0 } ; Event EV_tick[] = { A_TICK, 0 } ; diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c index ec24dc8..2939475 100644 --- a/spaghetti-monster/fsm-states.c +++ b/spaghetti-monster/fsm-states.c @@ -31,13 +31,14 @@ // 255: error (not sure what this would even mean though, or what difference it would make) // TODO: function to call stacked callbacks until one returns "handled" -void _set_state(StatePtr new_state, uint16_t arg) { +void _set_state(StatePtr new_state, uint16_t arg, + EventPtr exit_event, EventPtr enter_event) { // call old state-exit hook (don't use stack) - if (current_state != NULL) current_state(EV_leave_state, arg); + if (current_state != NULL) current_state(exit_event, 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, arg); + if (new_state != NULL) current_state(enter_event, arg); } int8_t push_state(StatePtr new_state, uint16_t arg) { @@ -46,7 +47,8 @@ int8_t push_state(StatePtr new_state, uint16_t arg) { // new hook for non-exit recursion into child? state_stack[state_stack_len] = new_state; state_stack_len ++; - _set_state(new_state, arg); + // FIXME: use EV_stacked_state? + _set_state(new_state, arg, EV_leave_state, EV_enter_state); return state_stack_len; } else { // TODO: um... how is a flashlight supposed to handle a recursion depth error? @@ -66,8 +68,7 @@ StatePtr pop_state() { new_state = state_stack[state_stack_len-1]; } // FIXME: what should 'arg' be? - // FIXME: do we need a EV_reenter_state? - _set_state(new_state, 0); + _set_state(new_state, 0, EV_leave_state, EV_reenter_state); return old_state; } diff --git a/spaghetti-monster/fsm-states.h b/spaghetti-monster/fsm-states.h index 0daf915..f92abba 100644 --- a/spaghetti-monster/fsm-states.h +++ b/spaghetti-monster/fsm-states.h @@ -35,7 +35,8 @@ volatile StatePtr current_state; StatePtr state_stack[STATE_STACK_SIZE]; uint8_t state_stack_len = 0; -void _set_state(StatePtr new_state, uint16_t arg); +void _set_state(StatePtr new_state, uint16_t arg, + EventPtr exit_event, EventPtr enter_event); int8_t push_state(StatePtr new_state, uint16_t arg); StatePtr pop_state(); uint8_t set_state(StatePtr new_state, uint16_t arg); -- cgit v1.2.3 From 898061b52a7aa75269327b65e41ea65054bd5b6c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 20:03:09 -0600 Subject: Made ramp config mode significantly easier to use. Added delays around each step. Fixed wrong number of blinks for each option. --- spaghetti-monster/anduril.c | 89 +++++++++++++++++++++---------------------- spaghetti-monster/anduril.txt | 20 ++++------ 2 files changed, 50 insertions(+), 59 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 0123ce0..0b4241d 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -404,47 +404,23 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { - //static uint8_t new_floor; - //static uint8_t new_ceil; - //static uint8_t new_num_steps; static uint8_t config_step; static uint8_t num_config_steps; if (event == EV_enter_state) { config_step = 0; if (ramp_style) { - //new_floor = ramp_discrete_floor; - //new_ceil = ramp_discrete_ceil; - //new_num_steps = ramp_discrete_steps; num_config_steps = 3; } else { - //new_floor = ramp_smooth_floor; - //new_ceil = ramp_smooth_ceil; num_config_steps = 2; } + set_level(0); return MISCHIEF_MANAGED; } // advance forward through config steps else if (event == EV_tick) { if (config_step < num_config_steps) { - //config_step ++; push_state(number_entry_state, config_step + 1); - /* - switch (config_step) { - // set the floor value - case 0: - push_state(number_entry_state, 1); - break; - // set the ceiling value - case 1: - push_state(number_entry_state, 2); - break; - // set the number of steps (discrete ramp only) - case 2: - push_state(number_entry_state, 3); - break; - } - */ } else { // TODO: save_config(); @@ -502,63 +478,84 @@ uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { uint8_t number_entry_state(EventPtr event, uint16_t arg) { static uint8_t value; static uint8_t blinks_left; - static uint8_t init_completed; + static uint8_t entry_step; static uint16_t wait_ticks; if (event == EV_enter_state) { value = 0; blinks_left = arg; - init_completed = 0; + entry_step = 0; wait_ticks = 0; // TODO: blink out the 'arg' to show which option this is return MISCHIEF_MANAGED; } + // advance through the process: + // 0: wait a moment + // 1: blink out the 'arg' value + // 2: wait a moment + // 3: "buzz" while counting clicks + // 4: save and exit else if (event == EV_tick) { + // wait a moment + if ((entry_step == 0) || (entry_step == 2)) { + if (wait_ticks < TICKS_PER_SECOND/2) + wait_ticks ++; + else { + entry_step ++; + wait_ticks = 0; + } + } // blink out the option number - if (! init_completed) { + else if (entry_step == 1) { if (blinks_left) { - if ((arg & 31) == 10) { + if ((wait_ticks & 31) == 10) { set_level(RAMP_SIZE/4); } - else if ((arg & 31) == 20) { + else if ((wait_ticks & 31) == 20) { set_level(0); } - else if ((arg & 31) == 31) { + else if ((wait_ticks & 31) == 31) { blinks_left --; } + wait_ticks ++; } else { - init_completed = 1; + entry_step ++; wait_ticks = 0; } } - else { // buzz while waiting for a number to be entered + else if (entry_step == 3) { // buzz while waiting for a number to be entered wait_ticks ++; // buzz for N seconds after last event - if ((arg & 3) == 0) { + if ((wait_ticks & 3) == 0) { set_level(RAMP_SIZE/6); } - else if ((arg & 3) == 2) { + else if ((wait_ticks & 3) == 2) { set_level(RAMP_SIZE/8); } - // time out after 4 seconds - if (wait_ticks > TICKS_PER_SECOND*4) { - number_entry_value = value; + // time out after 3 seconds + if (wait_ticks > TICKS_PER_SECOND*3) { + //number_entry_value = value; set_level(0); - pop_state(); + entry_step ++; } } + else if (entry_step == 4) { + number_entry_value = value; + pop_state(); + } return MISCHIEF_MANAGED; } // count clicks else if (event == EV_click1_release) { - value ++; - //number_entry_value = value; - wait_ticks = 0; empty_event_sequence(); - // flash briefly - set_level(RAMP_SIZE/2); - delay_4ms(8/2); - set_level(0); + if (entry_step == 3) { // only count during the "buzz" + value ++; + wait_ticks = 0; + // flash briefly + set_level(RAMP_SIZE/2); + delay_4ms(8/2); + set_level(0); + } return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index c391691..97f13fa 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -6,7 +6,7 @@ From off: * 3 clicks: battcheck mode * 4 clicks: lock-out * 5 clicks: strobe mode - + 6 clicks: momentary mode (disconnect power to exit) + * 6 clicks: momentary mode (disconnect power to exit) In steady mode: * 1 click: off @@ -14,18 +14,18 @@ In steady mode: * Click, hold: ramp down * 2 clicks: to/from turbo * 3 clicks: toggle smooth vs discrete ramping - - 4 clicks: configure current ramp + * 4 clicks: configure current ramp Smooth ramp config mode: - Setting 1: memory on/off - - Setting 2: low end - - Setting 3: high end + * Setting 2: low end + * Setting 3: high end Discrete ramp config mode: - Setting 1: memory on/off - - Setting 2: low end - - Setting 3: high end - - Setting 4: number of levels + * Setting 2: low end + * Setting 3: high end + * Setting 4: number of levels Strobe mode: * 1 click: off @@ -63,11 +63,5 @@ Momentary mode: TODO: - save settings in eeprom - - make memory toggle-able (discrete ramping) - - make lowest level configurable (smooth ramping) - - make highest level configurable (smooth ramping) - - make memory toggle-able (discrete ramping) - - make lowest level configurable (discrete ramping) - - make highest level configurable (discrete ramping) - a way to blink out the firmware version? - a way to configure indicator LED behavior? -- cgit v1.2.3 From 916c212ce6c8abdcedf11cbea8c1ab1d58609969 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 20:25:00 -0600 Subject: Now remembers ramp and strobe settings after battery change. Also, set new defaults for ramping: - ~50% power maximum - 0.3 lm min for smooth ramping, 10 lm min for discrete ramping --- spaghetti-monster/anduril.c | 65 +++++++++++++++++++++++++++++++------------ spaghetti-monster/anduril.txt | 2 +- 2 files changed, 48 insertions(+), 19 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 0b4241d..fc6805c 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -30,6 +30,8 @@ #define BATTCHECK_VpT #define RAMP_LENGTH 150 #define MAX_CLICKS 6 +#define USE_EEPROM +#define EEPROM_BYTES 8 #include "spaghetti-monster.h" // FSM states @@ -50,15 +52,18 @@ volatile uint8_t number_entry_value; void blink_confirm(uint8_t num); +void load_config(); +void save_config(); + // brightness control uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete volatile uint8_t ramp_smooth_floor = 1; -volatile uint8_t ramp_smooth_ceil = MAX_LEVEL; -volatile uint8_t ramp_discrete_floor = 20; -volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 50; -volatile uint8_t ramp_discrete_steps = 3; +volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30; +volatile uint8_t ramp_discrete_floor = 15; +volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30; +volatile uint8_t ramp_discrete_steps = 5; uint8_t ramp_discrete_step_size; // don't set this // calculate the nearest ramp level which would be valid at the moment @@ -208,7 +213,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { else if (event == EV_3clicks) { ramp_style ^= 1; memorized_level = nearest_level(memorized_level); - //save_config(); + save_config(); set_level(0); delay_4ms(20/4); set_level(memorized_level); @@ -299,11 +304,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { // 2 clicks: toggle party strobe vs tactical strobe else if (event == EV_2clicks) { strobe_type ^= 1; - return MISCHIEF_MANAGED; - } - // 3 clicks: go back to regular modes - else if (event == EV_3clicks) { - set_state(steady_state, memorized_level); + save_config(); return MISCHIEF_MANAGED; } // hold: change speed (go faster) @@ -320,6 +321,12 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { } return MISCHIEF_MANAGED; } + // release hold: save new strobe speed + else if ((event == EV_click1_hold_release) + || (event == EV_click2_hold_release)) { + save_config(); + return MISCHIEF_MANAGED; + } return EVENT_NOT_HANDLED; } @@ -423,7 +430,7 @@ uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { push_state(number_entry_state, config_step + 1); } else { - // TODO: save_config(); + save_config(); // TODO: blink out some sort of success pattern // return to steady mode set_state(steady_state, memorized_level); @@ -462,13 +469,6 @@ uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { } #endif config_step ++; - /* handled at next EV_tick - if (config_step > num_config_steps) { - // TODO: save_config(); - // exit config mode - set_state(steady_state, memorized_level); - } - */ return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -602,6 +602,33 @@ void blink_confirm(uint8_t num) { } +void load_config() { + if (load_eeprom()) { + ramp_style = eeprom[0]; + ramp_smooth_floor = eeprom[1]; + ramp_smooth_ceil = eeprom[2]; + ramp_discrete_floor = eeprom[3]; + ramp_discrete_ceil = eeprom[4]; + ramp_discrete_steps = eeprom[5]; + strobe_type = eeprom[6]; + strobe_delay = eeprom[7]; + } +} + +void save_config() { + eeprom[0] = ramp_style; + eeprom[1] = ramp_smooth_floor; + eeprom[2] = ramp_smooth_ceil; + eeprom[3] = ramp_discrete_floor; + eeprom[4] = ramp_discrete_ceil; + eeprom[5] = ramp_discrete_steps; + eeprom[6] = strobe_type; + eeprom[7] = strobe_delay; + + save_eeprom(); +} + + void low_voltage() { // "step down" from strobe to something low if (current_state == strobe_state) { @@ -628,6 +655,8 @@ void setup() { delay_4ms(3); set_level(0); + load_config(); + push_state(off_state, 0); } diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index 97f13fa..28d27f9 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -62,6 +62,6 @@ Momentary mode: * To exit, disconnect power. (loosen/tighten the tailcap) TODO: - - save settings in eeprom + * save settings in eeprom - a way to blink out the firmware version? - a way to configure indicator LED behavior? -- cgit v1.2.3 From a1dd029c3fb0782c67a5fec372e5cfb588d7e216 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 20:34:02 -0600 Subject: Adjusted default 2-channel 150-step ramp to start at 1/255. (this makes the newly-configurable moon mode able to work on pretty much any hardware) --- spaghetti-monster/fsm-ramping.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index a25ff8b..b4054bf 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -54,7 +54,12 @@ volatile uint8_t actual_level = 0; #define MAX_1x7135 33 #elif RAMP_LENGTH == 150 // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 - PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,30,32,34,37,40,43,46,49,52,56,59,63,67,71,76,80,85,90,95,100,106,112,118,124,130,137,144,151,158,166,173,181,190,198,207,216,225,235,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + //PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,30,32,34,37,40,43,46,49,52,56,59,63,67,71,76,80,85,90,95,100,106,112,118,124,130,137,144,151,158,166,173,181,190,198,207,216,225,235,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + //PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; + // ../../bin/level_calc.py 1 65 7135 1 0.8 150 + // ... mixed with this: + // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,11,12,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; #define MAX_1x7135 65 #endif -- cgit v1.2.3 From 90c983c8149c437c7e888144f21bae2811faa3b9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 21:32:39 -0600 Subject: Added bike flasher. --- spaghetti-monster/anduril.c | 71 ++++++++++++++++++++++++++++++++----------- spaghetti-monster/anduril.txt | 16 +++++----- 2 files changed, 63 insertions(+), 24 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index fc6805c..daafa90 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -31,13 +31,14 @@ #define RAMP_LENGTH 150 #define MAX_CLICKS 6 #define USE_EEPROM -#define EEPROM_BYTES 8 +#define EEPROM_BYTES 9 #include "spaghetti-monster.h" // FSM states uint8_t off_state(EventPtr event, uint16_t arg); uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t strobe_state(EventPtr event, uint16_t arg); +#define NUM_STROBES 3 #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); uint8_t tempcheck_state(EventPtr event, uint16_t arg); @@ -78,7 +79,8 @@ uint8_t target_level = 0; // strobe timing volatile uint8_t strobe_delay = 67; -volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe +volatile uint8_t strobe_type = 2; // 0 == party strobe, 1 == tactical strobe, 2 == bike flasher +volatile uint8_t bike_flasher_brightness = MAX_1x7135; // deferred "off" so we won't suspend in a weird state volatile uint8_t go_to_standby = 0; @@ -301,27 +303,46 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return MISCHIEF_MANAGED; } - // 2 clicks: toggle party strobe vs tactical strobe + // 2 clicks: rotate through strobe/flasher modes else if (event == EV_2clicks) { - strobe_type ^= 1; + strobe_type = (strobe_type + 1) % NUM_STROBES; + interrupt_nice_delays(); save_config(); return MISCHIEF_MANAGED; } // hold: change speed (go faster) + // or change brightness (brighter) else if (event == EV_click1_hold) { - if ((arg & 1) == 0) { - if (strobe_delay > 8) strobe_delay --; + if (strobe_type < 2) { + if ((arg & 1) == 0) { + if (strobe_delay > 8) strobe_delay --; + } + } + // biking mode brighter + else if (strobe_type == 2) { + if (bike_flasher_brightness < MAX_LEVEL/2) + bike_flasher_brightness ++; + set_level(bike_flasher_brightness); } return MISCHIEF_MANAGED; } // click, hold: change speed (go slower) + // or change brightness (dimmer) else if (event == EV_click2_hold) { - if ((arg & 1) == 0) { - if (strobe_delay < 255) strobe_delay ++; + if (strobe_type < 2) { + if ((arg & 1) == 0) { + if (strobe_delay < 255) strobe_delay ++; + } + } + // biking mode dimmer + else if (strobe_type == 2) { + if (bike_flasher_brightness > 1) + bike_flasher_brightness --; + set_level(bike_flasher_brightness); } return MISCHIEF_MANAGED; } - // release hold: save new strobe speed + // release hold: save new strobe settings else if ((event == EV_click1_hold_release) || (event == EV_click2_hold_release)) { save_config(); @@ -612,6 +633,7 @@ void load_config() { ramp_discrete_steps = eeprom[5]; strobe_type = eeprom[6]; strobe_delay = eeprom[7]; + bike_flasher_brightness = eeprom[8]; } } @@ -624,6 +646,7 @@ void save_config() { eeprom[5] = ramp_discrete_steps; eeprom[6] = strobe_type; eeprom[7] = strobe_delay; + eeprom[8] = bike_flasher_brightness; save_eeprom(); } @@ -671,15 +694,29 @@ void loop() { } if (current_state == strobe_state) { - set_level(MAX_LEVEL); - if (strobe_type == 0) { // party strobe - if (strobe_delay < 30) delay_zero(); - else delay_ms(1); - } else { //tactical strobe - nice_delay_ms(strobe_delay >> 1); + // party / tactical strobe + if (strobe_type < 2) { + set_level(MAX_LEVEL); + if (strobe_type == 0) { // party strobe + if (strobe_delay < 30) delay_zero(); + else delay_ms(1); + } else { //tactical strobe + nice_delay_ms(strobe_delay >> 1); + } + set_level(0); + nice_delay_ms(strobe_delay); + } + // bike flasher + else if (strobe_type == 2) { + uint8_t burst = bike_flasher_brightness << 1; + for(uint8_t i=0; i<4; i++) { + set_level(burst); + if (! nice_delay_ms(5)) return; + set_level(bike_flasher_brightness); + if (! nice_delay_ms(65)) return; + } + if (! nice_delay_ms(720)) return; } - set_level(0); - nice_delay_ms(strobe_delay); } #ifdef USE_BATTCHECK else if (current_state == battcheck_state) { diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index 28d27f9..725ebfb 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -4,8 +4,10 @@ From off: * 2 clicks: turbo * Click, hold: highest level then ramp down * 3 clicks: battcheck mode + (battcheck, tempcheck, beacon) * 4 clicks: lock-out - * 5 clicks: strobe mode + * 5 clicks: strobe modes + (bike flasher, party strobe, tactical strobe, police strobe?) * 6 clicks: momentary mode (disconnect power to exit) In steady mode: @@ -31,14 +33,14 @@ Strobe mode: * 1 click: off * Hold: change speed (faster) * Click, hold: change speed (slower) - + 2 clicks: next strobe mode - (party strobe, tactical strobe, random/police strobe?, bike flasher) + * 2 clicks: next strobe mode + (bike flasher, party strobe, tactical strobe, random/police strobe?) Bike flasher: - - 1 click: off - - 2 clicks: party strobe - - Hold: brighter - - Click, hold: dimmer + * 1 click: off + * 2 clicks: party strobe + * Hold: brighter + * Click, hold: dimmer Battcheck mode: * 1 click: off -- cgit v1.2.3 From 3eb0e1d020d689b7ae4b0d1c7777f9158e91a6ec Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 21:45:20 -0600 Subject: Remembered to put the thermal ceiling back at a sane value. UI idea updates. --- spaghetti-monster/anduril.c | 2 +- spaghetti-monster/anduril.txt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index daafa90..6d25afa 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -21,7 +21,7 @@ #define FSM_EMISAR_D4_LAYOUT #define USE_LVP #define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 32 +#define DEFAULT_THERM_CEIL 45 #define USE_DELAY_MS #define USE_DELAY_4MS #define USE_DELAY_ZERO diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index 725ebfb..23466af 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -49,7 +49,11 @@ Battcheck mode: Tempcheck mode: * 1 click: off - 2 clicks: beacon mode - - Hold: thermal calibration + - Hold: thermal calibration mode + +Thermal calibration mode: + - Hold until hot: set new ceiling value + - ... don't hold: blink out current ceiling value and exit Beacon mode: - 1 click: off @@ -66,4 +70,5 @@ Momentary mode: TODO: * save settings in eeprom - a way to blink out the firmware version? + - indicator LED support - a way to configure indicator LED behavior? -- cgit v1.2.3 From f48209ee791282f72353a0806c8dab7fb814a4cb Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 22:22:56 -0600 Subject: Fixed bug: double-click from off didn't *really* go to the top of the ramp. --- spaghetti-monster/anduril.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 6d25afa..eba21d1 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -116,7 +116,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { } // 2 clicks: highest mode else if (event == EV_2clicks) { - set_level(nearest_level(MAX_LEVEL)); + set_state(steady_state, nearest_level(MAX_LEVEL)); return MISCHIEF_MANAGED; } #ifdef USE_BATTCHECK -- cgit v1.2.3 From f688f4c149a590b89cd850a899a0baa0149cdd27 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 22:40:49 -0600 Subject: Use different speed values for party and tactical strobes. --- spaghetti-monster/anduril.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index eba21d1..4fb6118 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -31,7 +31,7 @@ #define RAMP_LENGTH 150 #define MAX_CLICKS 6 #define USE_EEPROM -#define EEPROM_BYTES 9 +#define EEPROM_BYTES 10 #include "spaghetti-monster.h" // FSM states @@ -78,7 +78,7 @@ uint8_t target_level = 0; #endif // strobe timing -volatile uint8_t strobe_delay = 67; +volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe volatile uint8_t strobe_type = 2; // 0 == party strobe, 1 == tactical strobe, 2 == bike flasher volatile uint8_t bike_flasher_brightness = MAX_1x7135; @@ -315,7 +315,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { else if (event == EV_click1_hold) { if (strobe_type < 2) { if ((arg & 1) == 0) { - if (strobe_delay > 8) strobe_delay --; + if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --; } } // biking mode brighter @@ -331,7 +331,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { else if (event == EV_click2_hold) { if (strobe_type < 2) { if ((arg & 1) == 0) { - if (strobe_delay < 255) strobe_delay ++; + if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++; } } // biking mode dimmer @@ -632,8 +632,9 @@ void load_config() { ramp_discrete_ceil = eeprom[4]; ramp_discrete_steps = eeprom[5]; strobe_type = eeprom[6]; - strobe_delay = eeprom[7]; - bike_flasher_brightness = eeprom[8]; + strobe_delays[0] = eeprom[7]; + strobe_delays[1] = eeprom[8]; + bike_flasher_brightness = eeprom[9]; } } @@ -645,8 +646,9 @@ void save_config() { eeprom[4] = ramp_discrete_ceil; eeprom[5] = ramp_discrete_steps; eeprom[6] = strobe_type; - eeprom[7] = strobe_delay; - eeprom[8] = bike_flasher_brightness; + eeprom[7] = strobe_delays[0]; + eeprom[8] = strobe_delays[1]; + eeprom[9] = bike_flasher_brightness; save_eeprom(); } @@ -698,13 +700,13 @@ void loop() { if (strobe_type < 2) { set_level(MAX_LEVEL); if (strobe_type == 0) { // party strobe - if (strobe_delay < 30) delay_zero(); + if (strobe_delays[strobe_type] < 42) delay_zero(); else delay_ms(1); } else { //tactical strobe - nice_delay_ms(strobe_delay >> 1); + nice_delay_ms(strobe_delays[strobe_type] >> 1); } set_level(0); - nice_delay_ms(strobe_delay); + nice_delay_ms(strobe_delays[strobe_type]); } // bike flasher else if (strobe_type == 2) { -- cgit v1.2.3 From 98380adfd4c1f9a5e12d7d416cc14b909284014b Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 22:41:37 -0600 Subject: Fixed a bug where sometimes the light wouldn't respond for a second or so after connecting power. (spurious events were detected at boot, and previously had to time out before it would respond) --- spaghetti-monster/fsm-main.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index 1a0c425..ae63126 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -88,6 +88,10 @@ int main() { // fallback for handling a few things push_state(default_state, 0); + // in case any spurious button presses were detected at boot + delay_ms(1); + empty_event_sequence(); + // call recipe's setup setup(); -- cgit v1.2.3 From 2589ce5a06472c0693515294f9aa575ef83c3029 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 27 Aug 2017 22:50:34 -0600 Subject: UI description up-to-date now. --- spaghetti-monster/anduril.txt | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index 23466af..d5b224e 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -1,40 +1,40 @@ From off: * 1 click: memorized level * Hold: Lowest level then ramp up - * 2 clicks: turbo + * 2 clicks: highest ramp level * Click, hold: highest level then ramp down * 3 clicks: battcheck mode - (battcheck, tempcheck, beacon) + (battcheck, tempcheck, then beacon?) * 4 clicks: lock-out * 5 clicks: strobe modes (bike flasher, party strobe, tactical strobe, police strobe?) + (remembers which you last used) * 6 clicks: momentary mode (disconnect power to exit) In steady mode: * 1 click: off * Hold: ramp up * Click, hold: ramp down - * 2 clicks: to/from turbo + * 2 clicks: to/from turbo (actual turbo, not just highest ramp level) * 3 clicks: toggle smooth vs discrete ramping * 4 clicks: configure current ramp Smooth ramp config mode: - - Setting 1: memory on/off + - Setting 1: memory on/off (not implemented yet) * Setting 2: low end + (click N times to set ramp floor to level N) * Setting 3: high end + (click N times to set ramp ceiling to level "151 - N") Discrete ramp config mode: - - Setting 1: memory on/off + - Setting 1: memory on/off (not implemented yet) * Setting 2: low end + (click N times to set ramp floor to level N) * Setting 3: high end - * Setting 4: number of levels - -Strobe mode: - * 1 click: off - * Hold: change speed (faster) - * Click, hold: change speed (slower) - * 2 clicks: next strobe mode - (bike flasher, party strobe, tactical strobe, random/police strobe?) + (click N times to set ramp ceiling to level "151 - N") + * Setting 4: number of levels in discrete ramp + (click N times to make discrete mode have N stair-steps) + (minimum 2, maximum 150) Bike flasher: * 1 click: off @@ -42,6 +42,13 @@ Bike flasher: * Hold: brighter * Click, hold: dimmer +Party / Tactical strobe modes: + * 1 click: off + * Hold: change speed (faster) + * Click, hold: change speed (slower) + * 2 clicks: next strobe mode + (bike flasher, party strobe, tactical strobe, random/police strobe?) + Battcheck mode: * 1 click: off * 2 clicks: tempcheck mode -- cgit v1.2.3 From 4a16af683c167fb27bc28b50a58227eb05da8c29 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 28 Aug 2017 01:14:34 -0600 Subject: Added beacon mode and beacon config mode. Renamed ramp_config_mode to ramp_config_state. --- spaghetti-monster/anduril.c | 83 ++++++++++++++++++++++++++++++++++++++++--- spaghetti-monster/anduril.txt | 10 +++--- 2 files changed, 83 insertions(+), 10 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 4fb6118..4ef0f1b 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -31,21 +31,29 @@ #define RAMP_LENGTH 150 #define MAX_CLICKS 6 #define USE_EEPROM -#define EEPROM_BYTES 10 +#define EEPROM_BYTES 11 #include "spaghetti-monster.h" // FSM states uint8_t off_state(EventPtr event, uint16_t arg); +// ramping mode and its related config mode uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t ramp_config_state(EventPtr event, uint16_t arg); +// party and tactical strobes uint8_t strobe_state(EventPtr event, uint16_t arg); #define NUM_STROBES 3 #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); uint8_t tempcheck_state(EventPtr event, uint16_t arg); #endif +// soft lockout uint8_t lockout_state(EventPtr event, uint16_t arg); +// momentary / signalling mode uint8_t momentary_state(EventPtr event, uint16_t arg); -uint8_t ramp_config_mode(EventPtr event, uint16_t arg); +// beacon mode and its related config mode +uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t beacon_config_state(EventPtr event, uint16_t arg); +// general helper function for config modes uint8_t number_entry_state(EventPtr event, uint16_t arg); // return value from number_entry_state() @@ -53,6 +61,7 @@ volatile uint8_t number_entry_value; void blink_confirm(uint8_t num); +// remember stuff even after battery was changed void load_config(); void save_config(); @@ -80,8 +89,13 @@ uint8_t target_level = 0; // strobe timing volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe volatile uint8_t strobe_type = 2; // 0 == party strobe, 1 == tactical strobe, 2 == bike flasher + +// bike mode config options volatile uint8_t bike_flasher_brightness = MAX_1x7135; +// beacon timing +volatile uint8_t beacon_seconds = 2; + // deferred "off" so we won't suspend in a weird state volatile uint8_t go_to_standby = 0; @@ -223,7 +237,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 4 clicks: configure this ramp mode else if (event == EV_4clicks) { - set_state(ramp_config_mode, 0); + set_state(ramp_config_state, 0); return MISCHIEF_MANAGED; } // hold: change brightness (brighter) @@ -373,10 +387,36 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return MISCHIEF_MANAGED; } + // 2 clicks: beacon mode + else if (event == EV_2clicks) { + set_state(beacon_state, 0); + return MISCHIEF_MANAGED; + } return EVENT_NOT_HANDLED; } #endif + +uint8_t beacon_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: battcheck mode + else if (event == EV_2clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + // 3 clicks: beacon config mode + else if (event == EV_3clicks) { + set_state(beacon_config_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + uint8_t lockout_state(EventPtr event, uint16_t arg) { // conserve power while locked out // (allow staying awake long enough to exit, but otherwise @@ -431,7 +471,7 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { } -uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { +uint8_t ramp_config_state(EventPtr event, uint16_t arg) { static uint8_t config_step; static uint8_t num_config_steps; if (event == EV_enter_state) { @@ -496,6 +536,33 @@ uint8_t ramp_config_mode(EventPtr event, uint16_t arg) { } +uint8_t beacon_config_state(EventPtr event, uint16_t arg) { + static uint8_t done = 0; + if (event == EV_enter_state) { + set_level(0); + done = 0; + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (! done) push_state(number_entry_state, 1); + else { + save_config(); + // return to beacon mode + set_state(beacon_state, 0); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + if (number_entry_value) beacon_seconds = number_entry_value; + done = 1; + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + uint8_t number_entry_state(EventPtr event, uint16_t arg) { static uint8_t value; static uint8_t blinks_left; @@ -635,6 +702,7 @@ void load_config() { strobe_delays[0] = eeprom[7]; strobe_delays[1] = eeprom[8]; bike_flasher_brightness = eeprom[9]; + beacon_seconds = eeprom[10]; } } @@ -649,6 +717,7 @@ void save_config() { eeprom[7] = strobe_delays[0]; eeprom[8] = strobe_delays[1]; eeprom[9] = bike_flasher_brightness; + eeprom[10] = beacon_seconds; save_eeprom(); } @@ -729,4 +798,10 @@ void loop() { nice_delay_ms(1000); } #endif + else if (current_state == beacon_state) { + set_level(memorized_level); + if (! nice_delay_ms(500)) return; + set_level(0); + nice_delay_ms(((beacon_seconds) * 1000) - 500); + } } diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index d5b224e..8a82447 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -55,7 +55,7 @@ Battcheck mode: Tempcheck mode: * 1 click: off - - 2 clicks: beacon mode + * 2 clicks: beacon mode - Hold: thermal calibration mode Thermal calibration mode: @@ -63,11 +63,9 @@ Thermal calibration mode: - ... don't hold: blink out current ceiling value and exit Beacon mode: - - 1 click: off - - 2 clicks: battcheck mode - - Hold: brighter - - Click, hold: dimmer - - 3 clicks: configure time between pulses + * 1 click: off + * 2 clicks: battcheck mode + * 3 clicks: configure time between pulses Momentary mode: * Press button: Light on (at memorized level). -- cgit v1.2.3 From a4be98cddedbbdfb87b18dd745ef468a536b1826 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 28 Aug 2017 01:59:06 -0600 Subject: Added thermal config mode... but I'm not sure if it's the right UI for this or not. --- spaghetti-monster/anduril.c | 41 +++++++++++++++++++++++++++++++++++++++-- spaghetti-monster/anduril.txt | 5 +++++ spaghetti-monster/fsm-adc.h | 2 +- 3 files changed, 45 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 4ef0f1b..9799190 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -31,7 +31,7 @@ #define RAMP_LENGTH 150 #define MAX_CLICKS 6 #define USE_EEPROM -#define EEPROM_BYTES 11 +#define EEPROM_BYTES 12 #include "spaghetti-monster.h" // FSM states @@ -45,6 +45,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg); #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); uint8_t tempcheck_state(EventPtr event, uint16_t arg); +uint8_t thermal_config_state(EventPtr event, uint16_t arg); #endif // soft lockout uint8_t lockout_state(EventPtr event, uint16_t arg); @@ -392,6 +393,11 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg) { set_state(beacon_state, 0); return MISCHIEF_MANAGED; } + // 3 clicks: thermal config mode + else if (event == EV_3clicks) { + set_state(thermal_config_state, 0); + return MISCHIEF_MANAGED; + } return EVENT_NOT_HANDLED; } #endif @@ -536,6 +542,34 @@ uint8_t ramp_config_state(EventPtr event, uint16_t arg) { } +uint8_t thermal_config_state(EventPtr event, uint16_t arg) { + static uint8_t done = 0; + if (event == EV_enter_state) { + set_level(0); + done = 0; + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (! done) push_state(number_entry_state, 1); + else { + save_config(); + // return to beacon mode + set_state(tempcheck_state, 0); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + if (number_entry_value) therm_ceil = 30 + number_entry_value; + if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; + done = 1; + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + uint8_t beacon_config_state(EventPtr event, uint16_t arg) { static uint8_t done = 0; if (event == EV_enter_state) { @@ -703,6 +737,7 @@ void load_config() { strobe_delays[1] = eeprom[8]; bike_flasher_brightness = eeprom[9]; beacon_seconds = eeprom[10]; + therm_ceil = eeprom[11]; } } @@ -718,6 +753,7 @@ void save_config() { eeprom[8] = strobe_delays[1]; eeprom[9] = bike_flasher_brightness; eeprom[10] = beacon_seconds; + eeprom[11] = therm_ceil; save_eeprom(); } @@ -794,9 +830,10 @@ void loop() { battcheck(); } else if (current_state == tempcheck_state) { - blink_num(projected_temperature>>2); + blink_num(temperature>>2); nice_delay_ms(1000); } + // TODO: blink out therm_ceil during thermal_config_state #endif else if (current_state == beacon_state) { set_level(memorized_level); diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index 8a82447..5e6d4b9 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -56,8 +56,12 @@ Battcheck mode: Tempcheck mode: * 1 click: off * 2 clicks: beacon mode + * 3 clicks: thermal config mode - Hold: thermal calibration mode +Thermal config mode: + * Click N times to set thermal limit to roughly 30 C + N. + Thermal calibration mode: - Hold until hot: set new ceiling value - ... don't hold: blink out current ceiling value and exit @@ -74,6 +78,7 @@ Momentary mode: TODO: * save settings in eeprom + - decide on "hold until hot" or "click N times" for thermal config mode - a way to blink out the firmware version? - indicator LED support - a way to configure indicator LED behavior? diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index c12405c..af30b69 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -69,7 +69,7 @@ void battcheck(); volatile int16_t temperature; // temperature in a few seconds, in C (ish) * 4 (13.2 fixed-point) volatile int16_t projected_temperature; // Fight the future! -uint8_t therm_ceil = DEFAULT_THERM_CEIL; +volatile uint8_t therm_ceil = DEFAULT_THERM_CEIL; //void low_temperature(); //void high_temperature(); #endif -- cgit v1.2.3 From 4a96d3e40ee667fa819c8c95b5e360245a12865a Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 28 Aug 2017 02:13:35 -0600 Subject: Fixed a crash when user presses the button more times than the UI supports. (need to leave room for a trailing 0 in the array, oops) --- spaghetti-monster/fsm-events.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 90d0ffa..99a6a72 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -47,7 +47,7 @@ uint8_t push_event(uint8_t ev_type) { uint8_t prev_event = 0; // never push the same event twice in a row for(i=0; current_event[i] && (i 1) bike_flasher_brightness --; set_level(bike_flasher_brightness); @@ -363,6 +373,13 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { save_config(); return MISCHIEF_MANAGED; } + #ifdef USE_LIGHTNING_MODE + // clock tick: bump the random seed + else if (event == EV_tick) { + pseudo_rand_seed += arg; + return MISCHIEF_MANAGED; + } + #endif return EVENT_NOT_HANDLED; } @@ -726,6 +743,17 @@ void blink_confirm(uint8_t num) { } +#ifdef USE_LIGHTNING_MODE +uint8_t pseudo_rand() { + static uint16_t offset = 1024; + // loop from 1024 to 4095 + offset = ((offset + 1) & 0x0fff) | 0x0400; + pseudo_rand_seed += 0b01010101; // 85 + return pgm_read_byte(offset) + pseudo_rand_seed; +} +#endif + + void load_config() { if (load_eeprom()) { ramp_style = eeprom[0]; @@ -734,7 +762,7 @@ void load_config() { ramp_discrete_floor = eeprom[3]; ramp_discrete_ceil = eeprom[4]; ramp_discrete_steps = eeprom[5]; - strobe_type = eeprom[6]; + strobe_type = eeprom[6]; // TODO: move this to eeprom_wl? strobe_delays[0] = eeprom[7]; strobe_delays[1] = eeprom[8]; bike_flasher_brightness = eeprom[9]; @@ -750,7 +778,7 @@ void save_config() { eeprom[3] = ramp_discrete_floor; eeprom[4] = ramp_discrete_ceil; eeprom[5] = ramp_discrete_steps; - eeprom[6] = strobe_type; + eeprom[6] = strobe_type; // TODO: move this to eeprom_wl? eeprom[7] = strobe_delays[0]; eeprom[8] = strobe_delays[1]; eeprom[9] = bike_flasher_brightness; @@ -817,8 +845,44 @@ void loop() { set_level(0); nice_delay_ms(strobe_delays[strobe_type]); } - // bike flasher + #ifdef USE_LIGHTNING_MODE + // lightning storm else if (strobe_type == 2) { + int16_t brightness; + uint16_t rand_time; + + // turn the emitter on at a random level, + // for a random amount of time between 1ms and 32ms + rand_time = 1 << (pseudo_rand() % 6); + brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64 + brightness += 1 << (pseudo_rand()&0x03); // 2 to 80 now + brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) + if (brightness > MAX_LEVEL) brightness = MAX_LEVEL; + set_level(brightness); + if (! nice_delay_ms(rand_time)) return; + + // decrease the brightness somewhat more gradually, like lightning + uint8_t stepdown = brightness >> 3; + if (stepdown < 1) stepdown = 1; + while(brightness > 1) { + if (! nice_delay_ms(rand_time)) return; + brightness -= stepdown; + if (brightness < 0) brightness = 0; + set_level(brightness); + } + + // turn the emitter off, + // for a random amount of time between 1ms and 8192ms + // (with a low bias) + rand_time = 1<<(pseudo_rand()%13); + rand_time += pseudo_rand()%rand_time; + set_level(0); + nice_delay_ms(rand_time); + + } + #endif + // bike flasher + else if (strobe_type == 3) { uint8_t burst = bike_flasher_brightness << 1; for(uint8_t i=0; i<4; i++) { set_level(burst); -- cgit v1.2.3 From af0d17cf6d69fdc2df163d5f26b9de019433de4e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 28 Aug 2017 15:14:21 -0600 Subject: Forgot to update the UI text file for lightning storm mode. --- spaghetti-monster/anduril.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index a9790b1..c6213e8 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -7,7 +7,7 @@ From off: (battcheck, tempcheck, then beacon?) * 4 clicks: lock-out * 5 clicks: strobe modes - (bike flasher, party strobe, tactical strobe, police strobe?) + (bike flasher, party strobe, tactical strobe, lightning storm mode) (remembers which you last used) * 6 clicks: momentary mode (disconnect power to exit) @@ -47,7 +47,12 @@ Party / Tactical strobe modes: * Hold: change speed (faster) * Click, hold: change speed (slower) * 2 clicks: next strobe mode - (bike flasher, party strobe, tactical strobe, random/police strobe?) + (bike flasher, party strobe, tactical strobe, lightning storm mode) + (TODO: random/police strobe?) + +Lightning storm mode: + * 1 click: off + * 2 clicks: bike flasher Battcheck mode: * 1 click: off -- cgit v1.2.3 From 2ce364480d2fb4ea3f9d2ae15563b4ca1df8e0af Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 28 Aug 2017 21:21:50 -0600 Subject: Added goodnight mode. Rearranged blinkies in battcheck group. --- spaghetti-monster/anduril.c | 69 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 11 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 9573705..6c21bf3 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -32,9 +32,14 @@ #define MAX_CLICKS 6 #define USE_EEPROM #define EEPROM_BYTES 12 -#define USE_LIGHTNING_MODE #include "spaghetti-monster.h" +// Options specific to this UI (not inherited from SpaghettiMonster) +#define USE_LIGHTNING_MODE +// set this a bit high, since the bottom 2 ramp levels might not emit any light at all +#define GOODNIGHT_TIME 65 // minutes (approximately) +#define GOODNIGHT_LEVEL 24 // ~11 lm + // FSM states uint8_t off_state(EventPtr event, uint16_t arg); // ramping mode and its related config mode @@ -52,16 +57,18 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg); uint8_t tempcheck_state(EventPtr event, uint16_t arg); uint8_t thermal_config_state(EventPtr event, uint16_t arg); #endif +// 1-hour ramp down from low, then automatic off +uint8_t goodnight_state(EventPtr event, uint16_t arg); +// beacon mode and its related config mode +uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t beacon_config_state(EventPtr event, uint16_t arg); // soft lockout uint8_t lockout_state(EventPtr event, uint16_t arg); // momentary / signalling mode uint8_t momentary_state(EventPtr event, uint16_t arg); -// beacon mode and its related config mode -uint8_t beacon_state(EventPtr event, uint16_t arg); -uint8_t beacon_config_state(EventPtr event, uint16_t arg); + // general helper function for config modes uint8_t number_entry_state(EventPtr event, uint16_t arg); - // return value from number_entry_state() volatile uint8_t number_entry_value; @@ -391,9 +398,9 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return MISCHIEF_MANAGED; } - // 2 clicks: tempcheck mode + // 2 clicks: goodnight mode else if (event == EV_2clicks) { - set_state(tempcheck_state, 0); + set_state(goodnight_state, 0); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -405,9 +412,9 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return MISCHIEF_MANAGED; } - // 2 clicks: beacon mode + // 2 clicks: battcheck mode else if (event == EV_2clicks) { - set_state(beacon_state, 0); + set_state(battcheck_state, 0); return MISCHIEF_MANAGED; } // 3 clicks: thermal config mode @@ -426,9 +433,9 @@ uint8_t beacon_state(EventPtr event, uint16_t arg) { set_state(off_state, 0); return MISCHIEF_MANAGED; } - // 2 clicks: battcheck mode + // 2 clicks: tempcheck mode else if (event == EV_2clicks) { - set_state(battcheck_state, 0); + set_state(tempcheck_state, 0); return MISCHIEF_MANAGED; } // 3 clicks: beacon config mode @@ -440,6 +447,46 @@ uint8_t beacon_state(EventPtr event, uint16_t arg) { } +#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL) +uint8_t goodnight_state(EventPtr event, uint16_t arg) { + static uint16_t ticks_since_stepdown = 0; + // blink on start + if (event == EV_enter_state) { + blink_confirm(4); + ticks_since_stepdown = 0; + set_level(GOODNIGHT_LEVEL); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: beacon mode + else if (event == EV_2clicks) { + set_state(beacon_state, 0); + return MISCHIEF_MANAGED; + } + // tick: step down (maybe) or off (maybe) + else if (event == EV_tick) { + if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) { + ticks_since_stepdown = 0; + set_level(actual_level-1); + if (! actual_level) { + #if 0 // test blink, to help measure timing + set_level(MAX_LEVEL>>2); + delay_4ms(8/2); + set_level(0); + #endif + set_state(off_state, 0); + } + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + uint8_t lockout_state(EventPtr event, uint16_t arg) { // conserve power while locked out // (allow staying awake long enough to exit, but otherwise -- cgit v1.2.3 From f881184e490bc13bb5d2436fc67ede0c1eb0c4c0 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 30 Aug 2017 22:16:37 -0600 Subject: Made event handling a bit more reliable while asleep. (was sometimes having difficulty getting out of soft lockout mode) --- spaghetti-monster/fsm-standby.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c index bef0533..86c5f0d 100644 --- a/spaghetti-monster/fsm-standby.c +++ b/spaghetti-monster/fsm-standby.c @@ -36,6 +36,7 @@ void sleep_until_eswitch_pressed() // make sure switch isn't currently pressed while (button_is_pressed()) {} + empty_event_sequence(); // cancel pending input on suspend PCINT_on(); // wake on e-switch event @@ -45,7 +46,8 @@ void sleep_until_eswitch_pressed() // something happened; wake up sleep_disable(); - PCINT_on(); + //PCINT_on(); // should be on already + // FIXME? if button is down, make sure a button press event is added to the current sequence ADC_on(); WDT_on(); } -- cgit v1.2.3 From f61088192b58a3e627cf97164aa05e4eb183fea3 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 30 Aug 2017 23:06:16 -0600 Subject: Updated anduril.txt to match current code. --- spaghetti-monster/anduril.txt | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt index c6213e8..bb0f5e2 100644 --- a/spaghetti-monster/anduril.txt +++ b/spaghetti-monster/anduril.txt @@ -4,7 +4,7 @@ From off: * 2 clicks: highest ramp level * Click, hold: highest level then ramp down * 3 clicks: battcheck mode - (battcheck, tempcheck, then beacon?) + (battcheck, goodnight, beacon, tempcheck) * 4 clicks: lock-out * 5 clicks: strobe modes (bike flasher, party strobe, tactical strobe, lightning storm mode) @@ -55,12 +55,24 @@ Lightning storm mode: * 2 clicks: bike flasher Battcheck mode: + * 1 click: off + * 2 clicks: goodnight mode + +Goodnight mode: + * 1 click: off + * 2 clicks: beacon mode + +Beacon mode: * 1 click: off * 2 clicks: tempcheck mode + * 3 clicks: configure time between pulses + +Beacon config mode: + * At buzz, click N times to set beacon frequency to N seconds. Tempcheck mode: * 1 click: off - * 2 clicks: beacon mode + * 2 clicks: battcheck mode * 3 clicks: thermal config mode - Hold: thermal calibration mode @@ -71,14 +83,6 @@ Thermal calibration mode: - Hold until hot: set new ceiling value - ... don't hold: blink out current ceiling value and exit -Beacon mode: - * 1 click: off - * 2 clicks: battcheck mode - * 3 clicks: configure time between pulses - -Beacon config mode: - * At buzz, click N times to set beacon frequency to N seconds. - Momentary mode: * Press button: Light on (at memorized level). * Release button: Light off. @@ -92,5 +96,5 @@ TODO: - a way to blink out the firmware version? - indicator LED support - a way to configure indicator LED behavior? - - add goodnight mode? - - add lightning mode? + * add goodnight mode? + * add lightning mode? -- cgit v1.2.3 From 15d5b2cbe807029c7493691a1877a1898d100455 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 30 Aug 2017 23:07:55 -0600 Subject: Replaced FSM_*_LAYOUT with FSM_*_DRIVER because I think it makes more sense. Made momentary.c compile again. Updated fsm-main to use whichever delay function is available. --- spaghetti-monster/anduril.c | 2 +- spaghetti-monster/baton.c | 2 +- spaghetti-monster/darkhorse.c | 2 +- spaghetti-monster/fsm-main.c | 4 ++++ spaghetti-monster/momentary.c | 3 ++- spaghetti-monster/ramping-ui.c | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c index 6c21bf3..998c0c5 100644 --- a/spaghetti-monster/anduril.c +++ b/spaghetti-monster/anduril.c @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#define FSM_EMISAR_D4_LAYOUT +#define FSM_EMISAR_D4_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 45 diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c index 2489f4e..4ba329e 100644 --- a/spaghetti-monster/baton.c +++ b/spaghetti-monster/baton.c @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#define FSM_EMISAR_D4_LAYOUT +#define FSM_EMISAR_D4_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 45 diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c index 2aa1b7b..2aa3e90 100644 --- a/spaghetti-monster/darkhorse.c +++ b/spaghetti-monster/darkhorse.c @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#define FSM_EMISAR_D4_LAYOUT +#define FSM_EMISAR_D4_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 45 diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index ae63126..9505322 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -89,7 +89,11 @@ int main() { push_state(default_state, 0); // in case any spurious button presses were detected at boot + #ifdef USE_DELAY_MS delay_ms(1); + #else + delay_4ms(1); + #endif empty_event_sequence(); // call recipe's setup diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c index fc26db3..6b049f4 100644 --- a/spaghetti-monster/momentary.c +++ b/spaghetti-monster/momentary.c @@ -19,9 +19,10 @@ * along with this program. If not, see . */ -#define FSM_EMISAR_D4_LAYOUT +#define FSM_EMISAR_D4_DRIVER #define USE_LVP #define USE_DEBUG_BLINK +#define USE_DELAY_4MS #include "spaghetti-monster.h" volatile uint8_t brightness; diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c index 2369686..527a824 100644 --- a/spaghetti-monster/ramping-ui.c +++ b/spaghetti-monster/ramping-ui.c @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#define FSM_EMISAR_D4_LAYOUT +#define FSM_EMISAR_D4_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 32 -- cgit v1.2.3 From 3e7fd00e614c0ac6693d697cc0ca3473a99490bf Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 30 Aug 2017 23:08:59 -0600 Subject: Added a simpler and more accurate Baton UI clone. Basically, took out the party strobe and added a lockout mode instead. --- spaghetti-monster/baton-simpler.c | 202 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 spaghetti-monster/baton-simpler.c (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/baton-simpler.c b/spaghetti-monster/baton-simpler.c new file mode 100644 index 0000000..a6ddf36 --- /dev/null +++ b/spaghetti-monster/baton-simpler.c @@ -0,0 +1,202 @@ +/* + * 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_MS +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t lockout_state(EventPtr event, uint16_t arg); + +// brightness control +uint8_t memorized_level = 1; +uint8_t actual_level = 0; +#ifdef USE_THERMAL_REGULATION +uint8_t target_level = 0; +#endif + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + +// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 +uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, }; +uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, }; +#define MAX_LEVEL (sizeof(pwm1_levels)-1) + +// set LED brightness +void set_level(uint8_t lvl) { + actual_level = lvl; + PWM1_LVL = pwm1_levels[lvl]; + PWM2_LVL = pwm2_levels[lvl]; +} + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + go_to_standby = 1; // sleep while off (lower power use) + return EVENT_HANDLED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(0); + return EVENT_HANDLED; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return EVENT_HANDLED; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return EVENT_HANDLED; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, MAX_LEVEL); + return EVENT_HANDLED; + } + // 4 clicks: soft lockout + else if (event == EV_4clicks) { + set_state(lockout_state, 0); + return EVENT_HANDLED; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + set_state(steady_state, 0); + return EVENT_HANDLED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t steady_state(EventPtr event, uint16_t arg) { + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > 0) && (arg < MAX_LEVEL)) + memorized_level = arg; + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return EVENT_HANDLED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { // go to turbo + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + set_level(MAX_LEVEL); + } + else { // return from turbo + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return EVENT_HANDLED; + } + // hold: change brightness + else if (event == EV_click1_hold) { + if ((arg % HOLD_TIMEOUT) == 0) { + memorized_level = (actual_level+1) % (MAX_LEVEL+1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return EVENT_HANDLED; + } + #ifdef USE_THERMAL_REGULATION + // overheating: drop by 1 level + else if (event == EV_temperature_high) { + if (actual_level > 1) { + set_level(actual_level - 1); + } + return EVENT_HANDLED; + } + // underheating: increase by 1 level if we're lower than the target + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + set_level(actual_level + 1); + } + return EVENT_HANDLED; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + // stay asleep while locked, but allow waking long enough to click 4 times + if (event == EV_tick) { + static uint8_t ticks_spent_awake = 0; + ticks_spent_awake ++; + PWM1_LVL = 0; PWM2_LVL = 0; + if (ticks_spent_awake > 3 * TICKS_PER_SECOND) { + ticks_spent_awake = 0; + go_to_standby = 1; + } + return MISCHIEF_MANAGED; + } + // 4 clicks: exit + else if (event == EV_4clicks) { + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +void low_voltage() { + // step down by one level or turn off + if (actual_level > 0) { + set_level(actual_level - 1); + } + else { + set_state(off_state, 0); + } +} + +void setup() { + // blink when power is connected + set_level(MAX_LEVEL/2); + delay_ms(10); + set_level(0); + + push_state(off_state, 0); +} + +void loop() { + if (go_to_standby) { + go_to_standby = 0; + PWM1_LVL = 0; PWM2_LVL = 0; + standby_mode(); + } +} -- cgit v1.2.3 From af011b08919e42736f514ae6ffe045571dbefda9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 30 Aug 2017 23:15:39 -0600 Subject: Reorganized FSM files, one dir per UI. --- spaghetti-monster/anduril.c | 961 ------------------------------ spaghetti-monster/anduril.txt | 100 ---- spaghetti-monster/anduril/anduril.c | 961 ++++++++++++++++++++++++++++++ spaghetti-monster/anduril/anduril.txt | 100 ++++ spaghetti-monster/baton-simpler.c | 202 ------- spaghetti-monster/baton.c | 230 ------- spaghetti-monster/baton/baton-simpler.c | 202 +++++++ spaghetti-monster/baton/baton.c | 231 +++++++ spaghetti-monster/darkhorse.c | 378 ------------ spaghetti-monster/darkhorse/darkhorse.c | 378 ++++++++++++ spaghetti-monster/momentary.c | 81 --- spaghetti-monster/momentary/momentary.c | 81 +++ spaghetti-monster/ramping-ui.c | 361 ----------- spaghetti-monster/ramping-ui/ramping-ui.c | 361 +++++++++++ 14 files changed, 2314 insertions(+), 2313 deletions(-) delete mode 100644 spaghetti-monster/anduril.c delete mode 100644 spaghetti-monster/anduril.txt create mode 100644 spaghetti-monster/anduril/anduril.c create mode 100644 spaghetti-monster/anduril/anduril.txt delete mode 100644 spaghetti-monster/baton-simpler.c delete mode 100644 spaghetti-monster/baton.c create mode 100644 spaghetti-monster/baton/baton-simpler.c create mode 100644 spaghetti-monster/baton/baton.c delete mode 100644 spaghetti-monster/darkhorse.c create mode 100644 spaghetti-monster/darkhorse/darkhorse.c delete mode 100644 spaghetti-monster/momentary.c create mode 100644 spaghetti-monster/momentary/momentary.c delete mode 100644 spaghetti-monster/ramping-ui.c create mode 100644 spaghetti-monster/ramping-ui/ramping-ui.c (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c deleted file mode 100644 index 998c0c5..0000000 --- a/spaghetti-monster/anduril.c +++ /dev/null @@ -1,961 +0,0 @@ -/* - * Anduril: Narsil-inspired UI for SpaghettiMonster. - * (Anduril is Aragorn's sword, the blade Narsil reforged) - * - * 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_DRIVER -#define USE_LVP -#define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 45 -#define USE_DELAY_MS -#define USE_DELAY_4MS -#define USE_DELAY_ZERO -#define USE_RAMPING -#define RAMP_LENGTH 150 -#define USE_BATTCHECK -#define BATTCHECK_VpT -#define MAX_CLICKS 6 -#define USE_EEPROM -#define EEPROM_BYTES 12 -#include "spaghetti-monster.h" - -// Options specific to this UI (not inherited from SpaghettiMonster) -#define USE_LIGHTNING_MODE -// set this a bit high, since the bottom 2 ramp levels might not emit any light at all -#define GOODNIGHT_TIME 65 // minutes (approximately) -#define GOODNIGHT_LEVEL 24 // ~11 lm - -// FSM states -uint8_t off_state(EventPtr event, uint16_t arg); -// ramping mode and its related config mode -uint8_t steady_state(EventPtr event, uint16_t arg); -uint8_t ramp_config_state(EventPtr event, uint16_t arg); -// party and tactical strobes -uint8_t strobe_state(EventPtr event, uint16_t arg); -#ifdef USE_LIGHTNING_MODE -#define NUM_STROBES 4 -#else -#define NUM_STROBES 3 -#endif -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg); -uint8_t tempcheck_state(EventPtr event, uint16_t arg); -uint8_t thermal_config_state(EventPtr event, uint16_t arg); -#endif -// 1-hour ramp down from low, then automatic off -uint8_t goodnight_state(EventPtr event, uint16_t arg); -// beacon mode and its related config mode -uint8_t beacon_state(EventPtr event, uint16_t arg); -uint8_t beacon_config_state(EventPtr event, uint16_t arg); -// soft lockout -uint8_t lockout_state(EventPtr event, uint16_t arg); -// momentary / signalling mode -uint8_t momentary_state(EventPtr event, uint16_t arg); - -// general helper function for config modes -uint8_t number_entry_state(EventPtr event, uint16_t arg); -// return value from number_entry_state() -volatile uint8_t number_entry_value; - -void blink_confirm(uint8_t num); - -// remember stuff even after battery was changed -void load_config(); -void save_config(); - -// brightness control -uint8_t memorized_level = MAX_1x7135; -// smooth vs discrete ramping -volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete -volatile uint8_t ramp_smooth_floor = 5; -volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30; -volatile uint8_t ramp_discrete_floor = 20; -volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30; -volatile uint8_t ramp_discrete_steps = 7; -uint8_t ramp_discrete_step_size; // don't set this - -// calculate the nearest ramp level which would be valid at the moment -// (is a no-op for smooth ramp, but limits discrete ramp to only the -// correct levels for the user's config) -uint8_t nearest_level(int16_t target); - -#ifdef USE_THERMAL_REGULATION -// brightness before thermal step-down -uint8_t target_level = 0; -#endif - -// strobe timing -volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe -volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 == lightning storm, 3 == bike flasher - -// bike mode config options -volatile uint8_t bike_flasher_brightness = MAX_1x7135; - -#ifdef USE_LIGHTNING_MODE -volatile uint8_t pseudo_rand_seed = 0; -uint8_t pseudo_rand(); -#endif - -// beacon timing -volatile uint8_t beacon_seconds = 2; - -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - - -uint8_t off_state(EventPtr event, uint16_t arg) { - // turn emitter off when entering state - if (event == EV_enter_state) { - set_level(0); - // sleep while off (lower power use) - go_to_standby = 1; - return MISCHIEF_MANAGED; - } - // hold (initially): go to lowest level, but allow abort for regular click - else if (event == EV_click1_press) { - set_level(nearest_level(1)); - return MISCHIEF_MANAGED; - } - // 1 click (before timeout): go to memorized level, but allow abort for double click - else if (event == EV_click1_release) { - set_level(nearest_level(memorized_level)); - return MISCHIEF_MANAGED; - } - // 1 click: regular mode - else if (event == EV_1click) { - set_state(steady_state, memorized_level); - return MISCHIEF_MANAGED; - } - // 2 clicks (initial press): off, to prep for later events - else if (event == EV_click2_press) { - set_level(0); - return MISCHIEF_MANAGED; - } - // 2 clicks: highest mode - else if (event == EV_2clicks) { - set_state(steady_state, nearest_level(MAX_LEVEL)); - return MISCHIEF_MANAGED; - } - #ifdef USE_BATTCHECK - // 3 clicks: battcheck mode - else if (event == EV_3clicks) { - set_state(battcheck_state, 0); - return MISCHIEF_MANAGED; - } - #endif - // 4 clicks: soft lockout - else if (event == EV_4clicks) { - blink_confirm(5); - set_state(lockout_state, 0); - return MISCHIEF_MANAGED; - } - // 5 clicks: strobe mode - else if (event == EV_5clicks) { - set_state(strobe_state, 0); - return MISCHIEF_MANAGED; - } - // 6 clicks: momentary mode - else if (event == EV_6clicks) { - blink_confirm(1); - set_state(momentary_state, 0); - return MISCHIEF_MANAGED; - } - // hold: go to lowest level - else if (event == EV_click1_hold) { - // don't start ramping immediately; - // give the user time to release at moon level - if (arg >= HOLD_TIMEOUT) { - set_state(steady_state, 1); - } - return MISCHIEF_MANAGED; - } - // hold, release quickly: go to lowest level - else if (event == EV_click1_hold_release) { - set_state(steady_state, 1); - return MISCHIEF_MANAGED; - } - // click, hold: go to highest level (for ramping down) - else if (event == EV_click2_hold) { - set_state(steady_state, MAX_LEVEL); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t steady_state(EventPtr event, uint16_t arg) { - uint8_t mode_min = ramp_smooth_floor; - uint8_t mode_max = ramp_smooth_ceil; - uint8_t ramp_step_size = 1; - if (ramp_style) { - mode_min = ramp_discrete_floor; - mode_max = ramp_discrete_ceil; - ramp_step_size = ramp_discrete_step_size; - } - - // turn LED on when we first enter the mode - if (event == EV_enter_state) { - // remember this level, unless it's moon or turbo - if ((arg > mode_min) && (arg < mode_max)) - memorized_level = arg; - // use the requested level even if not memorized - #ifdef USE_THERMAL_REGULATION - target_level = arg; - #endif - set_level(nearest_level(arg)); - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: go to/from highest level - else if (event == EV_2clicks) { - if (actual_level < MAX_LEVEL) { - memorized_level = actual_level; // in case we're on moon - #ifdef USE_THERMAL_REGULATION - target_level = MAX_LEVEL; - #endif - // true turbo, not the mode-specific ceiling - set_level(MAX_LEVEL); - } - else { - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return MISCHIEF_MANAGED; - } - // 3 clicks: toggle smooth vs discrete ramping - else if (event == EV_3clicks) { - ramp_style ^= 1; - memorized_level = nearest_level(memorized_level); - save_config(); - set_level(0); - delay_4ms(20/4); - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - // 4 clicks: configure this ramp mode - else if (event == EV_4clicks) { - set_state(ramp_config_state, 0); - return MISCHIEF_MANAGED; - } - // hold: change brightness (brighter) - else if (event == EV_click1_hold) { - // ramp slower in discrete mode - if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { - return MISCHIEF_MANAGED; - } - // TODO? make it ramp down instead, if already at max? - memorized_level = nearest_level((int16_t)actual_level + ramp_step_size); - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - || (memorized_level == mode_max))) { - set_level(0); - delay_4ms(8/4); - } - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - // click, hold: change brightness (dimmer) - else if (event == EV_click2_hold) { - // ramp slower in discrete mode - if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { - return MISCHIEF_MANAGED; - } - // TODO? make it ramp up instead, if already at min? - memorized_level = nearest_level((int16_t)actual_level - ramp_step_size); - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - || (memorized_level == mode_min))) { - set_level(0); - delay_4ms(8/4); - } - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - #ifdef USE_THERMAL_REGULATION - // TODO: test this on a real light - // overheating: drop by an amount proportional to how far we are above the ceiling - else if (event == EV_temperature_high) { - if (actual_level > MAX_LEVEL/4) { - uint8_t stepdown = actual_level - arg; - if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; - set_level(stepdown); - } - return MISCHIEF_MANAGED; - } - // underheating: increase slowly if we're lower than the target - // (proportional to how low we are) - else if (event == EV_temperature_low) { - if (actual_level < target_level) { - uint8_t stepup = actual_level + (arg>>1); - if (stepup > target_level) stepup = target_level; - set_level(stepup); - } - return MISCHIEF_MANAGED; - } - #endif - return EVENT_NOT_HANDLED; -} - - -uint8_t strobe_state(EventPtr event, uint16_t arg) { - if (event == EV_enter_state) { - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: rotate through strobe/flasher modes - else if (event == EV_2clicks) { - strobe_type = (strobe_type + 1) % NUM_STROBES; - interrupt_nice_delays(); - save_config(); - return MISCHIEF_MANAGED; - } - // hold: change speed (go faster) - // or change brightness (brighter) - else if (event == EV_click1_hold) { - if (strobe_type < 2) { - if ((arg & 1) == 0) { - if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --; - } - } - // biking mode brighter - else if (strobe_type == 3) { - if (bike_flasher_brightness < MAX_LEVEL/2) - bike_flasher_brightness ++; - set_level(bike_flasher_brightness); - } - return MISCHIEF_MANAGED; - } - // click, hold: change speed (go slower) - // or change brightness (dimmer) - else if (event == EV_click2_hold) { - if (strobe_type < 2) { - if ((arg & 1) == 0) { - if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++; - } - } - // biking mode dimmer - else if (strobe_type == 3) { - if (bike_flasher_brightness > 1) - bike_flasher_brightness --; - set_level(bike_flasher_brightness); - } - return MISCHIEF_MANAGED; - } - // release hold: save new strobe settings - else if ((event == EV_click1_hold_release) - || (event == EV_click2_hold_release)) { - save_config(); - return MISCHIEF_MANAGED; - } - #ifdef USE_LIGHTNING_MODE - // clock tick: bump the random seed - else if (event == EV_tick) { - pseudo_rand_seed += arg; - return MISCHIEF_MANAGED; - } - #endif - return EVENT_NOT_HANDLED; -} - - -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: goodnight mode - else if (event == EV_2clicks) { - set_state(goodnight_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - -uint8_t tempcheck_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: battcheck mode - else if (event == EV_2clicks) { - set_state(battcheck_state, 0); - return MISCHIEF_MANAGED; - } - // 3 clicks: thermal config mode - else if (event == EV_3clicks) { - set_state(thermal_config_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} -#endif - - -uint8_t beacon_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: tempcheck mode - else if (event == EV_2clicks) { - set_state(tempcheck_state, 0); - return MISCHIEF_MANAGED; - } - // 3 clicks: beacon config mode - else if (event == EV_3clicks) { - set_state(beacon_config_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL) -uint8_t goodnight_state(EventPtr event, uint16_t arg) { - static uint16_t ticks_since_stepdown = 0; - // blink on start - if (event == EV_enter_state) { - blink_confirm(4); - ticks_since_stepdown = 0; - set_level(GOODNIGHT_LEVEL); - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: beacon mode - else if (event == EV_2clicks) { - set_state(beacon_state, 0); - return MISCHIEF_MANAGED; - } - // tick: step down (maybe) or off (maybe) - else if (event == EV_tick) { - if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) { - ticks_since_stepdown = 0; - set_level(actual_level-1); - if (! actual_level) { - #if 0 // test blink, to help measure timing - set_level(MAX_LEVEL>>2); - delay_4ms(8/2); - set_level(0); - #endif - set_state(off_state, 0); - } - } - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t lockout_state(EventPtr event, uint16_t arg) { - // conserve power while locked out - // (allow staying awake long enough to exit, but otherwise - // be persistent about going back to sleep every few seconds - // even if the user keeps pressing the button) - if (event == EV_tick) { - static uint8_t ticks_spent_awake = 0; - ticks_spent_awake ++; - if (ticks_spent_awake > 180) { - ticks_spent_awake = 0; - go_to_standby = 1; - } - return MISCHIEF_MANAGED; - } - // 4 clicks: exit - else if (event == EV_4clicks) { - blink_confirm(2); - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t momentary_state(EventPtr event, uint16_t arg) { - if (event == EV_click1_press) { - set_level(memorized_level); - empty_event_sequence(); // don't attempt to parse multiple clicks - return MISCHIEF_MANAGED; - } - - else if (event == EV_release) { - set_level(0); - empty_event_sequence(); // don't attempt to parse multiple clicks - //go_to_standby = 1; // sleep while light is off - return MISCHIEF_MANAGED; - } - - // Sleep, dammit! (but wait a few seconds first) - // (because standby mode uses such little power that it can interfere - // with exiting via tailcap loosen+tighten unless you leave power - // disconnected for several seconds, so we want to be awake when that - // happens to speed up the process) - else if ((event == EV_tick) && (actual_level == 0)) { - if (arg > TICKS_PER_SECOND*15) { // sleep after 15 seconds - go_to_standby = 1; // sleep while light is off - } - return MISCHIEF_MANAGED; - } - - return EVENT_NOT_HANDLED; -} - - -uint8_t ramp_config_state(EventPtr event, uint16_t arg) { - static uint8_t config_step; - static uint8_t num_config_steps; - if (event == EV_enter_state) { - config_step = 0; - if (ramp_style) { - num_config_steps = 3; - } - else { - num_config_steps = 2; - } - set_level(0); - return MISCHIEF_MANAGED; - } - // advance forward through config steps - else if (event == EV_tick) { - if (config_step < num_config_steps) { - push_state(number_entry_state, config_step + 1); - } - else { - save_config(); - // TODO: blink out some sort of success pattern - // return to steady mode - set_state(steady_state, memorized_level); - } - return MISCHIEF_MANAGED; - } - // an option was set (return from number_entry_state) - else if (event == EV_reenter_state) { - #if 0 - // FIXME? this is a kludge which relies on the vars being consecutive - // in RAM and in the same order as declared - // ... and it doesn't work; it seems they're not consecutive :( - volatile uint8_t *dest; - if (ramp_style) dest = (&ramp_discrete_floor) + config_step; - else dest = (&ramp_smooth_floor) + config_step; - if (number_entry_value) - *dest = number_entry_value; - #else - switch (config_step) { - case 0: - if (number_entry_value) { - if (ramp_style) ramp_discrete_floor = number_entry_value; - else ramp_smooth_floor = number_entry_value; - } - break; - case 1: - if (number_entry_value) { - if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value; - else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value; - } - break; - case 2: - if (number_entry_value) - ramp_discrete_steps = number_entry_value; - break; - } - #endif - config_step ++; - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t thermal_config_state(EventPtr event, uint16_t arg) { - static uint8_t done = 0; - if (event == EV_enter_state) { - set_level(0); - done = 0; - return MISCHIEF_MANAGED; - } - // advance forward through config steps - else if (event == EV_tick) { - if (! done) push_state(number_entry_state, 1); - else { - save_config(); - // return to beacon mode - set_state(tempcheck_state, 0); - } - return MISCHIEF_MANAGED; - } - // an option was set (return from number_entry_state) - else if (event == EV_reenter_state) { - if (number_entry_value) therm_ceil = 30 + number_entry_value; - if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; - done = 1; - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t beacon_config_state(EventPtr event, uint16_t arg) { - static uint8_t done = 0; - if (event == EV_enter_state) { - set_level(0); - done = 0; - return MISCHIEF_MANAGED; - } - // advance forward through config steps - else if (event == EV_tick) { - if (! done) push_state(number_entry_state, 1); - else { - save_config(); - // return to beacon mode - set_state(beacon_state, 0); - } - return MISCHIEF_MANAGED; - } - // an option was set (return from number_entry_state) - else if (event == EV_reenter_state) { - if (number_entry_value) beacon_seconds = number_entry_value; - done = 1; - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t number_entry_state(EventPtr event, uint16_t arg) { - static uint8_t value; - static uint8_t blinks_left; - static uint8_t entry_step; - static uint16_t wait_ticks; - if (event == EV_enter_state) { - value = 0; - blinks_left = arg; - entry_step = 0; - wait_ticks = 0; - // TODO: blink out the 'arg' to show which option this is - return MISCHIEF_MANAGED; - } - // advance through the process: - // 0: wait a moment - // 1: blink out the 'arg' value - // 2: wait a moment - // 3: "buzz" while counting clicks - // 4: save and exit - else if (event == EV_tick) { - // wait a moment - if ((entry_step == 0) || (entry_step == 2)) { - if (wait_ticks < TICKS_PER_SECOND/2) - wait_ticks ++; - else { - entry_step ++; - wait_ticks = 0; - } - } - // blink out the option number - else if (entry_step == 1) { - if (blinks_left) { - if ((wait_ticks & 31) == 10) { - set_level(RAMP_SIZE/4); - } - else if ((wait_ticks & 31) == 20) { - set_level(0); - } - else if ((wait_ticks & 31) == 31) { - blinks_left --; - } - wait_ticks ++; - } - else { - entry_step ++; - wait_ticks = 0; - } - } - else if (entry_step == 3) { // buzz while waiting for a number to be entered - wait_ticks ++; - // buzz for N seconds after last event - if ((wait_ticks & 3) == 0) { - set_level(RAMP_SIZE/6); - } - else if ((wait_ticks & 3) == 2) { - set_level(RAMP_SIZE/8); - } - // time out after 3 seconds - if (wait_ticks > TICKS_PER_SECOND*3) { - //number_entry_value = value; - set_level(0); - entry_step ++; - } - } - else if (entry_step == 4) { - number_entry_value = value; - pop_state(); - } - return MISCHIEF_MANAGED; - } - // count clicks - else if (event == EV_click1_release) { - empty_event_sequence(); - if (entry_step == 3) { // only count during the "buzz" - value ++; - wait_ticks = 0; - // flash briefly - set_level(RAMP_SIZE/2); - delay_4ms(8/2); - set_level(0); - } - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -// find the ramp level closest to the target, -// using only the levels which are allowed in the current state -uint8_t nearest_level(int16_t target) { - // bounds check - // using int16_t here saves us a bunch of logic elsewhere, - // by allowing us to correct for numbers < 0 or > 255 in one central place - uint8_t mode_min = ramp_smooth_floor; - uint8_t mode_max = ramp_smooth_ceil; - if (ramp_style) { - mode_min = ramp_discrete_floor; - mode_max = ramp_discrete_ceil; - } - if (target < mode_min) return mode_min; - if (target > mode_max) return mode_max; - // the rest isn't relevant for smooth ramping - if (! ramp_style) return target; - - uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor; - ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1); - uint8_t this_level = ramp_discrete_floor; - - for(uint8_t i=0; i>1)) - return this_level; - } - return this_level; -} - - -void blink_confirm(uint8_t num) { - for (; num>0; num--) { - set_level(MAX_LEVEL/4); - delay_4ms(10/4); - set_level(0); - delay_4ms(100/4); - } -} - - -#ifdef USE_LIGHTNING_MODE -uint8_t pseudo_rand() { - static uint16_t offset = 1024; - // loop from 1024 to 4095 - offset = ((offset + 1) & 0x0fff) | 0x0400; - pseudo_rand_seed += 0b01010101; // 85 - return pgm_read_byte(offset) + pseudo_rand_seed; -} -#endif - - -void load_config() { - if (load_eeprom()) { - ramp_style = eeprom[0]; - ramp_smooth_floor = eeprom[1]; - ramp_smooth_ceil = eeprom[2]; - ramp_discrete_floor = eeprom[3]; - ramp_discrete_ceil = eeprom[4]; - ramp_discrete_steps = eeprom[5]; - strobe_type = eeprom[6]; // TODO: move this to eeprom_wl? - strobe_delays[0] = eeprom[7]; - strobe_delays[1] = eeprom[8]; - bike_flasher_brightness = eeprom[9]; - beacon_seconds = eeprom[10]; - therm_ceil = eeprom[11]; - } -} - -void save_config() { - eeprom[0] = ramp_style; - eeprom[1] = ramp_smooth_floor; - eeprom[2] = ramp_smooth_ceil; - eeprom[3] = ramp_discrete_floor; - eeprom[4] = ramp_discrete_ceil; - eeprom[5] = ramp_discrete_steps; - eeprom[6] = strobe_type; // TODO: move this to eeprom_wl? - eeprom[7] = strobe_delays[0]; - eeprom[8] = strobe_delays[1]; - eeprom[9] = bike_flasher_brightness; - eeprom[10] = beacon_seconds; - eeprom[11] = therm_ceil; - - save_eeprom(); -} - - -void low_voltage() { - // "step down" from strobe to something low - if (current_state == strobe_state) { - set_state(steady_state, RAMP_SIZE/6); - } - // in normal mode, step down by half or turn off - else if (current_state == steady_state) { - if (actual_level > 1) { - set_level(actual_level >> 1); - } - else { - set_state(off_state, 0); - } - } - // all other modes, just turn off when voltage is low - else { - set_state(off_state, 0); - } -} - - -void setup() { - // blink at power-on to let user know power is connected - set_level(RAMP_SIZE/8); - delay_4ms(3); - set_level(0); - - load_config(); - - push_state(off_state, 0); -} - - -void loop() { - - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - - if (current_state == strobe_state) { - // party / tactical strobe - if (strobe_type < 2) { - set_level(MAX_LEVEL); - if (strobe_type == 0) { // party strobe - if (strobe_delays[strobe_type] < 42) delay_zero(); - else delay_ms(1); - } else { //tactical strobe - nice_delay_ms(strobe_delays[strobe_type] >> 1); - } - set_level(0); - nice_delay_ms(strobe_delays[strobe_type]); - } - #ifdef USE_LIGHTNING_MODE - // lightning storm - else if (strobe_type == 2) { - int16_t brightness; - uint16_t rand_time; - - // turn the emitter on at a random level, - // for a random amount of time between 1ms and 32ms - rand_time = 1 << (pseudo_rand() % 6); - brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64 - brightness += 1 << (pseudo_rand()&0x03); // 2 to 80 now - brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) - if (brightness > MAX_LEVEL) brightness = MAX_LEVEL; - set_level(brightness); - if (! nice_delay_ms(rand_time)) return; - - // decrease the brightness somewhat more gradually, like lightning - uint8_t stepdown = brightness >> 3; - if (stepdown < 1) stepdown = 1; - while(brightness > 1) { - if (! nice_delay_ms(rand_time)) return; - brightness -= stepdown; - if (brightness < 0) brightness = 0; - set_level(brightness); - } - - // turn the emitter off, - // for a random amount of time between 1ms and 8192ms - // (with a low bias) - rand_time = 1<<(pseudo_rand()%13); - rand_time += pseudo_rand()%rand_time; - set_level(0); - nice_delay_ms(rand_time); - - } - #endif - // bike flasher - else if (strobe_type == 3) { - uint8_t burst = bike_flasher_brightness << 1; - for(uint8_t i=0; i<4; i++) { - set_level(burst); - if (! nice_delay_ms(5)) return; - set_level(bike_flasher_brightness); - if (! nice_delay_ms(65)) return; - } - if (! nice_delay_ms(720)) return; - } - } - - #ifdef USE_BATTCHECK - else if (current_state == battcheck_state) { - battcheck(); - } - else if (current_state == tempcheck_state) { - blink_num(temperature>>2); - nice_delay_ms(1000); - } - // TODO: blink out therm_ceil during thermal_config_state - #endif - - else if (current_state == beacon_state) { - set_level(memorized_level); - if (! nice_delay_ms(500)) return; - set_level(0); - nice_delay_ms(((beacon_seconds) * 1000) - 500); - } -} diff --git a/spaghetti-monster/anduril.txt b/spaghetti-monster/anduril.txt deleted file mode 100644 index bb0f5e2..0000000 --- a/spaghetti-monster/anduril.txt +++ /dev/null @@ -1,100 +0,0 @@ -From off: - * 1 click: memorized level - * Hold: lowest level then ramp up - * 2 clicks: highest ramp level - * Click, hold: highest level then ramp down - * 3 clicks: battcheck mode - (battcheck, goodnight, beacon, tempcheck) - * 4 clicks: lock-out - * 5 clicks: strobe modes - (bike flasher, party strobe, tactical strobe, lightning storm mode) - (remembers which you last used) - * 6 clicks: momentary mode (disconnect power to exit) - -In steady mode: - * 1 click: off - * Hold: ramp up - * Click, hold: ramp down - * 2 clicks: to/from turbo (actual turbo, not just highest ramp level) - * 3 clicks: toggle smooth vs discrete ramping - * 4 clicks: configure current ramp - -Smooth ramp config mode: - - Setting 1: memory on/off (not implemented yet) - * Setting 2: low end - (click N times to set ramp floor to level N) - * Setting 3: high end - (click N times to set ramp ceiling to level "151 - N") - -Discrete ramp config mode: - - Setting 1: memory on/off (not implemented yet) - * Setting 2: low end - (click N times to set ramp floor to level N) - * Setting 3: high end - (click N times to set ramp ceiling to level "151 - N") - * Setting 4: number of levels in discrete ramp - (click N times to make discrete mode have N stair-steps) - (minimum 2, maximum 150) - -Bike flasher: - * 1 click: off - * 2 clicks: party strobe - * Hold: brighter - * Click, hold: dimmer - -Party / Tactical strobe modes: - * 1 click: off - * Hold: change speed (faster) - * Click, hold: change speed (slower) - * 2 clicks: next strobe mode - (bike flasher, party strobe, tactical strobe, lightning storm mode) - (TODO: random/police strobe?) - -Lightning storm mode: - * 1 click: off - * 2 clicks: bike flasher - -Battcheck mode: - * 1 click: off - * 2 clicks: goodnight mode - -Goodnight mode: - * 1 click: off - * 2 clicks: beacon mode - -Beacon mode: - * 1 click: off - * 2 clicks: tempcheck mode - * 3 clicks: configure time between pulses - -Beacon config mode: - * At buzz, click N times to set beacon frequency to N seconds. - -Tempcheck mode: - * 1 click: off - * 2 clicks: battcheck mode - * 3 clicks: thermal config mode - - Hold: thermal calibration mode - -Thermal config mode: - * At buzz, click N times to set thermal limit to roughly 30 C + N. - -Thermal calibration mode: - - Hold until hot: set new ceiling value - - ... don't hold: blink out current ceiling value and exit - -Momentary mode: - * Press button: Light on (at memorized level). - * Release button: Light off. - * To exit, disconnect power. (loosen/tighten the tailcap) - -TODO: - * save settings in eeprom - - decide on "hold until hot" or "click N times" for thermal config mode - - test thermal regulation on an actual light - - improve thermal regulation - - a way to blink out the firmware version? - - indicator LED support - - a way to configure indicator LED behavior? - * add goodnight mode? - * add lightning mode? diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c new file mode 100644 index 0000000..998c0c5 --- /dev/null +++ b/spaghetti-monster/anduril/anduril.c @@ -0,0 +1,961 @@ +/* + * Anduril: Narsil-inspired UI for SpaghettiMonster. + * (Anduril is Aragorn's sword, the blade Narsil reforged) + * + * 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_MS +#define USE_DELAY_4MS +#define USE_DELAY_ZERO +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define USE_BATTCHECK +#define BATTCHECK_VpT +#define MAX_CLICKS 6 +#define USE_EEPROM +#define EEPROM_BYTES 12 +#include "spaghetti-monster.h" + +// Options specific to this UI (not inherited from SpaghettiMonster) +#define USE_LIGHTNING_MODE +// set this a bit high, since the bottom 2 ramp levels might not emit any light at all +#define GOODNIGHT_TIME 65 // minutes (approximately) +#define GOODNIGHT_LEVEL 24 // ~11 lm + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +// ramping mode and its related config mode +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t ramp_config_state(EventPtr event, uint16_t arg); +// party and tactical strobes +uint8_t strobe_state(EventPtr event, uint16_t arg); +#ifdef USE_LIGHTNING_MODE +#define NUM_STROBES 4 +#else +#define NUM_STROBES 3 +#endif +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t tempcheck_state(EventPtr event, uint16_t arg); +uint8_t thermal_config_state(EventPtr event, uint16_t arg); +#endif +// 1-hour ramp down from low, then automatic off +uint8_t goodnight_state(EventPtr event, uint16_t arg); +// beacon mode and its related config mode +uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t beacon_config_state(EventPtr event, uint16_t arg); +// soft lockout +uint8_t lockout_state(EventPtr event, uint16_t arg); +// momentary / signalling mode +uint8_t momentary_state(EventPtr event, uint16_t arg); + +// general helper function for config modes +uint8_t number_entry_state(EventPtr event, uint16_t arg); +// return value from number_entry_state() +volatile uint8_t number_entry_value; + +void blink_confirm(uint8_t num); + +// remember stuff even after battery was changed +void load_config(); +void save_config(); + +// brightness control +uint8_t memorized_level = MAX_1x7135; +// smooth vs discrete ramping +volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete +volatile uint8_t ramp_smooth_floor = 5; +volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30; +volatile uint8_t ramp_discrete_floor = 20; +volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30; +volatile uint8_t ramp_discrete_steps = 7; +uint8_t ramp_discrete_step_size; // don't set this + +// calculate the nearest ramp level which would be valid at the moment +// (is a no-op for smooth ramp, but limits discrete ramp to only the +// correct levels for the user's config) +uint8_t nearest_level(int16_t target); + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +// strobe timing +volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe +volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 == lightning storm, 3 == bike flasher + +// bike mode config options +volatile uint8_t bike_flasher_brightness = MAX_1x7135; + +#ifdef USE_LIGHTNING_MODE +volatile uint8_t pseudo_rand_seed = 0; +uint8_t pseudo_rand(); +#endif + +// beacon timing +volatile uint8_t beacon_seconds = 2; + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + go_to_standby = 1; + return MISCHIEF_MANAGED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(nearest_level(1)); + return MISCHIEF_MANAGED; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(nearest_level(memorized_level)); + return MISCHIEF_MANAGED; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } + // 2 clicks (initial press): off, to prep for later events + else if (event == EV_click2_press) { + set_level(0); + return MISCHIEF_MANAGED; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, nearest_level(MAX_LEVEL)); + return MISCHIEF_MANAGED; + } + #ifdef USE_BATTCHECK + // 3 clicks: battcheck mode + else if (event == EV_3clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + #endif + // 4 clicks: soft lockout + else if (event == EV_4clicks) { + blink_confirm(5); + set_state(lockout_state, 0); + return MISCHIEF_MANAGED; + } + // 5 clicks: strobe mode + else if (event == EV_5clicks) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } + // 6 clicks: momentary mode + else if (event == EV_6clicks) { + blink_confirm(1); + set_state(momentary_state, 0); + return MISCHIEF_MANAGED; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at moon level + if (arg >= HOLD_TIMEOUT) { + set_state(steady_state, 1); + } + return MISCHIEF_MANAGED; + } + // hold, release quickly: go to lowest level + else if (event == EV_click1_hold_release) { + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } + // click, hold: go to highest level (for ramping down) + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t steady_state(EventPtr event, uint16_t arg) { + uint8_t mode_min = ramp_smooth_floor; + uint8_t mode_max = ramp_smooth_ceil; + uint8_t ramp_step_size = 1; + if (ramp_style) { + mode_min = ramp_discrete_floor; + mode_max = ramp_discrete_ceil; + ramp_step_size = ramp_discrete_step_size; + } + + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > mode_min) && (arg < mode_max)) + memorized_level = arg; + // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(nearest_level(arg)); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + // true turbo, not the mode-specific ceiling + set_level(MAX_LEVEL); + } + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return MISCHIEF_MANAGED; + } + // 3 clicks: toggle smooth vs discrete ramping + else if (event == EV_3clicks) { + ramp_style ^= 1; + memorized_level = nearest_level(memorized_level); + save_config(); + set_level(0); + delay_4ms(20/4); + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // 4 clicks: configure this ramp mode + else if (event == EV_4clicks) { + set_state(ramp_config_state, 0); + return MISCHIEF_MANAGED; + } + // hold: change brightness (brighter) + else if (event == EV_click1_hold) { + // ramp slower in discrete mode + if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { + return MISCHIEF_MANAGED; + } + // TODO? make it ramp down instead, if already at max? + memorized_level = nearest_level((int16_t)actual_level + ramp_step_size); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == mode_max))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // click, hold: change brightness (dimmer) + else if (event == EV_click2_hold) { + // ramp slower in discrete mode + if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { + return MISCHIEF_MANAGED; + } + // TODO? make it ramp up instead, if already at min? + memorized_level = nearest_level((int16_t)actual_level - ramp_step_size); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == mode_min))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + #ifdef USE_THERMAL_REGULATION + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return MISCHIEF_MANAGED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return MISCHIEF_MANAGED; + } + #endif + return EVENT_NOT_HANDLED; +} + + +uint8_t strobe_state(EventPtr event, uint16_t arg) { + if (event == EV_enter_state) { + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: rotate through strobe/flasher modes + else if (event == EV_2clicks) { + strobe_type = (strobe_type + 1) % NUM_STROBES; + interrupt_nice_delays(); + save_config(); + return MISCHIEF_MANAGED; + } + // hold: change speed (go faster) + // or change brightness (brighter) + else if (event == EV_click1_hold) { + if (strobe_type < 2) { + if ((arg & 1) == 0) { + if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --; + } + } + // biking mode brighter + else if (strobe_type == 3) { + if (bike_flasher_brightness < MAX_LEVEL/2) + bike_flasher_brightness ++; + set_level(bike_flasher_brightness); + } + return MISCHIEF_MANAGED; + } + // click, hold: change speed (go slower) + // or change brightness (dimmer) + else if (event == EV_click2_hold) { + if (strobe_type < 2) { + if ((arg & 1) == 0) { + if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++; + } + } + // biking mode dimmer + else if (strobe_type == 3) { + if (bike_flasher_brightness > 1) + bike_flasher_brightness --; + set_level(bike_flasher_brightness); + } + return MISCHIEF_MANAGED; + } + // release hold: save new strobe settings + else if ((event == EV_click1_hold_release) + || (event == EV_click2_hold_release)) { + save_config(); + return MISCHIEF_MANAGED; + } + #ifdef USE_LIGHTNING_MODE + // clock tick: bump the random seed + else if (event == EV_tick) { + pseudo_rand_seed += arg; + return MISCHIEF_MANAGED; + } + #endif + return EVENT_NOT_HANDLED; +} + + +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: goodnight mode + else if (event == EV_2clicks) { + set_state(goodnight_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t tempcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: battcheck mode + else if (event == EV_2clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + // 3 clicks: thermal config mode + else if (event == EV_3clicks) { + set_state(thermal_config_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} +#endif + + +uint8_t beacon_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: tempcheck mode + else if (event == EV_2clicks) { + set_state(tempcheck_state, 0); + return MISCHIEF_MANAGED; + } + // 3 clicks: beacon config mode + else if (event == EV_3clicks) { + set_state(beacon_config_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL) +uint8_t goodnight_state(EventPtr event, uint16_t arg) { + static uint16_t ticks_since_stepdown = 0; + // blink on start + if (event == EV_enter_state) { + blink_confirm(4); + ticks_since_stepdown = 0; + set_level(GOODNIGHT_LEVEL); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: beacon mode + else if (event == EV_2clicks) { + set_state(beacon_state, 0); + return MISCHIEF_MANAGED; + } + // tick: step down (maybe) or off (maybe) + else if (event == EV_tick) { + if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) { + ticks_since_stepdown = 0; + set_level(actual_level-1); + if (! actual_level) { + #if 0 // test blink, to help measure timing + set_level(MAX_LEVEL>>2); + delay_4ms(8/2); + set_level(0); + #endif + set_state(off_state, 0); + } + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + // conserve power while locked out + // (allow staying awake long enough to exit, but otherwise + // be persistent about going back to sleep every few seconds + // even if the user keeps pressing the button) + if (event == EV_tick) { + static uint8_t ticks_spent_awake = 0; + ticks_spent_awake ++; + if (ticks_spent_awake > 180) { + ticks_spent_awake = 0; + go_to_standby = 1; + } + return MISCHIEF_MANAGED; + } + // 4 clicks: exit + else if (event == EV_4clicks) { + blink_confirm(2); + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + if (event == EV_click1_press) { + set_level(memorized_level); + empty_event_sequence(); // don't attempt to parse multiple clicks + return MISCHIEF_MANAGED; + } + + else if (event == EV_release) { + set_level(0); + empty_event_sequence(); // don't attempt to parse multiple clicks + //go_to_standby = 1; // sleep while light is off + return MISCHIEF_MANAGED; + } + + // Sleep, dammit! (but wait a few seconds first) + // (because standby mode uses such little power that it can interfere + // with exiting via tailcap loosen+tighten unless you leave power + // disconnected for several seconds, so we want to be awake when that + // happens to speed up the process) + else if ((event == EV_tick) && (actual_level == 0)) { + if (arg > TICKS_PER_SECOND*15) { // sleep after 15 seconds + go_to_standby = 1; // sleep while light is off + } + return MISCHIEF_MANAGED; + } + + return EVENT_NOT_HANDLED; +} + + +uint8_t ramp_config_state(EventPtr event, uint16_t arg) { + static uint8_t config_step; + static uint8_t num_config_steps; + if (event == EV_enter_state) { + config_step = 0; + if (ramp_style) { + num_config_steps = 3; + } + else { + num_config_steps = 2; + } + set_level(0); + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (config_step < num_config_steps) { + push_state(number_entry_state, config_step + 1); + } + else { + save_config(); + // TODO: blink out some sort of success pattern + // return to steady mode + set_state(steady_state, memorized_level); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + #if 0 + // FIXME? this is a kludge which relies on the vars being consecutive + // in RAM and in the same order as declared + // ... and it doesn't work; it seems they're not consecutive :( + volatile uint8_t *dest; + if (ramp_style) dest = (&ramp_discrete_floor) + config_step; + else dest = (&ramp_smooth_floor) + config_step; + if (number_entry_value) + *dest = number_entry_value; + #else + switch (config_step) { + case 0: + if (number_entry_value) { + if (ramp_style) ramp_discrete_floor = number_entry_value; + else ramp_smooth_floor = number_entry_value; + } + break; + case 1: + if (number_entry_value) { + if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value; + else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value; + } + break; + case 2: + if (number_entry_value) + ramp_discrete_steps = number_entry_value; + break; + } + #endif + config_step ++; + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t thermal_config_state(EventPtr event, uint16_t arg) { + static uint8_t done = 0; + if (event == EV_enter_state) { + set_level(0); + done = 0; + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (! done) push_state(number_entry_state, 1); + else { + save_config(); + // return to beacon mode + set_state(tempcheck_state, 0); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + if (number_entry_value) therm_ceil = 30 + number_entry_value; + if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; + done = 1; + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t beacon_config_state(EventPtr event, uint16_t arg) { + static uint8_t done = 0; + if (event == EV_enter_state) { + set_level(0); + done = 0; + return MISCHIEF_MANAGED; + } + // advance forward through config steps + else if (event == EV_tick) { + if (! done) push_state(number_entry_state, 1); + else { + save_config(); + // return to beacon mode + set_state(beacon_state, 0); + } + return MISCHIEF_MANAGED; + } + // an option was set (return from number_entry_state) + else if (event == EV_reenter_state) { + if (number_entry_value) beacon_seconds = number_entry_value; + done = 1; + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t number_entry_state(EventPtr event, uint16_t arg) { + static uint8_t value; + static uint8_t blinks_left; + static uint8_t entry_step; + static uint16_t wait_ticks; + if (event == EV_enter_state) { + value = 0; + blinks_left = arg; + entry_step = 0; + wait_ticks = 0; + // TODO: blink out the 'arg' to show which option this is + return MISCHIEF_MANAGED; + } + // advance through the process: + // 0: wait a moment + // 1: blink out the 'arg' value + // 2: wait a moment + // 3: "buzz" while counting clicks + // 4: save and exit + else if (event == EV_tick) { + // wait a moment + if ((entry_step == 0) || (entry_step == 2)) { + if (wait_ticks < TICKS_PER_SECOND/2) + wait_ticks ++; + else { + entry_step ++; + wait_ticks = 0; + } + } + // blink out the option number + else if (entry_step == 1) { + if (blinks_left) { + if ((wait_ticks & 31) == 10) { + set_level(RAMP_SIZE/4); + } + else if ((wait_ticks & 31) == 20) { + set_level(0); + } + else if ((wait_ticks & 31) == 31) { + blinks_left --; + } + wait_ticks ++; + } + else { + entry_step ++; + wait_ticks = 0; + } + } + else if (entry_step == 3) { // buzz while waiting for a number to be entered + wait_ticks ++; + // buzz for N seconds after last event + if ((wait_ticks & 3) == 0) { + set_level(RAMP_SIZE/6); + } + else if ((wait_ticks & 3) == 2) { + set_level(RAMP_SIZE/8); + } + // time out after 3 seconds + if (wait_ticks > TICKS_PER_SECOND*3) { + //number_entry_value = value; + set_level(0); + entry_step ++; + } + } + else if (entry_step == 4) { + number_entry_value = value; + pop_state(); + } + return MISCHIEF_MANAGED; + } + // count clicks + else if (event == EV_click1_release) { + empty_event_sequence(); + if (entry_step == 3) { // only count during the "buzz" + value ++; + wait_ticks = 0; + // flash briefly + set_level(RAMP_SIZE/2); + delay_4ms(8/2); + set_level(0); + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +// find the ramp level closest to the target, +// using only the levels which are allowed in the current state +uint8_t nearest_level(int16_t target) { + // bounds check + // using int16_t here saves us a bunch of logic elsewhere, + // by allowing us to correct for numbers < 0 or > 255 in one central place + uint8_t mode_min = ramp_smooth_floor; + uint8_t mode_max = ramp_smooth_ceil; + if (ramp_style) { + mode_min = ramp_discrete_floor; + mode_max = ramp_discrete_ceil; + } + if (target < mode_min) return mode_min; + if (target > mode_max) return mode_max; + // the rest isn't relevant for smooth ramping + if (! ramp_style) return target; + + uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor; + ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1); + uint8_t this_level = ramp_discrete_floor; + + for(uint8_t i=0; i>1)) + return this_level; + } + return this_level; +} + + +void blink_confirm(uint8_t num) { + for (; num>0; num--) { + set_level(MAX_LEVEL/4); + delay_4ms(10/4); + set_level(0); + delay_4ms(100/4); + } +} + + +#ifdef USE_LIGHTNING_MODE +uint8_t pseudo_rand() { + static uint16_t offset = 1024; + // loop from 1024 to 4095 + offset = ((offset + 1) & 0x0fff) | 0x0400; + pseudo_rand_seed += 0b01010101; // 85 + return pgm_read_byte(offset) + pseudo_rand_seed; +} +#endif + + +void load_config() { + if (load_eeprom()) { + ramp_style = eeprom[0]; + ramp_smooth_floor = eeprom[1]; + ramp_smooth_ceil = eeprom[2]; + ramp_discrete_floor = eeprom[3]; + ramp_discrete_ceil = eeprom[4]; + ramp_discrete_steps = eeprom[5]; + strobe_type = eeprom[6]; // TODO: move this to eeprom_wl? + strobe_delays[0] = eeprom[7]; + strobe_delays[1] = eeprom[8]; + bike_flasher_brightness = eeprom[9]; + beacon_seconds = eeprom[10]; + therm_ceil = eeprom[11]; + } +} + +void save_config() { + eeprom[0] = ramp_style; + eeprom[1] = ramp_smooth_floor; + eeprom[2] = ramp_smooth_ceil; + eeprom[3] = ramp_discrete_floor; + eeprom[4] = ramp_discrete_ceil; + eeprom[5] = ramp_discrete_steps; + eeprom[6] = strobe_type; // TODO: move this to eeprom_wl? + eeprom[7] = strobe_delays[0]; + eeprom[8] = strobe_delays[1]; + eeprom[9] = bike_flasher_brightness; + eeprom[10] = beacon_seconds; + eeprom[11] = therm_ceil; + + save_eeprom(); +} + + +void low_voltage() { + // "step down" from strobe to something low + if (current_state == strobe_state) { + set_state(steady_state, RAMP_SIZE/6); + } + // in normal mode, step down by half or turn off + else if (current_state == steady_state) { + if (actual_level > 1) { + set_level(actual_level >> 1); + } + else { + set_state(off_state, 0); + } + } + // all other modes, just turn off when voltage is low + else { + set_state(off_state, 0); + } +} + + +void setup() { + // blink at power-on to let user know power is connected + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + load_config(); + + push_state(off_state, 0); +} + + +void loop() { + + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + + if (current_state == strobe_state) { + // party / tactical strobe + if (strobe_type < 2) { + set_level(MAX_LEVEL); + if (strobe_type == 0) { // party strobe + if (strobe_delays[strobe_type] < 42) delay_zero(); + else delay_ms(1); + } else { //tactical strobe + nice_delay_ms(strobe_delays[strobe_type] >> 1); + } + set_level(0); + nice_delay_ms(strobe_delays[strobe_type]); + } + #ifdef USE_LIGHTNING_MODE + // lightning storm + else if (strobe_type == 2) { + int16_t brightness; + uint16_t rand_time; + + // turn the emitter on at a random level, + // for a random amount of time between 1ms and 32ms + rand_time = 1 << (pseudo_rand() % 6); + brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64 + brightness += 1 << (pseudo_rand()&0x03); // 2 to 80 now + brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) + if (brightness > MAX_LEVEL) brightness = MAX_LEVEL; + set_level(brightness); + if (! nice_delay_ms(rand_time)) return; + + // decrease the brightness somewhat more gradually, like lightning + uint8_t stepdown = brightness >> 3; + if (stepdown < 1) stepdown = 1; + while(brightness > 1) { + if (! nice_delay_ms(rand_time)) return; + brightness -= stepdown; + if (brightness < 0) brightness = 0; + set_level(brightness); + } + + // turn the emitter off, + // for a random amount of time between 1ms and 8192ms + // (with a low bias) + rand_time = 1<<(pseudo_rand()%13); + rand_time += pseudo_rand()%rand_time; + set_level(0); + nice_delay_ms(rand_time); + + } + #endif + // bike flasher + else if (strobe_type == 3) { + uint8_t burst = bike_flasher_brightness << 1; + for(uint8_t i=0; i<4; i++) { + set_level(burst); + if (! nice_delay_ms(5)) return; + set_level(bike_flasher_brightness); + if (! nice_delay_ms(65)) return; + } + if (! nice_delay_ms(720)) return; + } + } + + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + battcheck(); + } + else if (current_state == tempcheck_state) { + blink_num(temperature>>2); + nice_delay_ms(1000); + } + // TODO: blink out therm_ceil during thermal_config_state + #endif + + else if (current_state == beacon_state) { + set_level(memorized_level); + if (! nice_delay_ms(500)) return; + set_level(0); + nice_delay_ms(((beacon_seconds) * 1000) - 500); + } +} diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt new file mode 100644 index 0000000..bb0f5e2 --- /dev/null +++ b/spaghetti-monster/anduril/anduril.txt @@ -0,0 +1,100 @@ +From off: + * 1 click: memorized level + * Hold: lowest level then ramp up + * 2 clicks: highest ramp level + * Click, hold: highest level then ramp down + * 3 clicks: battcheck mode + (battcheck, goodnight, beacon, tempcheck) + * 4 clicks: lock-out + * 5 clicks: strobe modes + (bike flasher, party strobe, tactical strobe, lightning storm mode) + (remembers which you last used) + * 6 clicks: momentary mode (disconnect power to exit) + +In steady mode: + * 1 click: off + * Hold: ramp up + * Click, hold: ramp down + * 2 clicks: to/from turbo (actual turbo, not just highest ramp level) + * 3 clicks: toggle smooth vs discrete ramping + * 4 clicks: configure current ramp + +Smooth ramp config mode: + - Setting 1: memory on/off (not implemented yet) + * Setting 2: low end + (click N times to set ramp floor to level N) + * Setting 3: high end + (click N times to set ramp ceiling to level "151 - N") + +Discrete ramp config mode: + - Setting 1: memory on/off (not implemented yet) + * Setting 2: low end + (click N times to set ramp floor to level N) + * Setting 3: high end + (click N times to set ramp ceiling to level "151 - N") + * Setting 4: number of levels in discrete ramp + (click N times to make discrete mode have N stair-steps) + (minimum 2, maximum 150) + +Bike flasher: + * 1 click: off + * 2 clicks: party strobe + * Hold: brighter + * Click, hold: dimmer + +Party / Tactical strobe modes: + * 1 click: off + * Hold: change speed (faster) + * Click, hold: change speed (slower) + * 2 clicks: next strobe mode + (bike flasher, party strobe, tactical strobe, lightning storm mode) + (TODO: random/police strobe?) + +Lightning storm mode: + * 1 click: off + * 2 clicks: bike flasher + +Battcheck mode: + * 1 click: off + * 2 clicks: goodnight mode + +Goodnight mode: + * 1 click: off + * 2 clicks: beacon mode + +Beacon mode: + * 1 click: off + * 2 clicks: tempcheck mode + * 3 clicks: configure time between pulses + +Beacon config mode: + * At buzz, click N times to set beacon frequency to N seconds. + +Tempcheck mode: + * 1 click: off + * 2 clicks: battcheck mode + * 3 clicks: thermal config mode + - Hold: thermal calibration mode + +Thermal config mode: + * At buzz, click N times to set thermal limit to roughly 30 C + N. + +Thermal calibration mode: + - Hold until hot: set new ceiling value + - ... don't hold: blink out current ceiling value and exit + +Momentary mode: + * Press button: Light on (at memorized level). + * Release button: Light off. + * To exit, disconnect power. (loosen/tighten the tailcap) + +TODO: + * save settings in eeprom + - decide on "hold until hot" or "click N times" for thermal config mode + - test thermal regulation on an actual light + - improve thermal regulation + - a way to blink out the firmware version? + - indicator LED support + - a way to configure indicator LED behavior? + * add goodnight mode? + * add lightning mode? diff --git a/spaghetti-monster/baton-simpler.c b/spaghetti-monster/baton-simpler.c deleted file mode 100644 index a6ddf36..0000000 --- a/spaghetti-monster/baton-simpler.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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_DRIVER -#define USE_LVP -#define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 45 -#define USE_DELAY_MS -#include "spaghetti-monster.h" - -// FSM states -uint8_t off_state(EventPtr event, uint16_t arg); -uint8_t steady_state(EventPtr event, uint16_t arg); -uint8_t lockout_state(EventPtr event, uint16_t arg); - -// brightness control -uint8_t memorized_level = 1; -uint8_t actual_level = 0; -#ifdef USE_THERMAL_REGULATION -uint8_t target_level = 0; -#endif - -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - -// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 -uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, }; -uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, }; -#define MAX_LEVEL (sizeof(pwm1_levels)-1) - -// set LED brightness -void set_level(uint8_t lvl) { - actual_level = lvl; - PWM1_LVL = pwm1_levels[lvl]; - PWM2_LVL = pwm2_levels[lvl]; -} - -uint8_t off_state(EventPtr event, uint16_t arg) { - // turn emitter off when entering state - if (event == EV_enter_state) { - go_to_standby = 1; // sleep while off (lower power use) - return EVENT_HANDLED; - } - // hold (initially): go to lowest level, but allow abort for regular click - else if (event == EV_click1_press) { - set_level(0); - return EVENT_HANDLED; - } - // 1 click (before timeout): go to memorized level, but allow abort for double click - else if (event == EV_click1_release) { - set_level(memorized_level); - return EVENT_HANDLED; - } - // 1 click: regular mode - else if (event == EV_1click) { - set_state(steady_state, memorized_level); - return EVENT_HANDLED; - } - // 2 clicks: highest mode - else if (event == EV_2clicks) { - set_state(steady_state, MAX_LEVEL); - return EVENT_HANDLED; - } - // 4 clicks: soft lockout - else if (event == EV_4clicks) { - set_state(lockout_state, 0); - return EVENT_HANDLED; - } - // hold: go to lowest level - else if (event == EV_click1_hold) { - set_state(steady_state, 0); - return EVENT_HANDLED; - } - return EVENT_NOT_HANDLED; -} - -uint8_t steady_state(EventPtr event, uint16_t arg) { - // turn LED on when we first enter the mode - if (event == EV_enter_state) { - // remember this level, unless it's moon or turbo - if ((arg > 0) && (arg < MAX_LEVEL)) - memorized_level = arg; - #ifdef USE_THERMAL_REGULATION - target_level = arg; - #endif - set_level(arg); - return EVENT_HANDLED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return EVENT_HANDLED; - } - // 2 clicks: go to/from highest level - else if (event == EV_2clicks) { - if (actual_level < MAX_LEVEL) { // go to turbo - memorized_level = actual_level; // in case we're on moon - #ifdef USE_THERMAL_REGULATION - target_level = MAX_LEVEL; - #endif - set_level(MAX_LEVEL); - } - else { // return from turbo - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return EVENT_HANDLED; - } - // hold: change brightness - else if (event == EV_click1_hold) { - if ((arg % HOLD_TIMEOUT) == 0) { - memorized_level = (actual_level+1) % (MAX_LEVEL+1); - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return EVENT_HANDLED; - } - #ifdef USE_THERMAL_REGULATION - // overheating: drop by 1 level - else if (event == EV_temperature_high) { - if (actual_level > 1) { - set_level(actual_level - 1); - } - return EVENT_HANDLED; - } - // underheating: increase by 1 level if we're lower than the target - else if (event == EV_temperature_low) { - if (actual_level < target_level) { - set_level(actual_level + 1); - } - return EVENT_HANDLED; - } - #endif - return EVENT_NOT_HANDLED; -} - -uint8_t lockout_state(EventPtr event, uint16_t arg) { - // stay asleep while locked, but allow waking long enough to click 4 times - if (event == EV_tick) { - static uint8_t ticks_spent_awake = 0; - ticks_spent_awake ++; - PWM1_LVL = 0; PWM2_LVL = 0; - if (ticks_spent_awake > 3 * TICKS_PER_SECOND) { - ticks_spent_awake = 0; - go_to_standby = 1; - } - return MISCHIEF_MANAGED; - } - // 4 clicks: exit - else if (event == EV_4clicks) { - set_state(steady_state, 1); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - -void low_voltage() { - // step down by one level or turn off - if (actual_level > 0) { - set_level(actual_level - 1); - } - else { - set_state(off_state, 0); - } -} - -void setup() { - // blink when power is connected - set_level(MAX_LEVEL/2); - delay_ms(10); - set_level(0); - - push_state(off_state, 0); -} - -void loop() { - if (go_to_standby) { - go_to_standby = 0; - PWM1_LVL = 0; PWM2_LVL = 0; - standby_mode(); - } -} diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c deleted file mode 100644 index 4ba329e..0000000 --- a/spaghetti-monster/baton.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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_DRIVER -#define USE_LVP -#define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 45 -#define USE_DEBUG_BLINK -#define USE_DELAY_MS -#define USE_DELAY_ZERO -#include "spaghetti-monster.h" - -// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 -uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, }; -uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, }; -#define MAX_LEVEL (sizeof(pwm1_modes)-1) - -// FSM states -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); - -// brightness control -uint8_t memorized_level = 1; -uint8_t actual_level = 0; -#ifdef USE_THERMAL_REGULATION -uint8_t target_level = 0; -#endif - -void set_level(uint8_t lvl) { - actual_level = lvl; - PWM1_LVL = pwm1_modes[lvl]; - PWM2_LVL = pwm2_modes[lvl]; -} - -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; - // sleep while off (lower power use) - //empty_event_sequence(); // just in case (but shouldn't be needed) - standby_mode(); - return 0; - } - // hold (initially): go to lowest level, but allow abort for regular click - else if (event == EV_click1_press) { - set_level(0); - return 0; - } - // 1 click (before timeout): go to memorized level, but allow abort for double click - else if (event == EV_click1_release) { - set_level(memorized_level); - return 0; - } - // 1 click: regular mode - else if (event == EV_1click) { - set_state(steady_state, memorized_level); - return 0; - } - // 2 clicks: highest mode - else if (event == EV_2clicks) { - set_state(steady_state, MAX_LEVEL); - return 0; - } - // 3 clicks: strobe mode - else if (event == EV_3clicks) { - set_state(party_strobe_state, 255); - return 0; - } - // hold: go to lowest level - else if (event == EV_click1_hold) { - set_state(steady_state, 0); - return 0; - } - return 1; -} - -uint8_t steady_state(EventPtr event, uint16_t arg) { - // turn LED on when we first enter the mode - if (event == EV_enter_state) { - // remember this level, unless it's moon or turbo - if ((arg > 0) && (arg < MAX_LEVEL)) - memorized_level = arg; - // use the requested level even if not memorized - #ifdef USE_THERMAL_REGULATION - target_level = arg; - #endif - set_level(arg); - return 0; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return 0; - } - // 2 clicks: go to/from highest level - else if (event == EV_2clicks) { - if (actual_level < MAX_LEVEL) { - memorized_level = actual_level; // in case we're on moon - #ifdef USE_THERMAL_REGULATION - target_level = MAX_LEVEL; - #endif - set_level(MAX_LEVEL); - } - else { - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return 0; - } - // 3 clicks: go to strobe modes - else if (event == EV_3clicks) { - set_state(party_strobe_state, 0xff); - return 0; - } - // hold: change brightness - else if (event == EV_click1_hold) { - if ((arg % HOLD_TIMEOUT) == 0) { - memorized_level = (actual_level+1) % (MAX_LEVEL+1); - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return 0; - } - #ifdef USE_THERMAL_REGULATION - // overheating: drop by 1 level - else if (event == EV_temperature_high) { - if (actual_level > 1) { - set_level(actual_level - 1); - } - return 0; - } - // underheating: increase by 1 level if we're lower than the target - else if (event == EV_temperature_low) { - if (actual_level < target_level) { - set_level(actual_level + 1); - } - return 0; - } - #endif - return 1; -} - -uint8_t party_strobe_state(EventPtr event, uint16_t arg) { - static volatile uint8_t frames = 0; - static volatile uint8_t between = 2; - if (event == EV_enter_state) { - if (arg < 64) between = arg; - frames = 0; - return 0; - } - // tick: strobe the emitter - else if (event == EV_tick) { - if (frames == 0) { - PWM1_LVL = 0; - PWM2_LVL = 255; - if (between < 3) delay_zero(); - else delay_ms(1); - PWM2_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, memorized_level); - 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() { - // "step down" from strobe to level 2 - if (current_state == party_strobe_state) { - set_state(steady_state, 1); - } - // in normal mode, step down by one level or turn off - else if (current_state == steady_state) { - if (actual_level > 0) { - set_level(actual_level - 1); - } - else { - set_state(off_state, 0); - } - } -} - -void setup() { - debug_blink(2); - - push_state(off_state, 0); -} - -void loop() { } diff --git a/spaghetti-monster/baton/baton-simpler.c b/spaghetti-monster/baton/baton-simpler.c new file mode 100644 index 0000000..a6ddf36 --- /dev/null +++ b/spaghetti-monster/baton/baton-simpler.c @@ -0,0 +1,202 @@ +/* + * 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_MS +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t lockout_state(EventPtr event, uint16_t arg); + +// brightness control +uint8_t memorized_level = 1; +uint8_t actual_level = 0; +#ifdef USE_THERMAL_REGULATION +uint8_t target_level = 0; +#endif + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + +// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 +uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, }; +uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, }; +#define MAX_LEVEL (sizeof(pwm1_levels)-1) + +// set LED brightness +void set_level(uint8_t lvl) { + actual_level = lvl; + PWM1_LVL = pwm1_levels[lvl]; + PWM2_LVL = pwm2_levels[lvl]; +} + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + go_to_standby = 1; // sleep while off (lower power use) + return EVENT_HANDLED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(0); + return EVENT_HANDLED; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return EVENT_HANDLED; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return EVENT_HANDLED; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, MAX_LEVEL); + return EVENT_HANDLED; + } + // 4 clicks: soft lockout + else if (event == EV_4clicks) { + set_state(lockout_state, 0); + return EVENT_HANDLED; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + set_state(steady_state, 0); + return EVENT_HANDLED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t steady_state(EventPtr event, uint16_t arg) { + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > 0) && (arg < MAX_LEVEL)) + memorized_level = arg; + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return EVENT_HANDLED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { // go to turbo + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + set_level(MAX_LEVEL); + } + else { // return from turbo + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return EVENT_HANDLED; + } + // hold: change brightness + else if (event == EV_click1_hold) { + if ((arg % HOLD_TIMEOUT) == 0) { + memorized_level = (actual_level+1) % (MAX_LEVEL+1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return EVENT_HANDLED; + } + #ifdef USE_THERMAL_REGULATION + // overheating: drop by 1 level + else if (event == EV_temperature_high) { + if (actual_level > 1) { + set_level(actual_level - 1); + } + return EVENT_HANDLED; + } + // underheating: increase by 1 level if we're lower than the target + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + set_level(actual_level + 1); + } + return EVENT_HANDLED; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + // stay asleep while locked, but allow waking long enough to click 4 times + if (event == EV_tick) { + static uint8_t ticks_spent_awake = 0; + ticks_spent_awake ++; + PWM1_LVL = 0; PWM2_LVL = 0; + if (ticks_spent_awake > 3 * TICKS_PER_SECOND) { + ticks_spent_awake = 0; + go_to_standby = 1; + } + return MISCHIEF_MANAGED; + } + // 4 clicks: exit + else if (event == EV_4clicks) { + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +void low_voltage() { + // step down by one level or turn off + if (actual_level > 0) { + set_level(actual_level - 1); + } + else { + set_state(off_state, 0); + } +} + +void setup() { + // blink when power is connected + set_level(MAX_LEVEL/2); + delay_ms(10); + set_level(0); + + push_state(off_state, 0); +} + +void loop() { + if (go_to_standby) { + go_to_standby = 0; + PWM1_LVL = 0; PWM2_LVL = 0; + standby_mode(); + } +} diff --git a/spaghetti-monster/baton/baton.c b/spaghetti-monster/baton/baton.c new file mode 100644 index 0000000..515f2d0 --- /dev/null +++ b/spaghetti-monster/baton/baton.c @@ -0,0 +1,231 @@ +/* + * 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DEBUG_BLINK +#define USE_DELAY_MS +#define USE_DELAY_4MS +#define USE_DELAY_ZERO +#include "spaghetti-monster.h" + +// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 +uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, }; +uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, }; +#define MAX_LEVEL (sizeof(pwm1_modes)-1) + +// FSM states +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); + +// brightness control +uint8_t memorized_level = 1; +uint8_t actual_level = 0; +#ifdef USE_THERMAL_REGULATION +uint8_t target_level = 0; +#endif + +void set_level(uint8_t lvl) { + actual_level = lvl; + PWM1_LVL = pwm1_modes[lvl]; + PWM2_LVL = pwm2_modes[lvl]; +} + +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; + // sleep while off (lower power use) + //empty_event_sequence(); // just in case (but shouldn't be needed) + standby_mode(); + return 0; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(0); + return 0; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return 0; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return 0; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, MAX_LEVEL); + return 0; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(party_strobe_state, 255); + return 0; + } + // hold: go to lowest level + else if (event == EV_click1_hold) { + set_state(steady_state, 0); + return 0; + } + return 1; +} + +uint8_t steady_state(EventPtr event, uint16_t arg) { + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > 0) && (arg < MAX_LEVEL)) + memorized_level = arg; + // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return 0; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return 0; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + set_level(MAX_LEVEL); + } + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return 0; + } + // 3 clicks: go to strobe modes + else if (event == EV_3clicks) { + set_state(party_strobe_state, 0xff); + return 0; + } + // hold: change brightness + else if (event == EV_click1_hold) { + if ((arg % HOLD_TIMEOUT) == 0) { + memorized_level = (actual_level+1) % (MAX_LEVEL+1); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return 0; + } + #ifdef USE_THERMAL_REGULATION + // overheating: drop by 1 level + else if (event == EV_temperature_high) { + if (actual_level > 1) { + set_level(actual_level - 1); + } + return 0; + } + // underheating: increase by 1 level if we're lower than the target + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + set_level(actual_level + 1); + } + return 0; + } + #endif + return 1; +} + +uint8_t party_strobe_state(EventPtr event, uint16_t arg) { + static volatile uint8_t frames = 0; + static volatile uint8_t between = 2; + if (event == EV_enter_state) { + if (arg < 64) between = arg; + frames = 0; + return 0; + } + // tick: strobe the emitter + else if (event == EV_tick) { + if (frames == 0) { + PWM1_LVL = 0; + PWM2_LVL = 255; + if (between < 3) delay_zero(); + else delay_ms(1); + PWM2_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, memorized_level); + 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() { + // "step down" from strobe to level 2 + if (current_state == party_strobe_state) { + set_state(steady_state, 1); + } + // in normal mode, step down by one level or turn off + else if (current_state == steady_state) { + if (actual_level > 0) { + set_level(actual_level - 1); + } + else { + set_state(off_state, 0); + } + } +} + +void setup() { + debug_blink(2); + + push_state(off_state, 0); +} + +void loop() { } diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c deleted file mode 100644 index 2aa3e90..0000000 --- a/spaghetti-monster/darkhorse.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * DarkHorse: Improved ZebraLight clone 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_DRIVER -#define USE_LVP -#define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 45 -#define USE_DELAY_4MS -#define USE_RAMPING -#define RAMP_LENGTH 150 -#define USE_BATTCHECK -#define BATTCHECK_4bars -#define DONT_DELAY_AFTER_BATTCHECK -#define USE_EEPROM -#define EEPROM_BYTES 5 -#include "spaghetti-monster.h" - -// FSM states -uint8_t off_state(EventPtr event, uint16_t arg); -uint8_t low_mode_state(EventPtr event, uint16_t arg); -uint8_t med_mode_state(EventPtr event, uint16_t arg); -uint8_t hi_mode_state(EventPtr event, uint16_t arg); -uint8_t strobe_beacon_state(EventPtr event, uint16_t arg); -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg); -#endif -// Not a FSM state, just handles stuff common to all low/med/hi states -uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); - -void load_config(); -void save_config(); - -// toggle between L1/L2, M1/M2, H1/H2 -uint8_t L1 = 1; -uint8_t M1 = 1; -uint8_t H1 = 1; -// brightness for L2, M2, H2 (valid range 1 to 3 inclusive) -uint8_t L2 = 1; -uint8_t M2 = 1; -uint8_t H2 = 1; -// mode groups, ish -uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm -uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm -uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm -// strobe/beacon modes: -// 0: 0.2 Hz beacon at L1 -// 1: 0.2 Hz beacon at H1 -// 2: 4 Hz strobe at H1 -// 3: 19 Hz strobe at H1 -uint8_t strobe_beacon_mode = 0; - -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - -#ifdef USE_THERMAL_REGULATION -// brightness before thermal step-down -uint8_t target_level = 0; -#endif - -void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) { - // primary (H1/M1/L1) - if (primary) { - set_level(modes[0]); - } - // secondary (H2/M2/L2) - else { - set_level(modes[secondary]); - } - #ifdef USE_THERMAL_REGULATION - target_level = actual_level; - #endif -} - -inline void set_low_mode() { set_any_mode(L1, L2, low_modes); } -inline void set_med_mode() { set_any_mode(M1, M2, med_modes); } -inline void set_hi_mode() { set_any_mode(H1, H2, hi_modes); } - - -uint8_t off_state(EventPtr event, uint16_t arg) { - // turn emitter off when entering state - if (event == EV_enter_state) { - set_level(0); - // sleep while off (lower power use) - go_to_standby = 1; - return EVENT_HANDLED; - } - // hold (initially): go to lowest level, but allow abort for regular click - else if (event == EV_click1_press) { - set_low_mode(); - return EVENT_HANDLED; - } - // 1 click (before timeout): go to high level, but allow abort for double click - else if (event == EV_click1_release) { - set_hi_mode(); - return EVENT_HANDLED; - } - // 1 click: high mode - else if (event == EV_1click) { - set_state(hi_mode_state, 0); - return EVENT_HANDLED; - } - // click, press (initially): go to medium mode, but allow abort - else if (event == EV_click2_press) { - set_med_mode(); - return EVENT_HANDLED; - } - // 2 clicks: medium mode - else if (event == EV_2clicks) { - set_state(med_mode_state, 0); - return EVENT_HANDLED; - } - // click, click, press (initially): light off, prep for blinkies - else if (event == EV_click3_press) { - set_level(0); - return EVENT_HANDLED; - } - // 3 clicks: strobe mode - else if (event == EV_3clicks) { - set_state(strobe_beacon_state, 0); - return EVENT_HANDLED; - } - #ifdef USE_BATTCHECK - // 4 clicks: battcheck mode - else if (event == EV_4clicks) { - set_state(battcheck_state, 0); - return EVENT_HANDLED; - } - #endif - // hold: go to low mode, but allow ramping up - else if (event == EV_click1_hold) { - // don't start ramping immediately; - // give the user time to release at low mode - if (arg >= HOLD_TIMEOUT) - set_state(low_mode_state, 0); - return EVENT_HANDLED; - } - // hold, release quickly: go to low mode - else if (event == EV_click1_hold_release) { - set_state(low_mode_state, 0); - return EVENT_HANDLED; - } - /* TODO: implement - // click-release-hold: discrete ramp through all levels - else if (event == EV_click2_hold) { - set_state(steady_state, MAX_LEVEL); - return EVENT_HANDLED; - } - */ - return EVENT_NOT_HANDLED; -} - - -uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes) { - // turn on LED when entering the mode - if (event == EV_enter_state) { - set_any_mode(*primary, *secondary, modes); - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // hold: change brightness (low, med, hi, always starting at low) - else if (event == EV_click1_hold) { - uint8_t which = arg % (HOLD_TIMEOUT * 3) / HOLD_TIMEOUT; - switch(which) { - case 0: - set_state(low_mode_state, 0); - break; - case 1: - set_state(med_mode_state, 0); - break; - case 2: - set_state(hi_mode_state, 0); - break; - } - return MISCHIEF_MANAGED; - } - // 2 clicks: toggle primary/secondary level - else if (event == EV_2clicks) { - *primary ^= 1; - set_any_mode(*primary, *secondary, modes); - save_config(); - return MISCHIEF_MANAGED; - } - // click-release-hold: change secondary level - else if (event == EV_click2_hold) { - if (arg % HOLD_TIMEOUT == 0) { - *secondary = (*secondary + 1) & 3; - if (! *secondary) *secondary = 1; - *primary = 0; - set_any_mode(*primary, *secondary, modes); - } - return MISCHIEF_MANAGED; - } - // click, hold, release: save secondary level - else if (event == EV_click2_hold_release) { - save_config(); - } - #ifdef USE_THERMAL_REGULATION - // TODO: test this on a real light - // overheating: drop by an amount proportional to how far we are above the ceiling - else if (event == EV_temperature_high) { - if (actual_level > MAX_LEVEL/4) { - uint8_t stepdown = actual_level - arg; - if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; - set_level(stepdown); - } - return EVENT_HANDLED; - } - // underheating: increase slowly if we're lower than the target - // (proportional to how low we are) - else if (event == EV_temperature_low) { - if (actual_level < target_level) { - uint8_t stepup = actual_level + (arg>>1); - if (stepup > target_level) stepup = target_level; - set_level(stepup); - } - return EVENT_HANDLED; - } - #endif - return EVENT_NOT_HANDLED; -} - -uint8_t low_mode_state(EventPtr event, uint16_t arg) { - return any_mode_state(event, arg, &L1, &L2, low_modes); -} - -uint8_t med_mode_state(EventPtr event, uint16_t arg) { - return any_mode_state(event, arg, &M1, &M2, med_modes); -} - -uint8_t hi_mode_state(EventPtr event, uint16_t arg) { - return any_mode_state(event, arg, &H1, &H2, hi_modes); -} - - -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg) { - return EVENT_NOT_HANDLED; -} -#endif - - -uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 1 click (initially): cancel current blink - if (event == EV_click1_release) { - interrupt_nice_delays(); - return MISCHIEF_MANAGED; - } - // 2 clicks: rotate through blinky modes - else if (event == EV_2clicks) { - strobe_beacon_mode = (strobe_beacon_mode + 1) & 3; - save_config(); - interrupt_nice_delays(); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -void low_voltage() { - if (current_state == hi_mode_state) { - set_state(med_mode_state, 0); - } - else if (current_state == med_mode_state) { - set_state(low_mode_state, 0); - } - else if (current_state == low_mode_state) { - set_state(off_state, 0); - } - // "step down" from blinkies to low - else if (current_state == strobe_beacon_state) { - set_state(low_mode_state, 0); - } -} - -void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { - set_level(level); - if (! nice_delay_ms(ontime)) return; - set_level(0); - nice_delay_ms(offtime); -} - -void load_config() { - if (load_eeprom()) { - H1 = !(!(eeprom[0] & 0b00000100)); - M1 = !(!(eeprom[0] & 0b00000010)); - L1 = !(!(eeprom[0] & 0b00000001)); - H2 = eeprom[1]; - M2 = eeprom[2]; - L2 = eeprom[3]; - strobe_beacon_mode = eeprom[4]; - } -} - -void save_config() { - eeprom[0] = (H1<<2) | (M1<<1) | (L1); - eeprom[1] = H2; - eeprom[2] = M2; - eeprom[3] = L2; - eeprom[4] = strobe_beacon_mode; - - save_eeprom(); -} - -void setup() { - set_level(RAMP_SIZE/8); - delay_4ms(3); - set_level(0); - - load_config(); - - push_state(off_state, 0); -} - -void loop() { - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - - if (current_state == strobe_beacon_state) { - switch(strobe_beacon_mode) { - // 0.2 Hz beacon at L1 - case 0: - strobe(low_modes[0], 500, 4500); - break; - // 0.2 Hz beacon at H1 - case 1: - strobe(hi_modes[0], 500, 4500); - break; - // 4 Hz tactical strobe at H1 - case 2: - strobe(hi_modes[0], 83, 167); - break; - // 19 Hz tactical strobe at H1 - case 3: - strobe(hi_modes[0], 17, 35); - break; - } - } - - #ifdef USE_BATTCHECK - else if (current_state == battcheck_state) { - nice_delay_ms(500); // wait a moment to measure voltage - battcheck(); - set_state(off_state, 0); - } - #endif -} - - diff --git a/spaghetti-monster/darkhorse/darkhorse.c b/spaghetti-monster/darkhorse/darkhorse.c new file mode 100644 index 0000000..2aa3e90 --- /dev/null +++ b/spaghetti-monster/darkhorse/darkhorse.c @@ -0,0 +1,378 @@ +/* + * DarkHorse: Improved ZebraLight clone 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_4MS +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define USE_BATTCHECK +#define BATTCHECK_4bars +#define DONT_DELAY_AFTER_BATTCHECK +#define USE_EEPROM +#define EEPROM_BYTES 5 +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t low_mode_state(EventPtr event, uint16_t arg); +uint8_t med_mode_state(EventPtr event, uint16_t arg); +uint8_t hi_mode_state(EventPtr event, uint16_t arg); +uint8_t strobe_beacon_state(EventPtr event, uint16_t arg); +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +#endif +// Not a FSM state, just handles stuff common to all low/med/hi states +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); + +void load_config(); +void save_config(); + +// toggle between L1/L2, M1/M2, H1/H2 +uint8_t L1 = 1; +uint8_t M1 = 1; +uint8_t H1 = 1; +// brightness for L2, M2, H2 (valid range 1 to 3 inclusive) +uint8_t L2 = 1; +uint8_t M2 = 1; +uint8_t H2 = 1; +// mode groups, ish +uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm +uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm +uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm +// strobe/beacon modes: +// 0: 0.2 Hz beacon at L1 +// 1: 0.2 Hz beacon at H1 +// 2: 4 Hz strobe at H1 +// 3: 19 Hz strobe at H1 +uint8_t strobe_beacon_mode = 0; + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) { + // primary (H1/M1/L1) + if (primary) { + set_level(modes[0]); + } + // secondary (H2/M2/L2) + else { + set_level(modes[secondary]); + } + #ifdef USE_THERMAL_REGULATION + target_level = actual_level; + #endif +} + +inline void set_low_mode() { set_any_mode(L1, L2, low_modes); } +inline void set_med_mode() { set_any_mode(M1, M2, med_modes); } +inline void set_hi_mode() { set_any_mode(H1, H2, hi_modes); } + + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + go_to_standby = 1; + return EVENT_HANDLED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_low_mode(); + return EVENT_HANDLED; + } + // 1 click (before timeout): go to high level, but allow abort for double click + else if (event == EV_click1_release) { + set_hi_mode(); + return EVENT_HANDLED; + } + // 1 click: high mode + else if (event == EV_1click) { + set_state(hi_mode_state, 0); + return EVENT_HANDLED; + } + // click, press (initially): go to medium mode, but allow abort + else if (event == EV_click2_press) { + set_med_mode(); + return EVENT_HANDLED; + } + // 2 clicks: medium mode + else if (event == EV_2clicks) { + set_state(med_mode_state, 0); + return EVENT_HANDLED; + } + // click, click, press (initially): light off, prep for blinkies + else if (event == EV_click3_press) { + set_level(0); + return EVENT_HANDLED; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(strobe_beacon_state, 0); + return EVENT_HANDLED; + } + #ifdef USE_BATTCHECK + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return EVENT_HANDLED; + } + #endif + // hold: go to low mode, but allow ramping up + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at low mode + if (arg >= HOLD_TIMEOUT) + set_state(low_mode_state, 0); + return EVENT_HANDLED; + } + // hold, release quickly: go to low mode + else if (event == EV_click1_hold_release) { + set_state(low_mode_state, 0); + return EVENT_HANDLED; + } + /* TODO: implement + // click-release-hold: discrete ramp through all levels + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return EVENT_HANDLED; + } + */ + return EVENT_NOT_HANDLED; +} + + +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes) { + // turn on LED when entering the mode + if (event == EV_enter_state) { + set_any_mode(*primary, *secondary, modes); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // hold: change brightness (low, med, hi, always starting at low) + else if (event == EV_click1_hold) { + uint8_t which = arg % (HOLD_TIMEOUT * 3) / HOLD_TIMEOUT; + switch(which) { + case 0: + set_state(low_mode_state, 0); + break; + case 1: + set_state(med_mode_state, 0); + break; + case 2: + set_state(hi_mode_state, 0); + break; + } + return MISCHIEF_MANAGED; + } + // 2 clicks: toggle primary/secondary level + else if (event == EV_2clicks) { + *primary ^= 1; + set_any_mode(*primary, *secondary, modes); + save_config(); + return MISCHIEF_MANAGED; + } + // click-release-hold: change secondary level + else if (event == EV_click2_hold) { + if (arg % HOLD_TIMEOUT == 0) { + *secondary = (*secondary + 1) & 3; + if (! *secondary) *secondary = 1; + *primary = 0; + set_any_mode(*primary, *secondary, modes); + } + return MISCHIEF_MANAGED; + } + // click, hold, release: save secondary level + else if (event == EV_click2_hold_release) { + save_config(); + } + #ifdef USE_THERMAL_REGULATION + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return EVENT_HANDLED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return EVENT_HANDLED; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t low_mode_state(EventPtr event, uint16_t arg) { + return any_mode_state(event, arg, &L1, &L2, low_modes); +} + +uint8_t med_mode_state(EventPtr event, uint16_t arg) { + return any_mode_state(event, arg, &M1, &M2, med_modes); +} + +uint8_t hi_mode_state(EventPtr event, uint16_t arg) { + return any_mode_state(event, arg, &H1, &H2, hi_modes); +} + + +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + return EVENT_NOT_HANDLED; +} +#endif + + +uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 1 click (initially): cancel current blink + if (event == EV_click1_release) { + interrupt_nice_delays(); + return MISCHIEF_MANAGED; + } + // 2 clicks: rotate through blinky modes + else if (event == EV_2clicks) { + strobe_beacon_mode = (strobe_beacon_mode + 1) & 3; + save_config(); + interrupt_nice_delays(); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +void low_voltage() { + if (current_state == hi_mode_state) { + set_state(med_mode_state, 0); + } + else if (current_state == med_mode_state) { + set_state(low_mode_state, 0); + } + else if (current_state == low_mode_state) { + set_state(off_state, 0); + } + // "step down" from blinkies to low + else if (current_state == strobe_beacon_state) { + set_state(low_mode_state, 0); + } +} + +void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { + set_level(level); + if (! nice_delay_ms(ontime)) return; + set_level(0); + nice_delay_ms(offtime); +} + +void load_config() { + if (load_eeprom()) { + H1 = !(!(eeprom[0] & 0b00000100)); + M1 = !(!(eeprom[0] & 0b00000010)); + L1 = !(!(eeprom[0] & 0b00000001)); + H2 = eeprom[1]; + M2 = eeprom[2]; + L2 = eeprom[3]; + strobe_beacon_mode = eeprom[4]; + } +} + +void save_config() { + eeprom[0] = (H1<<2) | (M1<<1) | (L1); + eeprom[1] = H2; + eeprom[2] = M2; + eeprom[3] = L2; + eeprom[4] = strobe_beacon_mode; + + save_eeprom(); +} + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + load_config(); + + push_state(off_state, 0); +} + +void loop() { + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + + if (current_state == strobe_beacon_state) { + switch(strobe_beacon_mode) { + // 0.2 Hz beacon at L1 + case 0: + strobe(low_modes[0], 500, 4500); + break; + // 0.2 Hz beacon at H1 + case 1: + strobe(hi_modes[0], 500, 4500); + break; + // 4 Hz tactical strobe at H1 + case 2: + strobe(hi_modes[0], 83, 167); + break; + // 19 Hz tactical strobe at H1 + case 3: + strobe(hi_modes[0], 17, 35); + break; + } + } + + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + nice_delay_ms(500); // wait a moment to measure voltage + battcheck(); + set_state(off_state, 0); + } + #endif +} + + diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c deleted file mode 100644 index 6b049f4..0000000 --- a/spaghetti-monster/momentary.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Momentary: Very simple example UI for SpaghettiMonster. - * Is intended to be the simplest possible FSM e-switch UI. - * The light is on while the button is held; off otherwise. - * - * 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_DRIVER -#define USE_LVP -#define USE_DEBUG_BLINK -#define USE_DELAY_4MS -#include "spaghetti-monster.h" - -volatile uint8_t brightness; -volatile uint8_t on_now; - -void light_on() { - on_now = 1; - PWM1_LVL = brightness; - PWM2_LVL = 0; -} - -void light_off() { - on_now = 0; - PWM1_LVL = 0; - PWM2_LVL = 0; -} - -uint8_t momentary_state(EventPtr event, uint16_t arg) { - - if (event == EV_click1_press) { - brightness = 255; - light_on(); - empty_event_sequence(); // don't attempt to parse multiple clicks - return 0; - } - - else if (event == EV_release) { - light_off(); - empty_event_sequence(); // don't attempt to parse multiple clicks - standby_mode(); // sleep while light is off - return 0; - } - - return 1; // event not handled -} - -// LVP / low-voltage protection -void low_voltage() { - if (brightness > 0) { - debug_blink(3); - brightness >>= 1; - if (on_now) light_on(); - } else { - debug_blink(8); - light_off(); - standby_mode(); - } -} - -void setup() { - debug_blink(2); - push_state(momentary_state, 0); -} - -void loop() { } - diff --git a/spaghetti-monster/momentary/momentary.c b/spaghetti-monster/momentary/momentary.c new file mode 100644 index 0000000..6b049f4 --- /dev/null +++ b/spaghetti-monster/momentary/momentary.c @@ -0,0 +1,81 @@ +/* + * Momentary: Very simple example UI for SpaghettiMonster. + * Is intended to be the simplest possible FSM e-switch UI. + * The light is on while the button is held; off otherwise. + * + * 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_DRIVER +#define USE_LVP +#define USE_DEBUG_BLINK +#define USE_DELAY_4MS +#include "spaghetti-monster.h" + +volatile uint8_t brightness; +volatile uint8_t on_now; + +void light_on() { + on_now = 1; + PWM1_LVL = brightness; + PWM2_LVL = 0; +} + +void light_off() { + on_now = 0; + PWM1_LVL = 0; + PWM2_LVL = 0; +} + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + + if (event == EV_click1_press) { + brightness = 255; + light_on(); + empty_event_sequence(); // don't attempt to parse multiple clicks + return 0; + } + + else if (event == EV_release) { + light_off(); + empty_event_sequence(); // don't attempt to parse multiple clicks + standby_mode(); // sleep while light is off + return 0; + } + + return 1; // event not handled +} + +// LVP / low-voltage protection +void low_voltage() { + if (brightness > 0) { + debug_blink(3); + brightness >>= 1; + if (on_now) light_on(); + } else { + debug_blink(8); + light_off(); + standby_mode(); + } +} + +void setup() { + debug_blink(2); + push_state(momentary_state, 0); +} + +void loop() { } + diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c deleted file mode 100644 index 527a824..0000000 --- a/spaghetti-monster/ramping-ui.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Ramping-UI: Ramping 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_DRIVER -#define USE_LVP -#define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 32 -#define USE_DELAY_MS -#define USE_DELAY_4MS -#define USE_DELAY_ZERO -#define USE_RAMPING -#define USE_BATTCHECK -#define BATTCHECK_VpT -#define RAMP_LENGTH 150 -#include "spaghetti-monster.h" - -// FSM states -uint8_t off_state(EventPtr event, uint16_t arg); -uint8_t steady_state(EventPtr event, uint16_t arg); -uint8_t strobe_state(EventPtr event, uint16_t arg); -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg); -uint8_t tempcheck_state(EventPtr event, uint16_t arg); -#endif - -// brightness control -uint8_t memorized_level = MAX_1x7135; -// smooth vs discrete ramping -uint8_t ramp_step_size = 1; - -#ifdef USE_THERMAL_REGULATION -// brightness before thermal step-down -uint8_t target_level = 0; -#endif - -// strobe timing -volatile uint8_t strobe_delay = 67; -volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe - - -uint8_t off_state(EventPtr event, uint16_t arg) { - // turn emitter off when entering state - if (event == EV_enter_state) { - set_level(0); - // sleep while off (lower power use) - //empty_event_sequence(); // just in case (but shouldn't be needed) - standby_mode(); - return MISCHIEF_MANAGED; - } - // hold (initially): go to lowest level, but allow abort for regular click - else if (event == EV_click1_press) { - set_level(1); - return MISCHIEF_MANAGED; - } - // 1 click (before timeout): go to memorized level, but allow abort for double click - else if (event == EV_click1_release) { - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - // 1 click: regular mode - else if (event == EV_1click) { - set_state(steady_state, memorized_level); - return MISCHIEF_MANAGED; - } - // 2 clicks (initial press): off, to prep for later events - else if (event == EV_click2_press) { - set_level(0); - return MISCHIEF_MANAGED; - } - // 2 clicks: highest mode - else if (event == EV_2clicks) { - set_state(steady_state, MAX_LEVEL); - return MISCHIEF_MANAGED; - } - // 3 clicks: strobe mode - else if (event == EV_3clicks) { - set_state(strobe_state, 0); - return MISCHIEF_MANAGED; - } - #ifdef USE_BATTCHECK - // 4 clicks: battcheck mode - else if (event == EV_4clicks) { - set_state(battcheck_state, 0); - return MISCHIEF_MANAGED; - } - #endif - // hold: go to lowest level - else if (event == EV_click1_hold) { - // don't start ramping immediately; - // give the user time to release at moon level - if (arg >= HOLD_TIMEOUT) - set_state(steady_state, 1); - return MISCHIEF_MANAGED; - } - // hold, release quickly: go to lowest level - else if (event == EV_click1_hold_release) { - set_state(steady_state, 1); - return MISCHIEF_MANAGED; - } - // click, hold: go to highest level (for ramping down) - else if (event == EV_click2_hold) { - set_state(steady_state, MAX_LEVEL); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -uint8_t steady_state(EventPtr event, uint16_t arg) { - // turn LED on when we first enter the mode - if (event == EV_enter_state) { - // remember this level, unless it's moon or turbo - if ((arg > 1) && (arg < MAX_LEVEL)) - memorized_level = arg; - // use the requested level even if not memorized - #ifdef USE_THERMAL_REGULATION - target_level = arg; - #endif - set_level(arg); - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: go to/from highest level - else if (event == EV_2clicks) { - if (actual_level < MAX_LEVEL) { - memorized_level = actual_level; // in case we're on moon - #ifdef USE_THERMAL_REGULATION - target_level = MAX_LEVEL; - #endif - set_level(MAX_LEVEL); - } - else { - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - set_level(memorized_level); - } - return MISCHIEF_MANAGED; - } - // 3 clicks: go to strobe modes - else if (event == EV_3clicks) { - set_state(strobe_state, 0); - return MISCHIEF_MANAGED; - } - // 4 clicks: toggle smooth vs discrete ramping - else if (event == EV_4clicks) { - if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6; - else ramp_step_size = 1; - set_level(0); - delay_4ms(20/4); - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - // hold: change brightness (brighter) - else if (event == EV_click1_hold) { - // ramp slower in discrete mode - if (arg % ramp_step_size != 0) { - return MISCHIEF_MANAGED; - } - // FIXME: make it ramp down instead, if already at max - if (actual_level + ramp_step_size < MAX_LEVEL) - memorized_level = actual_level + ramp_step_size; - else memorized_level = MAX_LEVEL; - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - || (memorized_level == MAX_LEVEL))) { - set_level(0); - delay_4ms(8/4); - } - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - // click, hold: change brightness (dimmer) - else if (event == EV_click2_hold) { - // ramp slower in discrete mode - if (arg % ramp_step_size != 0) { - return MISCHIEF_MANAGED; - } - // FIXME: make it ramp up instead, if already at min - if (actual_level > ramp_step_size) - memorized_level = (actual_level-ramp_step_size); - else - memorized_level = 1; - #ifdef USE_THERMAL_REGULATION - target_level = memorized_level; - #endif - // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - || (memorized_level == 1))) { - set_level(0); - delay_4ms(8/4); - } - set_level(memorized_level); - return MISCHIEF_MANAGED; - } - #ifdef USE_THERMAL_REGULATION - // TODO: test this on a real light - // overheating: drop by an amount proportional to how far we are above the ceiling - else if (event == EV_temperature_high) { - if (actual_level > MAX_LEVEL/4) { - uint8_t stepdown = actual_level - arg; - if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; - set_level(stepdown); - } - return MISCHIEF_MANAGED; - } - // underheating: increase slowly if we're lower than the target - // (proportional to how low we are) - else if (event == EV_temperature_low) { - if (actual_level < target_level) { - uint8_t stepup = actual_level + (arg>>1); - if (stepup > target_level) stepup = target_level; - set_level(stepup); - } - return MISCHIEF_MANAGED; - } - #endif - return EVENT_NOT_HANDLED; -} - - -uint8_t strobe_state(EventPtr event, uint16_t arg) { - if (event == EV_enter_state) { - return MISCHIEF_MANAGED; - } - // 1 click: off - else if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: toggle party strobe vs tactical strobe - else if (event == EV_2clicks) { - strobe_type ^= 1; - return MISCHIEF_MANAGED; - } - // 3 clicks: go back to regular modes - else if (event == EV_3clicks) { - set_state(steady_state, memorized_level); - return MISCHIEF_MANAGED; - } - // hold: change speed (go faster) - else if (event == EV_click1_hold) { - if ((arg & 1) == 0) { - if (strobe_delay > 8) strobe_delay --; - } - return MISCHIEF_MANAGED; - } - // click, hold: change speed (go slower) - else if (event == EV_click2_hold) { - if ((arg & 1) == 0) { - if (strobe_delay < 255) strobe_delay ++; - } - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - - -#ifdef USE_BATTCHECK -uint8_t battcheck_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - // 2 clicks: tempcheck mode - else if (event == EV_2clicks) { - set_state(tempcheck_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} - -uint8_t tempcheck_state(EventPtr event, uint16_t arg) { - // 1 click: off - if (event == EV_1click) { - set_state(off_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; -} -#endif - - -void low_voltage() { - // "step down" from strobe to something low - if (current_state == strobe_state) { - set_state(steady_state, RAMP_SIZE/6); - } - // in normal mode, step down by half or turn off - else if (current_state == steady_state) { - if (actual_level > 1) { - set_level(actual_level >> 1); - } - else { - set_state(off_state, 0); - } - } - // all other modes, just turn off when voltage is low - else { - set_state(off_state, 0); - } -} - - -void setup() { - set_level(RAMP_SIZE/8); - delay_4ms(3); - set_level(0); - - push_state(off_state, 0); -} - - -void loop() { - if (current_state == strobe_state) { - set_level(MAX_LEVEL); - if (strobe_type == 0) { // party strobe - if (strobe_delay < 30) delay_zero(); - else delay_ms(1); - } else { //tactical strobe - nice_delay_ms(strobe_delay >> 1); - } - set_level(0); - nice_delay_ms(strobe_delay); - } - #ifdef USE_BATTCHECK - else if (current_state == battcheck_state) { - battcheck(); - } - else if (current_state == tempcheck_state) { - blink_num(projected_temperature>>2); - nice_delay_ms(1000); - } - #endif -} diff --git a/spaghetti-monster/ramping-ui/ramping-ui.c b/spaghetti-monster/ramping-ui/ramping-ui.c new file mode 100644 index 0000000..527a824 --- /dev/null +++ b/spaghetti-monster/ramping-ui/ramping-ui.c @@ -0,0 +1,361 @@ +/* + * Ramping-UI: Ramping 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 32 +#define USE_DELAY_MS +#define USE_DELAY_4MS +#define USE_DELAY_ZERO +#define USE_RAMPING +#define USE_BATTCHECK +#define BATTCHECK_VpT +#define RAMP_LENGTH 150 +#include "spaghetti-monster.h" + +// FSM states +uint8_t off_state(EventPtr event, uint16_t arg); +uint8_t steady_state(EventPtr event, uint16_t arg); +uint8_t strobe_state(EventPtr event, uint16_t arg); +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t tempcheck_state(EventPtr event, uint16_t arg); +#endif + +// brightness control +uint8_t memorized_level = MAX_1x7135; +// smooth vs discrete ramping +uint8_t ramp_step_size = 1; + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +// strobe timing +volatile uint8_t strobe_delay = 67; +volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe + + +uint8_t off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + //empty_event_sequence(); // just in case (but shouldn't be needed) + standby_mode(); + return MISCHIEF_MANAGED; + } + // hold (initially): go to lowest level, but allow abort for regular click + else if (event == EV_click1_press) { + set_level(1); + return MISCHIEF_MANAGED; + } + // 1 click (before timeout): go to memorized level, but allow abort for double click + else if (event == EV_click1_release) { + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // 1 click: regular mode + else if (event == EV_1click) { + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } + // 2 clicks (initial press): off, to prep for later events + else if (event == EV_click2_press) { + set_level(0); + return MISCHIEF_MANAGED; + } + // 2 clicks: highest mode + else if (event == EV_2clicks) { + set_state(steady_state, MAX_LEVEL); + return MISCHIEF_MANAGED; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } + #ifdef USE_BATTCHECK + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return MISCHIEF_MANAGED; + } + #endif + // hold: go to lowest level + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at moon level + if (arg >= HOLD_TIMEOUT) + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } + // hold, release quickly: go to lowest level + else if (event == EV_click1_hold_release) { + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } + // click, hold: go to highest level (for ramping down) + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +uint8_t steady_state(EventPtr event, uint16_t arg) { + // turn LED on when we first enter the mode + if (event == EV_enter_state) { + // remember this level, unless it's moon or turbo + if ((arg > 1) && (arg < MAX_LEVEL)) + memorized_level = arg; + // use the requested level even if not memorized + #ifdef USE_THERMAL_REGULATION + target_level = arg; + #endif + set_level(arg); + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: go to/from highest level + else if (event == EV_2clicks) { + if (actual_level < MAX_LEVEL) { + memorized_level = actual_level; // in case we're on moon + #ifdef USE_THERMAL_REGULATION + target_level = MAX_LEVEL; + #endif + set_level(MAX_LEVEL); + } + else { + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + set_level(memorized_level); + } + return MISCHIEF_MANAGED; + } + // 3 clicks: go to strobe modes + else if (event == EV_3clicks) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } + // 4 clicks: toggle smooth vs discrete ramping + else if (event == EV_4clicks) { + if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6; + else ramp_step_size = 1; + set_level(0); + delay_4ms(20/4); + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // hold: change brightness (brighter) + else if (event == EV_click1_hold) { + // ramp slower in discrete mode + if (arg % ramp_step_size != 0) { + return MISCHIEF_MANAGED; + } + // FIXME: make it ramp down instead, if already at max + if (actual_level + ramp_step_size < MAX_LEVEL) + memorized_level = actual_level + ramp_step_size; + else memorized_level = MAX_LEVEL; + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == MAX_LEVEL))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + // click, hold: change brightness (dimmer) + else if (event == EV_click2_hold) { + // ramp slower in discrete mode + if (arg % ramp_step_size != 0) { + return MISCHIEF_MANAGED; + } + // FIXME: make it ramp up instead, if already at min + if (actual_level > ramp_step_size) + memorized_level = (actual_level-ramp_step_size); + else + memorized_level = 1; + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #endif + // only blink once for each threshold + if ((memorized_level != actual_level) + && ((memorized_level == MAX_1x7135) + || (memorized_level == 1))) { + set_level(0); + delay_4ms(8/4); + } + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + #ifdef USE_THERMAL_REGULATION + // TODO: test this on a real light + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return MISCHIEF_MANAGED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return MISCHIEF_MANAGED; + } + #endif + return EVENT_NOT_HANDLED; +} + + +uint8_t strobe_state(EventPtr event, uint16_t arg) { + if (event == EV_enter_state) { + return MISCHIEF_MANAGED; + } + // 1 click: off + else if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: toggle party strobe vs tactical strobe + else if (event == EV_2clicks) { + strobe_type ^= 1; + return MISCHIEF_MANAGED; + } + // 3 clicks: go back to regular modes + else if (event == EV_3clicks) { + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } + // hold: change speed (go faster) + else if (event == EV_click1_hold) { + if ((arg & 1) == 0) { + if (strobe_delay > 8) strobe_delay --; + } + return MISCHIEF_MANAGED; + } + // click, hold: change speed (go slower) + else if (event == EV_click2_hold) { + if ((arg & 1) == 0) { + if (strobe_delay < 255) strobe_delay ++; + } + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + + +#ifdef USE_BATTCHECK +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + // 2 clicks: tempcheck mode + else if (event == EV_2clicks) { + set_state(tempcheck_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t tempcheck_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} +#endif + + +void low_voltage() { + // "step down" from strobe to something low + if (current_state == strobe_state) { + set_state(steady_state, RAMP_SIZE/6); + } + // in normal mode, step down by half or turn off + else if (current_state == steady_state) { + if (actual_level > 1) { + set_level(actual_level >> 1); + } + else { + set_state(off_state, 0); + } + } + // all other modes, just turn off when voltage is low + else { + set_state(off_state, 0); + } +} + + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + push_state(off_state, 0); +} + + +void loop() { + if (current_state == strobe_state) { + set_level(MAX_LEVEL); + if (strobe_type == 0) { // party strobe + if (strobe_delay < 30) delay_zero(); + else delay_ms(1); + } else { //tactical strobe + nice_delay_ms(strobe_delay >> 1); + } + set_level(0); + nice_delay_ms(strobe_delay); + } + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + battcheck(); + } + else if (current_state == tempcheck_state) { + blink_num(projected_temperature>>2); + nice_delay_ms(1000); + } + #endif +} -- cgit v1.2.3 From 0a801bff0f22be650aed6c3724c41cae03814d8f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 31 Aug 2017 23:45:36 -0600 Subject: Started a Meteor M43 clone UI. So far, UI1 and battcheck both work. UI2 and UI3 and other blinkies aren't implement yet. Added 6-bar battcheck style to match Meteor (ish). Increased maximum number of clicks to 12, because WTF. If your UI needs 12 clicks, what are you even doing in life? --- spaghetti-monster/fsm-adc.c | 5 + spaghetti-monster/fsm-adc.h | 2 +- spaghetti-monster/fsm-events.h | 168 ++++++++++++++++ spaghetti-monster/meteor/meteor.c | 413 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 spaghetti-monster/meteor/meteor.c (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 541f9a4..7f26fde 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -269,6 +269,11 @@ PROGMEM const uint8_t voltage_blinks[] = { 30, 35, 38, 40, 42, 99, }; #endif +#ifdef BATTCHECK_6bars +PROGMEM const uint8_t voltage_blinks[] = { + 30, 34, 36, 38, 40, 41, 43, 99, +}; +#endif #ifdef BATTCHECK_8bars PROGMEM const uint8_t voltage_blinks[] = { 30, 33, 35, 37, 38, 39, 40, 41, 42, 99, diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index af30b69..6317b0b 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -41,7 +41,7 @@ void battcheck(); #ifdef BATTCHECK_VpT #define USE_BLINK_NUM #endif -#if defined(BATTCHECK_8bars) || defined(BATTCHECK_4bars) +#if defined(BATTCHECK_8bars) || defined(BATTCHECK_6bars) || defined(BATTCHECK_4bars) #define USE_BLINK_DIGIT #endif #endif diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index e3edc77..c77facd 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -233,6 +233,156 @@ Event EV_click6_complete[] = { A_RELEASE_TIMEOUT, 0 }; #endif +#if MAX_CLICKS >= 7 +#define EV_7clicks EV_click7_complete +Event EV_click7_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 8 +#define EV_8clicks EV_click8_complete +Event EV_click8_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 9 +#define EV_9clicks EV_click9_complete +Event EV_click9_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 10 +#define EV_10clicks EV_click10_complete +Event EV_click10_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 11 +#define EV_11clicks EV_click11_complete +Event EV_click11_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif +#if MAX_CLICKS >= 12 +#define EV_12clicks EV_click12_complete +Event EV_click12_complete[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#endif // ... and so on // A list of button event types for easy iteration @@ -264,6 +414,24 @@ EventPtr event_sequences[] = { #if MAX_CLICKS >= 6 EV_click6_complete, #endif + #if MAX_CLICKS >= 7 + EV_click7_complete, + #endif + #if MAX_CLICKS >= 8 + EV_click8_complete, + #endif + #if MAX_CLICKS >= 9 + EV_click9_complete, + #endif + #if MAX_CLICKS >= 10 + EV_click10_complete, + #endif + #if MAX_CLICKS >= 11 + EV_click11_complete, + #endif + #if MAX_CLICKS >= 12 + EV_click12_complete, + #endif // ... }; diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c new file mode 100644 index 0000000..9a630cd --- /dev/null +++ b/spaghetti-monster/meteor/meteor.c @@ -0,0 +1,413 @@ +/* + * Meteor: Meteor M43 clone 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_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_4MS +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define USE_BATTCHECK +#define BATTCHECK_6bars +#define DONT_DELAY_AFTER_BATTCHECK +//#define USE_EEPROM +//#define EEPROM_BYTES 5 +#define MAX_CLICKS 11 +#include "spaghetti-monster.h" + +// FSM states +uint8_t base_off_state(EventPtr event, uint16_t arg); +uint8_t ui1_off_state(EventPtr event, uint16_t arg); +uint8_t ui2_off_state(EventPtr event, uint16_t arg); +uint8_t ui3_off_state(EventPtr event, uint16_t arg); +uint8_t base_on_state(EventPtr event, uint16_t arg, uint8_t *mode, uint8_t *group); +uint8_t ui1_on_state(EventPtr event, uint16_t arg); +uint8_t ui2_on_state(EventPtr event, uint16_t arg); +uint8_t ui3_on_state(EventPtr event, uint16_t arg); +uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t strobe_state(EventPtr event, uint16_t arg); +uint8_t biking_state(EventPtr event, uint16_t arg); +uint8_t lockout_state(EventPtr event, uint16_t arg); +uint8_t momentary_state(EventPtr event, uint16_t arg); +// Not a FSM state, just handles stuff common to all low/med/hi states +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); + +#ifdef USE_EEPROM +void load_config(); +void save_config(); +#endif + +// fixed output levels +uint8_t levels[] = {3, 16, 30, 43, 56, 70, 83, 96, 110, 123, 137, MAX_LEVEL}; +// select an interface +uint8_t UI = 1; // 1, 2, or 3 +// UI1 +uint8_t UI1_mode = 0; +uint8_t UI1_mode1 = 1; +uint8_t UI1_mode2 = 1; +uint8_t UI1_group1[] = {0, 2}; +uint8_t UI1_group2[] = {6, 9}; +// UI2 +uint8_t UI2_mode = 0; +uint8_t UI2_mode1 = 1; +uint8_t UI2_mode2 = 0; +uint8_t UI2_mode3 = 0; +uint8_t UI2_group1[] = {0, 2}; +uint8_t UI2_group2[] = {4, 6}; +uint8_t UI2_group3[] = {8, 10}; +// UI3 can access all levels, with 3 different mode memory slots +uint8_t UI3_mode = 0; +uint8_t UI3_mode1 = 2; +uint8_t UI3_mode2 = 5; +uint8_t UI3_mode3 = 8; + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +void set_any_mode(uint8_t mode, uint8_t *group) { + set_level(levels[group[mode]]); + #ifdef USE_THERMAL_REGULATION + target_level = actual_level; + #endif +} + +uint8_t base_off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + go_to_standby = 1; + // ensure we're in a real off state, not the base + switch(UI) { + case 1: set_state(ui1_off_state, 0); break; + case 2: set_state(ui2_off_state, 0); break; + default: set_state(ui3_off_state, 0); break; + } + return EVENT_HANDLED; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(beacon_state, 0); + return EVENT_HANDLED; + } + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return EVENT_HANDLED; + } + // 5 clicks: battcheck mode + else if (event == EV_5clicks) { + set_state(biking_state, 0); + return EVENT_HANDLED; + } + // 6 clicks: soft lockout mode + else if (event == EV_6clicks) { + set_state(lockout_state, 0); + return EVENT_HANDLED; + } + // 9 clicks: activate UI1 + else if (event == EV_9clicks) { + set_state(ui1_off_state, 0); + return EVENT_HANDLED; + } + // 10 clicks: activate UI2 + else if (event == EV_10clicks) { + set_state(ui2_off_state, 0); + return EVENT_HANDLED; + } + // 11 clicks: activate UI3 + else if (event == EV_11clicks) { + set_state(ui3_off_state, 0); + return EVENT_HANDLED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t ui1_off_state(EventPtr event, uint16_t arg) { + UI = 1; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + // 1 click: low modes + if (event == EV_1click) { + set_any_mode(UI1_mode1, UI1_group1); + set_state(ui1_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: high modes + else if (event == EV_2clicks) { + set_any_mode(UI1_mode2, UI1_group2); + set_state(ui1_on_state, 1); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t ui2_off_state(EventPtr event, uint16_t arg) { + UI = 2; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t ui3_off_state(EventPtr event, uint16_t arg) { + UI = 3; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t base_on_state(EventPtr event, uint16_t arg, uint8_t *mode, uint8_t *group) { + // 1 click: off + if (event == EV_1click) { + set_state(base_off_state, 0); + return MISCHIEF_MANAGED; + } + #ifdef USE_THERMAL_REGULATION + // overheating: drop by an amount proportional to how far we are above the ceiling + else if (event == EV_temperature_high) { + if (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return EVENT_HANDLED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return EVENT_HANDLED; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t ui1_on_state(EventPtr event, uint16_t arg) { + // turn on LED when entering the mode + static uint8_t *mode = &UI1_mode1; + static uint8_t *group = UI1_group1; + if (event == EV_enter_state) { + UI1_mode = arg; + } + if (UI1_mode == 0) { + mode = &UI1_mode1; + group = UI1_group1; + } + else { + mode = &UI1_mode2; + group = UI1_group2; + } + + if (event == EV_enter_state) { + set_any_mode(*mode, group); + return EVENT_HANDLED; + } + // 2 clicks: toggle moon/low or mid/high + else if (event == EV_2clicks) { + *mode ^= 1; + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) set_level(MAX_LEVEL); + return MISCHIEF_MANAGED; + } + // release: exit turbo + else if (event == EV_click1_hold_release) { + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + return base_on_state(event, arg, &UI1_mode1, UI1_group1); +} + +uint8_t ui2_on_state(EventPtr event, uint16_t arg) { + return base_on_state(event, arg, &UI2_mode1, UI2_group1); +} + +uint8_t ui3_on_state(EventPtr event, uint16_t arg) { + return base_on_state(event, arg, &UI3_mode1, levels); +} + + +uint8_t blinky_base_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(base_off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t beacon_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + return EVENT_NOT_HANDLED; +} + +uint8_t strobe_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t biking_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + + +void low_voltage() { + if ((current_state == ui1_on_state) || + (current_state == ui2_on_state) || + (current_state == ui3_on_state)) { + if (actual_level > 5) { + set_level(actual_level >> 1); + } + else { + set_state(base_off_state, 0); + } + } + /* + // "step down" from blinkies to low + else if (current_state == strobe_beacon_state) { + set_state(low_mode_state, 0); + } + */ +} + +void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { + set_level(level); + if (! nice_delay_ms(ontime)) return; + set_level(0); + nice_delay_ms(offtime); +} + +#ifdef USE_EEPROM +void load_config() { + if (load_eeprom()) { + H1 = !(!(eeprom[0] & 0b00000100)); + M1 = !(!(eeprom[0] & 0b00000010)); + L1 = !(!(eeprom[0] & 0b00000001)); + H2 = eeprom[1]; + M2 = eeprom[2]; + L2 = eeprom[3]; + strobe_beacon_mode = eeprom[4]; + } +} + +void save_config() { + eeprom[0] = (H1<<2) | (M1<<1) | (L1); + eeprom[1] = H2; + eeprom[2] = M2; + eeprom[3] = L2; + eeprom[4] = strobe_beacon_mode; + + save_eeprom(); +} +#endif + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + #ifdef USE_EEPROM + load_config(); + #endif + + push_state(base_off_state, 0); +} + +void loop() { + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + + /* + if (current_state == strobe_beacon_state) { + switch(strobe_beacon_mode) { + // 0.2 Hz beacon at L1 + case 0: + strobe(low_modes[0], 500, 4500); + break; + // 0.2 Hz beacon at H1 + case 1: + strobe(hi_modes[0], 500, 4500); + break; + // 4 Hz tactical strobe at H1 + case 2: + strobe(hi_modes[0], 83, 167); + break; + // 19 Hz tactical strobe at H1 + case 3: + strobe(hi_modes[0], 17, 35); + break; + } + } + */ + + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + nice_delay_ms(500); // wait a moment to measure voltage + battcheck(); + set_state(base_off_state, 0); + } + #endif +} + + -- cgit v1.2.3 From bb6314450b170220a884df49baf0b76cf549b3bc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 1 Sep 2017 00:16:03 -0600 Subject: Added UI2. UI2 is weird. Not sure I got everything the same; will have to try UI2 on my actual Meteor to see how it works. --- spaghetti-monster/meteor/meteor.c | 91 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c index 9a630cd..c2ea6ec 100644 --- a/spaghetti-monster/meteor/meteor.c +++ b/spaghetti-monster/meteor/meteor.c @@ -70,9 +70,11 @@ uint8_t UI2_mode = 0; uint8_t UI2_mode1 = 1; uint8_t UI2_mode2 = 0; uint8_t UI2_mode3 = 0; -uint8_t UI2_group1[] = {0, 2}; -uint8_t UI2_group2[] = {4, 6}; -uint8_t UI2_group3[] = {8, 10}; +uint8_t UI2_mode4 = 0; // doesn't matter, makes other code easier +uint8_t UI2_group1[] = { 0, 2}; // moon, low +uint8_t UI2_group2[] = { 4, 6}; // mid1, mid2 +uint8_t UI2_group3[] = { 8, 10}; // high1, high2 +uint8_t UI2_group4[] = {11, 11}; // turbo only // UI3 can access all levels, with 3 different mode memory slots uint8_t UI3_mode = 0; uint8_t UI3_mode1 = 2; @@ -94,6 +96,12 @@ void set_any_mode(uint8_t mode, uint8_t *group) { #endif } +void blink_fast() { + set_level(MAX_LEVEL/2); + delay_4ms(8/4); + set_level(0); +} + uint8_t base_off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { @@ -130,16 +138,19 @@ uint8_t base_off_state(EventPtr event, uint16_t arg) { } // 9 clicks: activate UI1 else if (event == EV_9clicks) { + blink_fast(); set_state(ui1_off_state, 0); return EVENT_HANDLED; } // 10 clicks: activate UI2 else if (event == EV_10clicks) { + blink_fast(); set_state(ui2_off_state, 0); return EVENT_HANDLED; } // 11 clicks: activate UI3 else if (event == EV_11clicks) { + blink_fast(); set_state(ui3_off_state, 0); return EVENT_HANDLED; } @@ -184,6 +195,31 @@ uint8_t ui2_off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { return EVENT_HANDLED; } + // 1 click: low modes + if (event == EV_1click) { + set_any_mode(UI2_mode1, UI2_group1); + set_state(ui2_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: high modes + else if (event == EV_2clicks) { + set_any_mode(UI2_mode3, UI2_group3); + set_state(ui2_on_state, 2); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } return base_off_state(event, arg); } @@ -261,11 +297,56 @@ uint8_t ui1_on_state(EventPtr event, uint16_t arg) { set_any_mode(*mode, group); return MISCHIEF_MANAGED; } - return base_on_state(event, arg, &UI1_mode1, UI1_group1); + return base_on_state(event, arg, mode, group); } uint8_t ui2_on_state(EventPtr event, uint16_t arg) { - return base_on_state(event, arg, &UI2_mode1, UI2_group1); + // turn on LED when entering the mode + static uint8_t *mode = &UI2_mode1; + static uint8_t *group = UI2_group1; + if (event == EV_enter_state) { + UI2_mode = arg; + } + switch (UI2_mode) { + case 0: + mode = &UI2_mode1; + group = UI2_group1; + break; + case 1: + mode = &UI2_mode2; + group = UI2_group2; + break; + case 2: + mode = &UI2_mode3; + group = UI2_group3; + break; + default: // turbo only + mode = &UI2_mode4; + group = UI2_group4; + break; + } + + if (event == EV_enter_state) { + set_any_mode(*mode, group); + return EVENT_HANDLED; + } + // 2 clicks: toggle moon/low, mid1/mid2, or high1/high2 + else if (event == EV_2clicks) { + *mode ^= 1; + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + // hold: rotate through low/mid/high/turbo + else if (event == EV_hold) { + if (arg % HOLD_TIMEOUT == 0) { + UI2_mode = (UI2_mode + 1) & 3; + } + else if (arg % HOLD_TIMEOUT == 1) { + set_any_mode(*mode, group); + } + return MISCHIEF_MANAGED; + } + return base_on_state(event, arg, mode, group); } uint8_t ui3_on_state(EventPtr event, uint16_t arg) { -- cgit v1.2.3 From a419850e536f00549120255a627137faffded47a Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 3 Sep 2017 14:58:22 -0600 Subject: Got the 4th PWM channel to work, ish. (channel 4 is inverted though) Moved go_to_suspend thing into main() instead of making each UI handle that during loop(). Made default_state() optional. Fixed bug where battcheck and other number readouts could interfere with the state which interrupted them. (they would sometimes turn the LED off after the new state had already started) Updated darkhorse's moon levels to match new ramp on D4 hardware. --- spaghetti-monster/anduril/anduril.c | 13 +---- spaghetti-monster/baton/baton-simpler.c | 8 --- spaghetti-monster/baton/baton.c | 3 +- spaghetti-monster/darkhorse/darkhorse.c | 13 +---- spaghetti-monster/fsm-events.c | 4 -- spaghetti-monster/fsm-main.c | 89 ++++++++++++++++++++----------- spaghetti-monster/fsm-misc.c | 2 +- spaghetti-monster/fsm-standby.h | 7 +++ spaghetti-monster/fsm-states.c | 11 ++-- spaghetti-monster/fsm-states.h | 2 + spaghetti-monster/momentary/momentary.c | 4 +- spaghetti-monster/ramping-ui/ramping-ui.c | 3 +- 12 files changed, 81 insertions(+), 78 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 998c0c5..c27ca68 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -114,9 +114,6 @@ uint8_t pseudo_rand(); // beacon timing volatile uint8_t beacon_seconds = 2; -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - uint8_t off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state @@ -452,8 +449,8 @@ uint8_t goodnight_state(EventPtr event, uint16_t arg) { static uint16_t ticks_since_stepdown = 0; // blink on start if (event == EV_enter_state) { - blink_confirm(4); ticks_since_stepdown = 0; + blink_confirm(2); set_level(GOODNIGHT_LEVEL); return MISCHIEF_MANAGED; } @@ -871,14 +868,6 @@ void setup() { void loop() { - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - if (current_state == strobe_state) { // party / tactical strobe if (strobe_type < 2) { diff --git a/spaghetti-monster/baton/baton-simpler.c b/spaghetti-monster/baton/baton-simpler.c index a6ddf36..96b0ea7 100644 --- a/spaghetti-monster/baton/baton-simpler.c +++ b/spaghetti-monster/baton/baton-simpler.c @@ -36,9 +36,6 @@ uint8_t actual_level = 0; uint8_t target_level = 0; #endif -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - // moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500 uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, }; uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, }; @@ -194,9 +191,4 @@ void setup() { } void loop() { - if (go_to_standby) { - go_to_standby = 0; - PWM1_LVL = 0; PWM2_LVL = 0; - standby_mode(); - } } diff --git a/spaghetti-monster/baton/baton.c b/spaghetti-monster/baton/baton.c index 515f2d0..1266ddd 100644 --- a/spaghetti-monster/baton/baton.c +++ b/spaghetti-monster/baton/baton.c @@ -56,8 +56,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { PWM1_LVL = 0; PWM2_LVL = 0; // sleep while off (lower power use) - //empty_event_sequence(); // just in case (but shouldn't be needed) - standby_mode(); + go_to_standby = 1; return 0; } // hold (initially): go to lowest level, but allow abort for regular click diff --git a/spaghetti-monster/darkhorse/darkhorse.c b/spaghetti-monster/darkhorse/darkhorse.c index 2aa3e90..97053b5 100644 --- a/spaghetti-monster/darkhorse/darkhorse.c +++ b/spaghetti-monster/darkhorse/darkhorse.c @@ -55,7 +55,7 @@ uint8_t L2 = 1; uint8_t M2 = 1; uint8_t H2 = 1; // mode groups, ish -uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm +uint8_t low_modes[] = {12, 3, 5, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm // strobe/beacon modes: @@ -65,9 +65,6 @@ uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 // 3: 19 Hz strobe at H1 uint8_t strobe_beacon_mode = 0; -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down uint8_t target_level = 0; @@ -337,14 +334,6 @@ void setup() { } void loop() { - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - if (current_state == strobe_beacon_state) { switch(strobe_beacon_mode) { // 0.2 Hz beacon at L1 diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 99a6a72..5448feb 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -170,8 +170,4 @@ void emit_current_event(uint16_t arg) { //return err; } -// TODO? add events to a queue when inside an interrupt -// instead of calling the event functions directly? -// (then empty the queue in main loop?) - #endif diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index 9505322..d526455 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -22,6 +22,20 @@ #include "fsm-main.h" +#if PWM_CHANNELS == 4 +// 4th PWM channel requires manually turning the pin on/off via interrupt :( +ISR(TIMER1_OVF_vect) { + //bitClear(PORTB, 3); + PORTB &= 0b11110111; + //PORTB |= 0b00001000; +} +ISR(TIMER1_COMPA_vect) { + //if (!bitRead(TIFR,TOV1)) bitSet(PORTB, 3); + if (! (TIFR & (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 << PWM1_PIN); - DDRB |= (1 << PWM2_PIN); - TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) - TCCR0A = PHASE; - #elif PWM_CHANNELS == 3 - 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); - TCCR1 = _BV (CS10); - GTCCR = _BV (COM1B1) | _BV (PWM1B); - OCR1C = 255; // Set ceiling value to maximum - #elif PWM_CHANNELS == 4 - DDRB |= (1 << PWM1_PIN); + #endif + #if PWM_CHANNELS >= 2 DDRB |= (1 << PWM2_PIN); - TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) - TCCR0A = PHASE; + #endif + #if PWM_CHANNELS >= 3 // 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 + #endif + #if PWM_CHANNELS >= 4 + // 4th PWM channel is ... not actually supported in hardware :( DDRB |= (1 << PWM4_PIN); + //OCR1C = 255; // Set ceiling value to maximum + TCCR1 = 1<= 1 + PWM1_LVL = 0; + #endif + #if PWM_CHANNELS >= 2 + PWM2_LVL = 0; + #endif + #if PWM_CHANNELS >= 3 + PWM3_LVL = 0; + #endif + #if PWM_CHANNELS >= 4 + PWM4_LVL = 255; // inverted :( + #endif + #endif + standby_mode(); + } + + // give the recipe some time slices loop(); } } diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c index 7322a59..5d28002 100644 --- a/spaghetti-monster/fsm-misc.c +++ b/spaghetti-monster/fsm-misc.c @@ -30,7 +30,7 @@ uint8_t blink_digit(uint8_t num) { for (; num>0; num--) { set_level(BLINK_BRIGHTNESS); - if (! nice_delay_ms(ontime)) { set_level(0); return 0; } + if (! nice_delay_ms(ontime)) { return 0; } set_level(0); //if (current_state != old_state) return 0; if (! nice_delay_ms(400)) return 0; diff --git a/spaghetti-monster/fsm-standby.h b/spaghetti-monster/fsm-standby.h index baeb6af..a23bd0c 100644 --- a/spaghetti-monster/fsm-standby.h +++ b/spaghetti-monster/fsm-standby.h @@ -20,7 +20,14 @@ #ifndef FSM_STANDBY_H #define FSM_STANDBY_H +// deferred "off" so we won't suspend in a weird state +// (like... during the middle of a strobe pulse) +// set this to nonzero to enter standby mode next time the system is idle +volatile uint8_t go_to_standby = 0; + #define standby_mode sleep_until_eswitch_pressed void sleep_until_eswitch_pressed(); +// TODO: half-sleep "twilight" mode with WDT on but running slowly + #endif diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c index 2939475..09ae804 100644 --- a/spaghetti-monster/fsm-states.c +++ b/spaghetti-monster/fsm-states.c @@ -67,26 +67,28 @@ StatePtr pop_state() { if (state_stack_len > 0) { new_state = state_stack[state_stack_len-1]; } - // FIXME: what should 'arg' be? + // FIXME: what should 'arg' be? (maybe re-entry should be entry with arg+1?) _set_state(new_state, 0, EV_leave_state, EV_reenter_state); return old_state; } uint8_t set_state(StatePtr new_state, uint16_t arg) { // FIXME: this calls exit/enter hooks it shouldn't + // (for the layer underneath the top) pop_state(); return push_state(new_state, arg); } +#ifndef DONT_USE_DEFAULT_STATE // bottom state on stack // handles default actions for LVP, thermal regulation, etc uint8_t default_state(EventPtr event, uint16_t arg) { - if (0) {} + if (0) {} // this should get compiled out #ifdef USE_LVP else if (event == EV_voltage_low) { low_voltage(); - return 0; + return EVENT_HANDLED; } #endif @@ -105,7 +107,8 @@ uint8_t default_state(EventPtr event, uint16_t arg) { #endif // event not handled - return 1; + return EVENT_NOT_HANDLED; } +#endif #endif diff --git a/spaghetti-monster/fsm-states.h b/spaghetti-monster/fsm-states.h index f92abba..6e4a2a0 100644 --- a/spaghetti-monster/fsm-states.h +++ b/spaghetti-monster/fsm-states.h @@ -40,6 +40,8 @@ void _set_state(StatePtr new_state, uint16_t arg, int8_t push_state(StatePtr new_state, uint16_t arg); StatePtr pop_state(); uint8_t set_state(StatePtr new_state, uint16_t arg); +#ifndef DONT_USE_DEFAULT_STATE uint8_t default_state(EventPtr event, uint16_t arg); +#endif #endif diff --git a/spaghetti-monster/momentary/momentary.c b/spaghetti-monster/momentary/momentary.c index 6b049f4..d4ac1db 100644 --- a/spaghetti-monster/momentary/momentary.c +++ b/spaghetti-monster/momentary/momentary.c @@ -52,7 +52,7 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { else if (event == EV_release) { light_off(); empty_event_sequence(); // don't attempt to parse multiple clicks - standby_mode(); // sleep while light is off + go_to_standby = 1; // sleep while light is off return 0; } @@ -68,7 +68,7 @@ void low_voltage() { } else { debug_blink(8); light_off(); - standby_mode(); + go_to_standby = 1; } } diff --git a/spaghetti-monster/ramping-ui/ramping-ui.c b/spaghetti-monster/ramping-ui/ramping-ui.c index 527a824..e6f571d 100644 --- a/spaghetti-monster/ramping-ui/ramping-ui.c +++ b/spaghetti-monster/ramping-ui/ramping-ui.c @@ -59,8 +59,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { set_level(0); // sleep while off (lower power use) - //empty_event_sequence(); // just in case (but shouldn't be needed) - standby_mode(); + go_to_standby = 1; return MISCHIEF_MANAGED; } // hold (initially): go to lowest level, but allow abort for regular click -- cgit v1.2.3 From e4ed3ff00a652f738b1cea483f8ff4abefa49c90 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 5 Sep 2017 03:43:18 -0600 Subject: Added momentary(ish) moon mode to lockout state. Made LVP step down in smaller steps. --- spaghetti-monster/anduril/anduril.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index c27ca68..dd06160 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -157,7 +157,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { #endif // 4 clicks: soft lockout else if (event == EV_4clicks) { - blink_confirm(5); + blink_confirm(2); set_state(lockout_state, 0); return MISCHIEF_MANAGED; } @@ -485,12 +485,31 @@ uint8_t goodnight_state(EventPtr event, uint16_t arg) { uint8_t lockout_state(EventPtr event, uint16_t arg) { + static uint8_t ticks_spent_awake = 0; + + // momentary(ish) moon mode during lockout + // not all presses will be counted; + // it depends on what is in the master event_sequences table + uint8_t last = 0; + for(uint8_t i=0; pgm_read_byte(event + i) && (i 180) { ticks_spent_awake = 0; @@ -500,10 +519,11 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { } // 4 clicks: exit else if (event == EV_4clicks) { - blink_confirm(2); + blink_confirm(1); set_state(off_state, 0); return MISCHIEF_MANAGED; } + return EVENT_NOT_HANDLED; } @@ -841,7 +861,7 @@ void low_voltage() { // in normal mode, step down by half or turn off else if (current_state == steady_state) { if (actual_level > 1) { - set_level(actual_level >> 1); + set_level((actual_level >> 1) + (actual_level >> 2)); } else { set_state(off_state, 0); -- cgit v1.2.3 From 5aa64f52af0505ebe257eafba2e46d2e477f61af Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 9 Sep 2017 14:38:55 -0600 Subject: Made "off" mode more vigilant about staying off. --- spaghetti-monster/anduril/anduril.c | 17 ++++++++++++++++- spaghetti-monster/spaghetti-monster.h | 3 --- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index dd06160..91c7b0b 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -63,6 +63,7 @@ uint8_t goodnight_state(EventPtr event, uint16_t arg); uint8_t beacon_state(EventPtr event, uint16_t arg); uint8_t beacon_config_state(EventPtr event, uint16_t arg); // soft lockout +#define MOON_DURING_LOCKOUT_MODE uint8_t lockout_state(EventPtr event, uint16_t arg); // momentary / signalling mode uint8_t momentary_state(EventPtr event, uint16_t arg); @@ -116,11 +117,22 @@ volatile uint8_t beacon_seconds = 2; uint8_t off_state(EventPtr event, uint16_t arg) { + static uint8_t ticks_spent_awake = 0; // turn emitter off when entering state if (event == EV_enter_state) { set_level(0); // sleep while off (lower power use) go_to_standby = 1; + ticks_spent_awake = 0; + return MISCHIEF_MANAGED; + } + // go back to sleep eventually if we got bumped but didn't leave "off" state + else if (event == EV_tick) { + ticks_spent_awake ++; + if (ticks_spent_awake > 240) { + ticks_spent_awake = 0; + go_to_standby = 1; + } return MISCHIEF_MANAGED; } // hold (initially): go to lowest level, but allow abort for regular click @@ -149,7 +161,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return MISCHIEF_MANAGED; } #ifdef USE_BATTCHECK - // 3 clicks: battcheck mode + // 3 clicks: battcheck mode / blinky mode group 1 else if (event == EV_3clicks) { set_state(battcheck_state, 0); return MISCHIEF_MANAGED; @@ -162,6 +174,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return MISCHIEF_MANAGED; } // 5 clicks: strobe mode + // TODO: remap this to click, click, long-click? else if (event == EV_5clicks) { set_state(strobe_state, 0); return MISCHIEF_MANAGED; @@ -487,6 +500,7 @@ uint8_t goodnight_state(EventPtr event, uint16_t arg) { uint8_t lockout_state(EventPtr event, uint16_t arg) { static uint8_t ticks_spent_awake = 0; + #ifdef MOON_DURING_LOCKOUT_MODE // momentary(ish) moon mode during lockout // not all presses will be counted; // it depends on what is in the master event_sequences table @@ -503,6 +517,7 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { else if ((last == A_RELEASE) || (last == A_RELEASE_TIMEOUT)) { set_level(0); } + #endif // regular event handling // conserve power while locked out diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h index 82800b4..5599bca 100644 --- a/spaghetti-monster/spaghetti-monster.h +++ b/spaghetti-monster/spaghetti-monster.h @@ -57,9 +57,6 @@ void debug_blink(uint8_t num) { } #endif -// TODO? new delay() functions which handle queue consumption? -// TODO? new interruptible delay() functions? - // Define these in your SpaghettiMonster recipe // boot-time tasks void setup(); -- cgit v1.2.3 From 7315575b2e7506417c2f2e81846e378d09949b5e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 9 Sep 2017 22:58:26 -0600 Subject: Made thermal regulation adjust smoothly (1 PWM step at a time) to make adjustments less noticeable. Added set_level_gradually() and gradual_tick() to ramping lib. Not terribly happy with it yet, but it at least works in practice. Added extra thermal regulation checks because I was running into stupid behavior in edge cases. Increased lowest thermal stepdown level to MAX_LEVEL/3 because it was going down way too low before. (is still pretty low, but not quite as bad) --- spaghetti-monster/anduril/anduril.c | 41 +++++++++- spaghetti-monster/fsm-ramping.c | 146 ++++++++++++++++++++++++++++++++++++ spaghetti-monster/fsm-ramping.h | 7 ++ 3 files changed, 190 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 91c7b0b..cf6f7f0 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -26,6 +26,7 @@ #define USE_DELAY_4MS #define USE_DELAY_ZERO #define USE_RAMPING +#define USE_SET_LEVEL_GRADUALLY #define RAMP_LENGTH 150 #define USE_BATTCHECK #define BATTCHECK_VpT @@ -127,6 +128,7 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return MISCHIEF_MANAGED; } // go back to sleep eventually if we got bumped but didn't leave "off" state + // FIXME: can I just use arg instead of ticks_spent_awake? else if (event == EV_tick) { ticks_spent_awake ++; if (ticks_spent_awake > 240) { @@ -310,24 +312,55 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return MISCHIEF_MANAGED; } + #ifdef USE_SET_LEVEL_GRADUALLY + else if (event == EV_tick) { + //if (!(arg & 7)) gradual_tick(); + if (!(arg & 3)) gradual_tick(); + //gradual_tick(); + return MISCHIEF_MANAGED; + } + #endif #ifdef USE_THERMAL_REGULATION // TODO: test this on a real light // overheating: drop by an amount proportional to how far we are above the ceiling else if (event == EV_temperature_high) { - if (actual_level > MAX_LEVEL/4) { - uint8_t stepdown = actual_level - arg; - if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + /* + uint8_t foo = actual_level; + set_level(0); + delay_4ms(2); + set_level(foo); + */ + if (actual_level > MAX_LEVEL/3) { + int16_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/3) stepdown = MAX_LEVEL/3; + else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL; + #ifdef USE_SET_LEVEL_GRADUALLY + set_level_gradually(stepdown); + #else set_level(stepdown); + #endif } return MISCHIEF_MANAGED; } // underheating: increase slowly if we're lower than the target // (proportional to how low we are) else if (event == EV_temperature_low) { + /* + uint8_t foo = actual_level; + set_level(0); + delay_4ms(2); + set_level(foo); + */ if (actual_level < target_level) { - uint8_t stepup = actual_level + (arg>>1); + //int16_t stepup = actual_level + (arg>>1); + int16_t stepup = actual_level + arg; if (stepup > target_level) stepup = target_level; + else if (stepup < MAX_LEVEL/3) stepup = MAX_LEVEL/3; + #ifdef USE_SET_LEVEL_GRADUALLY + set_level_gradually(stepup); + #else set_level(stepup); + #endif } return MISCHIEF_MANAGED; } diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c index ab7bd3c..50b4102 100644 --- a/spaghetti-monster/fsm-ramping.c +++ b/spaghetti-monster/fsm-ramping.c @@ -25,6 +25,9 @@ void set_level(uint8_t level) { actual_level = level; + #ifdef USE_SET_LEVEL_GRADUALLY + gradual_target = level; + #endif //TCCR0A = PHASE; if (level == 0) { #if PWM_CHANNELS >= 1 @@ -56,6 +59,149 @@ void set_level(uint8_t level) { } } +#ifdef USE_SET_LEVEL_GRADUALLY +inline void set_level_gradually(uint8_t lvl) { + gradual_target = lvl; +} + +// FIXME: This sucks +// FIXME: I think this can behave badly in some circumstances: +// - large smooth adjustment in progress +// - from something like (255,10) to (200, 0) +// - ... and a new target gets set while actual_level is ... not quite right +// - ... and it then ends up massively overshooting or even turning off +// (still haven't figured out the exact circumstances to cause it) +// (at the very least, actual_level can suddenly jump when multiple PWM levels are being adjusted) +// (so it's bad to rely on actual_level in the middle of a smooth adjustment) +void gradual_tick() { + int8_t changed = 0; + uint8_t target; + uint8_t gt = gradual_target - 1; + // FIXME: rewrite this completely, probably + // (perhaps go by only one ramp level at a time instead of directly to the target?) + #if PWM_CHANNELS >= 1 + target = pgm_read_byte(pwm1_levels + gt); + if (PWM1_LVL < target) { PWM1_LVL ++; changed = 1; } + else if (PWM1_LVL > target) { PWM1_LVL --; changed = -1; } + #endif + #if PWM_CHANNELS >= 2 + target = pgm_read_byte(pwm2_levels + gt); + if (PWM2_LVL < target) { PWM2_LVL ++; changed = 1; } + else if (PWM2_LVL > target) { PWM2_LVL --; changed = -1; } + #endif + #if PWM_CHANNELS >= 3 + target = pgm_read_byte(pwm3_levels + gt); + if (PWM3_LVL < target) { PWM3_LVL ++; changed = 1; } + else if (PWM3_LVL > target) { PWM3_LVL --; changed = -1; } + #endif + #if PWM_CHANNELS >= 4 + target = pgm_read_byte(pwm4_levels + gt); + if (PWM4_LVL < target) { PWM4_LVL ++; changed = 1; } + else if (PWM4_LVL > target) { PWM4_LVL --; changed = -1; } + #endif + // figure out what actual level is + /* + if (changed < 0) { + #if PWM_CHANNELS >= 4 + if (PWM4_LVL < pgm_read_byte(pwm4_levels + actual_level - 1)) + actual_level --; + else + #endif + #if PWM_CHANNELS >= 3 + if (PWM3_LVL < pgm_read_byte(pwm3_levels + actual_level - 1)) + actual_level --; + else + #endif + #if PWM_CHANNELS >= 2 + if (PWM2_LVL < pgm_read_byte(pwm2_levels + actual_level - 1)) + actual_level --; + else + #endif + if (PWM1_LVL < pgm_read_byte(pwm1_levels + actual_level - 1)) + actual_level --; + } + else if (changed > 0) { + #if PWM_CHANNELS >= 4 + if (PWM4_LVL > pgm_read_byte(pwm4_levels + actual_level - 1)) + actual_level ++; + else + #endif + #if PWM_CHANNELS >= 3 + if (PWM3_LVL > pgm_read_byte(pwm3_levels + actual_level - 1)) + actual_level ++; + else + #endif + #if PWM_CHANNELS >= 2 + if (PWM2_LVL > pgm_read_byte(pwm2_levels + actual_level - 1)) + actual_level ++; + else + #endif + if (PWM1_LVL > pgm_read_byte(pwm1_levels + actual_level - 1)) + actual_level ++; + } + else { + actual_level = gradual_target; + } + */ + /* + if (changed) { + #if PWM_CHANNELS >= 4 + if (PWM4_LVL != pgm_read_byte(pwm4_levels + actual_level - 1)) + actual_level += changed; + else + #endif + #if PWM_CHANNELS >= 3 + if (PWM3_LVL != pgm_read_byte(pwm3_levels + actual_level - 1)) + actual_level += changed; + else + #endif + #if PWM_CHANNELS >= 2 + if (PWM2_LVL != pgm_read_byte(pwm2_levels + actual_level - 1)) + actual_level += changed; + else + #endif + if (PWM1_LVL != pgm_read_byte(pwm1_levels + actual_level - 1)) + actual_level += changed; + } else { + actual_level = gradual_target; + } + */ + //if (! changed) { actual_level = gradual_target; } + if (changed) { + #if PWM_CHANNELS >= 4 + if (PWM4_LVL > 0) { + for (actual_level = 0; + (actual_level < MAX_LEVEL) && + (PWM4_LVL <= pgm_read_byte(pwm4_levels + actual_level - 1)); + actual_level ++) {} + } else + #endif + #if PWM_CHANNELS >= 3 + if (PWM3_LVL > 0) { + for (actual_level = 0; + (actual_level < MAX_LEVEL) && + (PWM3_LVL <= pgm_read_byte(pwm3_levels + actual_level - 1)); + actual_level ++) {} + } else + #endif + #if PWM_CHANNELS >= 2 + if (PWM2_LVL > 0) { + for (actual_level = 0; + (actual_level < MAX_LEVEL) && + (PWM2_LVL <= pgm_read_byte(pwm2_levels + actual_level - 1)); + actual_level ++) {} + } else + #endif + for (actual_level = 0; + (actual_level < MAX_LEVEL) && + (PWM1_LVL <= pgm_read_byte(pwm1_levels + actual_level - 1)); + actual_level ++) {} + } else { + actual_level = gradual_target; + } +} +#endif + // TODO: set_lvl_smooth? #endif // ifdef USE_RAMPING diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index b4054bf..ac4e58c 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -26,6 +26,13 @@ // actual_level: last ramp level set by set_level() volatile uint8_t actual_level = 0; +#ifdef USE_SET_LEVEL_GRADUALLY +// adjust brightness very smoothly +volatile uint8_t gradual_target; +inline void set_level_gradually(uint8_t lvl); +void gradual_tick(); +#endif + // ramp tables #if PWM_CHANNELS == 1 #if RAMP_LENGTH == 50 -- cgit v1.2.3 From fd3f600c7b17995ba6509802da71a3d2fd0fce6e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 9 Sep 2017 23:07:35 -0600 Subject: Reworked thermal regulation. Now actually works on turbo (was previously emitting "temp low" instead of "temp high" while in direct-drive, probably due to an overflow). Made stepdown work based on an average of the last few temperature predictions instead of just the most recent one. (reduced noise sensitivity) Made each temperature sample based on 8 measurements instead of 4, to reduce noise. Made standby mode re-init thermal measurement arrays, to avoid weird behavior next time light is used. Reduced fixed-point precision to avoid overflows. Reduced prediction strength to encourage stepping down faster while hot. (unfortunately also steps down later, I think, if it wasn't already hot) Not totally happy with new algorithm, but it's the least crappy of a whole bunch of things I tried. (for example, a PID approach with correction based mostly on I... didn't work very well) (taking an average of the last few predictions is very similar though, and works) (but the result is still kind of meh) Saving this so I'll have a functional base next time I try to improve it. --- spaghetti-monster/fsm-adc.c | 85 ++++++++++++++++++++++++++--------------- spaghetti-monster/fsm-adc.h | 1 + spaghetti-monster/fsm-standby.c | 5 +++ 3 files changed, 60 insertions(+), 31 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 7f26fde..4efb11c 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -60,12 +60,14 @@ ISR(ADC_vect) { // thermal declarations #ifdef USE_THERMAL_REGULATION - #define NUM_THERMAL_VALUES 4 + #define NUM_THERMAL_VALUES 8 #define NUM_THERMAL_VALUES_HISTORY 16 + #define NUM_THERMAL_PROJECTED_HISTORY 8 #define ADC_STEPS 4 - static uint8_t first_temp_reading = 1; static int16_t temperature_values[NUM_THERMAL_VALUES]; // last few readings in C - static int16_t temperature_history[NUM_THERMAL_VALUES_HISTORY]; // 13.2 fixed-point + static int16_t temperature_history[NUM_THERMAL_VALUES_HISTORY]; // 14.1 fixed-point + static int16_t projected_temperature_history[NUM_THERMAL_PROJECTED_HISTORY]; // 14.1 fixed-point + static uint8_t projected_temperature_history_counter = 0; static uint8_t temperature_timer = 0; static uint8_t overheat_lowpass = 0; static uint8_t underheat_lowpass = 0; @@ -143,18 +145,21 @@ ISR(ADC_vect) { int16_t temp = measurement - 275 + THERM_CAL_OFFSET; // prime on first execution - if (first_temp_reading) { - first_temp_reading = 0; + if (reset_thermal_history) { + reset_thermal_history = 0; for(uint8_t i=0; i> 2; // More precise method: use noise as extra precision // (values are now basically fixed-point, signed 13.2) - temperature = total; + //temperature = total; + // 14.1 is less prone to overflows + temperature = total >> 2; } // guess what the temperature will be in a few seconds { uint8_t i; int16_t diff; + int16_t t = temperature; // algorithm tweaking; not really intended to be modified // how far ahead should we predict? - #define THERM_PREDICTION_STRENGTH 4 + #define THERM_PREDICTION_STRENGTH 3 // how proportional should the adjustments be? - #define THERM_DIFF_ATTENUATION 4 + #define THERM_DIFF_ATTENUATION 2 // acceptable temperature window size in C #define THERM_WINDOW_SIZE 8 // highest temperature allowed - // (convert configured value to 13.2 fixed-point) - #define THERM_CEIL (therm_ceil<<2) - // bottom of target temperature window (13.2 fixed-point) - #define THERM_FLOOR (THERM_CEIL - (THERM_WINDOW_SIZE<<2)) + // (convert configured value to 14.1 fixed-point) + #define THERM_CEIL (((int16_t)therm_ceil)<<1) + // bottom of target temperature window (14.1 fixed-point) + #define THERM_FLOOR (THERM_CEIL - (THERM_WINDOW_SIZE<<1)) // rotate measurements and add a new one for (i=0; i THERM_FLOOR) { + // average prediction to reduce noise + int16_t avg_projected_temperature = 0; + for (uint8_t i = 0; i < NUM_THERMAL_PROJECTED_HISTORY; i++) + avg_projected_temperature += projected_temperature_history[i]; + avg_projected_temperature /= NUM_THERMAL_PROJECTED_HISTORY; + + // cancel counters if appropriate + if (avg_projected_temperature > THERM_FLOOR) { underheat_lowpass = 0; // we're definitely not too cold - } else if (projected_temperature < THERM_CEIL) { + } else if (avg_projected_temperature < THERM_CEIL) { overheat_lowpass = 0; // we're definitely not too hot } @@ -211,15 +232,16 @@ ISR(ADC_vect) { } else { // it has been long enough since the last warning // Too hot? - if (projected_temperature > THERM_CEIL) { + if (avg_projected_temperature > THERM_CEIL) { if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) { overheat_lowpass ++; } else { // how far above the ceiling? - int16_t howmuch = (projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION; - if (howmuch < 1) howmuch = 1; - // try to send out a warning - emit(EV_temperature_high, howmuch); + int16_t howmuch = (avg_projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION; + if (howmuch > 0) { + // try to send out a warning + emit(EV_temperature_high, howmuch); + } // reset counters temperature_timer = TEMPERATURE_TIMER_START; overheat_lowpass = 0; @@ -227,17 +249,18 @@ ISR(ADC_vect) { } // Too cold? - else if (projected_temperature < THERM_FLOOR) { + else if (avg_projected_temperature < THERM_FLOOR) { if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) { underheat_lowpass ++; } else { // how far below the floor? - int16_t howmuch = (THERM_FLOOR - projected_temperature) >> THERM_DIFF_ATTENUATION; - if (howmuch < 1) howmuch = 1; - // try to send out a warning (unless voltage is low) - // (LVP and underheat warnings fight each other) - if (voltage > VOLTAGE_LOW) - emit(EV_temperature_low, howmuch); + int16_t howmuch = (THERM_FLOOR - avg_projected_temperature) >> THERM_DIFF_ATTENUATION; + if (howmuch > 0) { + // try to send out a warning (unless voltage is low) + // (LVP and underheat warnings fight each other) + if (voltage > VOLTAGE_LOW) + emit(EV_temperature_low, howmuch); + } // reset counters temperature_timer = TEMPERATURE_TIMER_START; underheat_lowpass = 0; diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index 6317b0b..2a7c26d 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -72,6 +72,7 @@ volatile int16_t projected_temperature; // Fight the future! volatile uint8_t therm_ceil = DEFAULT_THERM_CEIL; //void low_temperature(); //void high_temperature(); +volatile uint8_t reset_thermal_history = 1; #endif diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c index 86c5f0d..44a2e0a 100644 --- a/spaghetti-monster/fsm-standby.c +++ b/spaghetti-monster/fsm-standby.c @@ -46,6 +46,11 @@ void sleep_until_eswitch_pressed() // something happened; wake up sleep_disable(); + + // forget what the temperature was last time we were on + reset_thermal_history = 1; + + // go back to normal running mode //PCINT_on(); // should be on already // FIXME? if button is down, make sure a button press event is added to the current sequence ADC_on(); -- cgit v1.2.3 From b34403f8ad26017a5bf311c1b168d4083282f473 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 9 Sep 2017 23:51:59 -0600 Subject: Rewrote gradual_tick(). Is smaller, better, and safer now. Decreased gradual adjustment speed again to make thermal regulation less stair-stepped. --- spaghetti-monster/anduril/anduril.c | 4 +- spaghetti-monster/fsm-ramping.c | 144 +++++++----------------------------- 2 files changed, 30 insertions(+), 118 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index cf6f7f0..91a74b2 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -314,8 +314,8 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } #ifdef USE_SET_LEVEL_GRADUALLY else if (event == EV_tick) { - //if (!(arg & 7)) gradual_tick(); - if (!(arg & 3)) gradual_tick(); + if (!(arg & 7)) gradual_tick(); + //if (!(arg & 3)) gradual_tick(); //gradual_tick(); return MISCHIEF_MANAGED; } diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c index 50b4102..092c242 100644 --- a/spaghetti-monster/fsm-ramping.c +++ b/spaghetti-monster/fsm-ramping.c @@ -64,145 +64,57 @@ inline void set_level_gradually(uint8_t lvl) { gradual_target = lvl; } -// FIXME: This sucks -// FIXME: I think this can behave badly in some circumstances: -// - large smooth adjustment in progress -// - from something like (255,10) to (200, 0) -// - ... and a new target gets set while actual_level is ... not quite right -// - ... and it then ends up massively overshooting or even turning off -// (still haven't figured out the exact circumstances to cause it) -// (at the very least, actual_level can suddenly jump when multiple PWM levels are being adjusted) -// (so it's bad to rely on actual_level in the middle of a smooth adjustment) +// call this every frame or every few frames to change brightness very smoothly void gradual_tick() { - int8_t changed = 0; - uint8_t target; + // go by only one ramp level at a time instead of directly to the target uint8_t gt = gradual_target - 1; - // FIXME: rewrite this completely, probably - // (perhaps go by only one ramp level at a time instead of directly to the target?) + if (gt < actual_level) gt = actual_level - 1; + else if (gt > actual_level) gt = actual_level + 1; + + uint8_t target; + #if PWM_CHANNELS >= 1 target = pgm_read_byte(pwm1_levels + gt); - if (PWM1_LVL < target) { PWM1_LVL ++; changed = 1; } - else if (PWM1_LVL > target) { PWM1_LVL --; changed = -1; } + if ((gt < actual_level) // special case for FET-only turbo + && (PWM1_LVL == 0) // (bypass adjustment period for first step) + && (target == 255)) PWM1_LVL = 255; + else if (PWM1_LVL < target) PWM1_LVL ++; + else if (PWM1_LVL > target) PWM1_LVL --; #endif #if PWM_CHANNELS >= 2 target = pgm_read_byte(pwm2_levels + gt); - if (PWM2_LVL < target) { PWM2_LVL ++; changed = 1; } - else if (PWM2_LVL > target) { PWM2_LVL --; changed = -1; } + if (PWM2_LVL < target) PWM2_LVL ++; + else if (PWM2_LVL > target) PWM2_LVL --; #endif #if PWM_CHANNELS >= 3 target = pgm_read_byte(pwm3_levels + gt); - if (PWM3_LVL < target) { PWM3_LVL ++; changed = 1; } - else if (PWM3_LVL > target) { PWM3_LVL --; changed = -1; } + if (PWM3_LVL < target) PWM3_LVL ++; + else if (PWM3_LVL > target) PWM3_LVL --; #endif #if PWM_CHANNELS >= 4 target = pgm_read_byte(pwm4_levels + gt); - if (PWM4_LVL < target) { PWM4_LVL ++; changed = 1; } - else if (PWM4_LVL > target) { PWM4_LVL --; changed = -1; } + if (PWM4_LVL < target) PWM4_LVL ++; + else if (PWM4_LVL > target) PWM4_LVL --; #endif - // figure out what actual level is - /* - if (changed < 0) { - #if PWM_CHANNELS >= 4 - if (PWM4_LVL < pgm_read_byte(pwm4_levels + actual_level - 1)) - actual_level --; - else - #endif - #if PWM_CHANNELS >= 3 - if (PWM3_LVL < pgm_read_byte(pwm3_levels + actual_level - 1)) - actual_level --; - else - #endif - #if PWM_CHANNELS >= 2 - if (PWM2_LVL < pgm_read_byte(pwm2_levels + actual_level - 1)) - actual_level --; - else - #endif - if (PWM1_LVL < pgm_read_byte(pwm1_levels + actual_level - 1)) - actual_level --; - } - else if (changed > 0) { - #if PWM_CHANNELS >= 4 - if (PWM4_LVL > pgm_read_byte(pwm4_levels + actual_level - 1)) - actual_level ++; - else - #endif - #if PWM_CHANNELS >= 3 - if (PWM3_LVL > pgm_read_byte(pwm3_levels + actual_level - 1)) - actual_level ++; - else - #endif + + // did we go far enough to hit the next defined ramp level? + // if so, update the main ramp level tracking var + if ((PWM1_LVL == pgm_read_byte(pwm1_levels + gt)) #if PWM_CHANNELS >= 2 - if (PWM2_LVL > pgm_read_byte(pwm2_levels + actual_level - 1)) - actual_level ++; - else - #endif - if (PWM1_LVL > pgm_read_byte(pwm1_levels + actual_level - 1)) - actual_level ++; - } - else { - actual_level = gradual_target; - } - */ - /* - if (changed) { - #if PWM_CHANNELS >= 4 - if (PWM4_LVL != pgm_read_byte(pwm4_levels + actual_level - 1)) - actual_level += changed; - else + && (PWM2_LVL == pgm_read_byte(pwm2_levels + gt)) #endif #if PWM_CHANNELS >= 3 - if (PWM3_LVL != pgm_read_byte(pwm3_levels + actual_level - 1)) - actual_level += changed; - else - #endif - #if PWM_CHANNELS >= 2 - if (PWM2_LVL != pgm_read_byte(pwm2_levels + actual_level - 1)) - actual_level += changed; - else + && (PWM3_LVL == pgm_read_byte(pwm3_levels + gt)) #endif - if (PWM1_LVL != pgm_read_byte(pwm1_levels + actual_level - 1)) - actual_level += changed; - } else { - actual_level = gradual_target; - } - */ - //if (! changed) { actual_level = gradual_target; } - if (changed) { #if PWM_CHANNELS >= 4 - if (PWM4_LVL > 0) { - for (actual_level = 0; - (actual_level < MAX_LEVEL) && - (PWM4_LVL <= pgm_read_byte(pwm4_levels + actual_level - 1)); - actual_level ++) {} - } else - #endif - #if PWM_CHANNELS >= 3 - if (PWM3_LVL > 0) { - for (actual_level = 0; - (actual_level < MAX_LEVEL) && - (PWM3_LVL <= pgm_read_byte(pwm3_levels + actual_level - 1)); - actual_level ++) {} - } else + && (PWM4_LVL == pgm_read_byte(pwm4_levels + gt)) #endif - #if PWM_CHANNELS >= 2 - if (PWM2_LVL > 0) { - for (actual_level = 0; - (actual_level < MAX_LEVEL) && - (PWM2_LVL <= pgm_read_byte(pwm2_levels + actual_level - 1)); - actual_level ++) {} - } else - #endif - for (actual_level = 0; - (actual_level < MAX_LEVEL) && - (PWM1_LVL <= pgm_read_byte(pwm1_levels + actual_level - 1)); - actual_level ++) {} - } else { - actual_level = gradual_target; + ) + { + actual_level = gt; } } #endif -// TODO: set_lvl_smooth? - #endif // ifdef USE_RAMPING #endif -- cgit v1.2.3 From b7d8faa65451a5c71b5b619c605e8623ecd627dc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 10 Sep 2017 00:27:30 -0600 Subject: Made thermal regulation slightly more prediction-heavy, less noisy at stable state, and slightly smaller. Added a bit of overflow protection since I think I saw it overflow with the prediction weight increased. Uses immediate temperature (instead of avg) for lowpass reset, to make it less noisy at stable state. (more trigger-happy reset switch biases it toward inaction) Avoid using volatile var, to reduce space. Overflow prevention also caps the maximum adjustment step, which should slightly help prevent overshooting the target. --- spaghetti-monster/fsm-adc.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 4efb11c..699c6cb 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -177,6 +177,7 @@ ISR(ADC_vect) { } // guess what the temperature will be in a few seconds + int16_t pt; { uint8_t i; int16_t diff; @@ -184,7 +185,7 @@ ISR(ADC_vect) { // algorithm tweaking; not really intended to be modified // how far ahead should we predict? - #define THERM_PREDICTION_STRENGTH 3 + #define THERM_PREDICTION_STRENGTH 4 // how proportional should the adjustments be? #define THERM_DIFF_ATTENUATION 2 // acceptable temperature window size in C @@ -207,24 +208,26 @@ ISR(ADC_vect) { diff = t - temperature_history[0]; // projected_temperature = current temp extended forward by amplified rate of change //projected_temperature = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] + (diff< THERM_FLOOR) { - underheat_lowpass = 0; // we're definitely not too cold - } else if (avg_projected_temperature < THERM_CEIL) { - overheat_lowpass = 0; // we're definitely not too hot + if (pt > THERM_FLOOR) { + underheat_lowpass = 0; // we're probably not too cold + } else if (pt < THERM_CEIL) { + overheat_lowpass = 0; // we're probably not too hot } if (temperature_timer) { -- cgit v1.2.3 From 4c2ac2d287fd15e1db51833c0a6cb4915191edb7 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 10 Sep 2017 01:33:04 -0600 Subject: Adjusted thermal regulation based on results of some handheld runtime tests. It was bouncing before, and now it's not. (cut response magnitude in half, increased width of null zone) --- spaghetti-monster/fsm-adc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 699c6cb..02fd8c2 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -187,9 +187,9 @@ ISR(ADC_vect) { // how far ahead should we predict? #define THERM_PREDICTION_STRENGTH 4 // how proportional should the adjustments be? - #define THERM_DIFF_ATTENUATION 2 + #define THERM_DIFF_ATTENUATION 3 // acceptable temperature window size in C - #define THERM_WINDOW_SIZE 8 + #define THERM_WINDOW_SIZE 10 // highest temperature allowed // (convert configured value to 14.1 fixed-point) #define THERM_CEIL (((int16_t)therm_ceil)<<1) -- cgit v1.2.3 From d4a3e889ec53155a913763a52fd4a82bee14d95c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 10 Sep 2017 02:37:39 -0600 Subject: Moved strobes from 5 clicks to "click, click, long-click". Moved momentary mode from 6 clicks to 5 clicks. --- spaghetti-monster/anduril/anduril.c | 15 +++++++-------- spaghetti-monster/fsm-events.h | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 91a74b2..6cc0095 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -30,7 +30,7 @@ #define RAMP_LENGTH 150 #define USE_BATTCHECK #define BATTCHECK_VpT -#define MAX_CLICKS 6 +#define MAX_CLICKS 5 #define USE_EEPROM #define EEPROM_BYTES 12 #include "spaghetti-monster.h" @@ -169,20 +169,19 @@ uint8_t off_state(EventPtr event, uint16_t arg) { return MISCHIEF_MANAGED; } #endif + // click, click, long-click: strobe mode + else if (event == EV_click3_hold) { + set_state(strobe_state, 0); + return MISCHIEF_MANAGED; + } // 4 clicks: soft lockout else if (event == EV_4clicks) { blink_confirm(2); set_state(lockout_state, 0); return MISCHIEF_MANAGED; } - // 5 clicks: strobe mode - // TODO: remap this to click, click, long-click? + // 5 clicks: momentary mode else if (event == EV_5clicks) { - set_state(strobe_state, 0); - return MISCHIEF_MANAGED; - } - // 6 clicks: momentary mode - else if (event == EV_6clicks) { blink_confirm(1); set_state(momentary_state, 0); return MISCHIEF_MANAGED; diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index c77facd..074b459 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -166,6 +166,25 @@ Event EV_click3_press[] = { A_RELEASE, A_PRESS, 0 }; +Event EV_click3_hold[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_HOLD, + 0 }; +/* +Event EV_click3_hold_release[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + A_PRESS, + A_HOLD, + A_RELEASE, + 0 }; + */ Event EV_click3_release[] = { A_PRESS, A_RELEASE, @@ -402,6 +421,8 @@ EventPtr event_sequences[] = { #endif #if MAX_CLICKS >= 3 EV_click3_press, + EV_click3_hold, + //EV_click3_hold_release, EV_click3_release, EV_click3_complete, #endif -- cgit v1.2.3 From a23cc299a2738878ff535b9c7ba01efbe6e5983b Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 10 Sep 2017 02:48:23 -0600 Subject: Applied recent changes to UI description text. --- spaghetti-monster/anduril/anduril.txt | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index bb0f5e2..2fb74bf 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -1,15 +1,20 @@ From off: + + Ramp shortcuts: * 1 click: memorized level * Hold: lowest level then ramp up * 2 clicks: highest ramp level * Click, hold: highest level then ramp down - * 3 clicks: battcheck mode - (battcheck, goodnight, beacon, tempcheck) - * 4 clicks: lock-out - * 5 clicks: strobe modes + + Blinkies: + * 3 clicks: specials (battcheck, goodnight, beacon, tempcheck) + * Click, click, hold: strobes (bike flasher, party strobe, tactical strobe, lightning storm mode) (remembers which you last used) - * 6 clicks: momentary mode (disconnect power to exit) + + Other: + * 4 clicks: lock-out + * 5 clicks: momentary mode (disconnect power to exit) In steady mode: * 1 click: off @@ -83,6 +88,10 @@ Thermal calibration mode: - Hold until hot: set new ceiling value - ... don't hold: blink out current ceiling value and exit +Lockout mode: + * Hold: momentary moon + * 4 clicks: exit lockout (return to regular "off" mode) + Momentary mode: * Press button: Light on (at memorized level). * Release button: Light off. @@ -91,8 +100,8 @@ Momentary mode: TODO: * save settings in eeprom - decide on "hold until hot" or "click N times" for thermal config mode - - test thermal regulation on an actual light - - improve thermal regulation + * test thermal regulation on an actual light + * improve thermal regulation - a way to blink out the firmware version? - indicator LED support - a way to configure indicator LED behavior? -- cgit v1.2.3 From e5864361c5e05aa5356d06cba6a19441ea0dc996 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 10 Sep 2017 16:38:40 -0600 Subject: Fixed a bug in gradual_tick(), I think. It was off by one, and caused actual_level to be off by one, which caused other parts of the code to do weird things... - end-of-ramp blink would happen repeatedly. - ramping up went slower (or not at all, with gradual_tick on each tick) - would sometimes trigger underheating code at moon level, and then glide up to the minimum regulation floor --- spaghetti-monster/anduril/anduril.c | 1 - spaghetti-monster/fsm-ramping.c | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 6cc0095..3d08b26 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -320,7 +320,6 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } #endif #ifdef USE_THERMAL_REGULATION - // TODO: test this on a real light // overheating: drop by an amount proportional to how far we are above the ceiling else if (event == EV_temperature_high) { /* diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c index 092c242..cc5f486 100644 --- a/spaghetti-monster/fsm-ramping.c +++ b/spaghetti-monster/fsm-ramping.c @@ -67,10 +67,12 @@ inline void set_level_gradually(uint8_t lvl) { // call this every frame or every few frames to change brightness very smoothly void gradual_tick() { // go by only one ramp level at a time instead of directly to the target - uint8_t gt = gradual_target - 1; + uint8_t gt = gradual_target; if (gt < actual_level) gt = actual_level - 1; else if (gt > actual_level) gt = actual_level + 1; + gt --; // convert 1-based number to 0-based + uint8_t target; #if PWM_CHANNELS >= 1 @@ -111,7 +113,7 @@ void gradual_tick() { #endif ) { - actual_level = gt; + actual_level = gt + 1; } } #endif -- cgit v1.2.3 From 91fcc0b8ce25161692a7ef79cbc06d8c735a0318 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 07:20:54 -0600 Subject: Added FW3A driver support to FSM and Anduril. Made lightning storm mode look a bit more like real lightning. Minor refactoring on how single-option config modes save state. --- spaghetti-monster/anduril/anduril.c | 44 +++++++++++++++++++++++++------------ spaghetti-monster/fsm-ramping.h | 12 +++++----- 2 files changed, 36 insertions(+), 20 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 3d08b26..22f6eb9 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -19,9 +19,10 @@ */ #define FSM_EMISAR_D4_DRIVER +//#define FSM_FW3A_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION -#define DEFAULT_THERM_CEIL 45 +#define DEFAULT_THERM_CEIL 50 #define USE_DELAY_MS #define USE_DELAY_4MS #define USE_DELAY_ZERO @@ -283,6 +284,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // only blink once for each threshold if ((memorized_level != actual_level) && ((memorized_level == MAX_1x7135) + #if PWM_CHANNELS >= 3 + || (memorized_level == MAX_Nx7135) + #endif || (memorized_level == mode_max))) { set_level(0); delay_4ms(8/4); @@ -304,6 +308,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // only blink once for each threshold if ((memorized_level != actual_level) && ((memorized_level == MAX_1x7135) + #if PWM_CHANNELS >= 3 + || (memorized_level == MAX_Nx7135) + #endif || (memorized_level == mode_min))) { set_level(0); delay_4ms(8/4); @@ -678,18 +685,17 @@ uint8_t thermal_config_state(EventPtr event, uint16_t arg) { } // advance forward through config steps else if (event == EV_tick) { - if (! done) push_state(number_entry_state, 1); - else { - save_config(); - // return to beacon mode - set_state(tempcheck_state, 0); - } + // ask the user for a number + if (! done) push_state(number_entry_state, 0); + // return to original mode + else set_state(tempcheck_state, 0); return MISCHIEF_MANAGED; } // an option was set (return from number_entry_state) else if (event == EV_reenter_state) { if (number_entry_value) therm_ceil = 30 + number_entry_value; if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; + save_config(); done = 1; return MISCHIEF_MANAGED; } @@ -706,17 +712,16 @@ uint8_t beacon_config_state(EventPtr event, uint16_t arg) { } // advance forward through config steps else if (event == EV_tick) { - if (! done) push_state(number_entry_state, 1); - else { - save_config(); - // return to beacon mode - set_state(beacon_state, 0); - } + // ask the user for a number + if (! done) push_state(number_entry_state, 0); + // return to original mode + else set_state(beacon_state, 0); return MISCHIEF_MANAGED; } // an option was set (return from number_entry_state) else if (event == EV_reenter_state) { if (number_entry_value) beacon_seconds = number_entry_value; + save_config(); done = 1; return MISCHIEF_MANAGED; } @@ -955,7 +960,8 @@ void loop() { // turn the emitter on at a random level, // for a random amount of time between 1ms and 32ms - rand_time = 1 << (pseudo_rand() % 6); + //rand_time = 1 << (pseudo_rand() % 7); + rand_time = pseudo_rand() & 63; brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64 brightness += 1 << (pseudo_rand()&0x03); // 2 to 80 now brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) @@ -971,6 +977,16 @@ void loop() { brightness -= stepdown; if (brightness < 0) brightness = 0; set_level(brightness); + /* + if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) { + brightness <<= 1; + set_level(brightness); + } + */ + if (! (pseudo_rand() & 3)) { + if (! nice_delay_ms(rand_time)) return; + set_level(brightness>>1); + } } // turn the emitter off, diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index ac4e58c..04beb31 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -89,12 +89,12 @@ void gradual_tick(); #define MAX_Nx7135 59 #elif RAMP_LENGTH == 150 // FIXME: These values aren't tweaked or tested at all - // ../../bin/level_calc.py 3 150 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000 - PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,6,6,7,7,8,9,10,11,12,13,15,16,18,20,22,24,26,28,31,33,36,39,42,46,49,53,57,61,65,70,75,80,85,90,96,102,108,115,121,128,136,143,151,159,167,176,185,194,204,214,224,235,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; - PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,6,8,10,13,15,17,20,22,25,28,30,33,36,39,42,45,48,51,55,58,62,65,69,73,76,80,84,88,92,97,101,105,110,115,119,124,129,134,139,144,149,155,160,166,171,177,183,189,195,201,207,214,220,227,234,241,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; - PROGMEM const uint8_t pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,10,17,23,30,36,43,50,57,64,71,78,85,93,100,108,115,123,131,139,148,156,164,173,182,190,199,208,217,227,236,245,255 }; - #define MAX_1x7135 59 - #define MAX_Nx7135 117 + // ../../../bin/level_calc.py 3 150 7135 1 0.33 150 7135 1 1 850 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 1,1,1,2,2,2,3,3,4,5,5,6,7,8,9,10,11,12,14,15,17,19,21,23,25,27,29,32,34,37,40,43,46,50,53,57,61,65,69,74,78,83,88,93,99,104,110,116,122,129,135,142,149,157,164,172,180,189,197,206,215,225,235,244,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,6,8,10,13,15,17,19,22,24,26,29,31,34,37,39,42,45,48,51,54,57,60,64,67,70,74,77,81,85,88,92,96,100,104,108,112,116,121,125,130,134,139,143,148,153,158,163,168,173,179,184,189,195,201,206,212,218,224,230,236,243,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,19,31,43,55,67,79,91,104,117,130,143,157,170,184,198,212,226,240,255 }; + #define MAX_1x7135 65 + #define MAX_Nx7135 130 #endif #elif PWM_CHANNELS == 4 4-channel PWM not really supported yet, sorry. -- cgit v1.2.3 From e4634255795f04a1d859aa691fbc6e729973b14d Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 07:22:16 -0600 Subject: Slightly increased resolution of VOLTAGE_FUDGE_FACTOR. My lights seemed to measure just a bit low, so hopefully this will help. It bumps up reported values by 0.05V. --- spaghetti-monster/fsm-adc.c | 3 ++- spaghetti-monster/fsm-adc.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 02fd8c2..5be3061 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -108,7 +108,8 @@ ISR(ADC_vect) { // 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; + //voltage = (uint16_t)(1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR; + voltage = ((uint16_t)(2*1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR) >> 1; #endif // if low, callback EV_voltage_low / EV_voltage_critical // (but only if it has been more than N ticks since last call) diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index 2a7c26d..b9462f7 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -30,9 +30,9 @@ #ifndef VOLTAGE_LOW #define VOLTAGE_LOW 29 #endif -// MCU sees voltage 0.X volts lower than actual, add X to readings +// MCU sees voltage 0.X volts lower than actual, add X/2 to readings #ifndef VOLTAGE_FUDGE_FACTOR -#define VOLTAGE_FUDGE_FACTOR 2 +#define VOLTAGE_FUDGE_FACTOR 5 #endif volatile uint8_t voltage; void low_voltage(); -- cgit v1.2.3 From 8b9cb8116df4e9bd06b38e3a3c2994520e88006f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 07:24:23 -0600 Subject: Updated Meteor UI to use provided go_to_standby. Started on UI3, but it's not really working yet. Needs some underlying plumbing changes first, adding support for medium-length clicks. --- spaghetti-monster/meteor/meteor.c | 86 +++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 12 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c index c2ea6ec..3ec79f4 100644 --- a/spaghetti-monster/meteor/meteor.c +++ b/spaghetti-monster/meteor/meteor.c @@ -81,9 +81,6 @@ uint8_t UI3_mode1 = 2; uint8_t UI3_mode2 = 5; uint8_t UI3_mode3 = 8; -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down uint8_t target_level = 0; @@ -228,6 +225,37 @@ uint8_t ui3_off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { return EVENT_HANDLED; } + // 1 click: memory slot 1 + if (event == EV_1click) { + set_level(levels[UI3_mode1]); + set_state(ui3_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: memory slot 2 + else if (event == EV_2clicks) { + set_level(levels[UI3_mode2]); + set_state(ui3_on_state, 1); + return EVENT_HANDLED; + } + // Click, hold: memory slot 3 + else if (event == EV_click2_hold) { + set_level(levels[UI3_mode3]); + set_state(ui3_on_state, 2); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } return base_off_state(event, arg); } @@ -350,7 +378,49 @@ uint8_t ui2_on_state(EventPtr event, uint16_t arg) { } uint8_t ui3_on_state(EventPtr event, uint16_t arg) { - return base_on_state(event, arg, &UI3_mode1, levels); + // turn on LED when entering the mode + static uint8_t *mode = &UI3_mode1; + if (event == EV_enter_state) { + UI3_mode = arg; + } + // 2 clicks: rotate through mode1/mode2/mode3 + else if (event == EV_2clicks) { + UI3_mode = (UI3_mode + 1) % 3; + } + // short click, long click: rotate through mode3/mode2/mode1 + /* + else if (event == EV_click1_hold) { + if (arg % HOLD_TIMEOUT == 0) + UI3_mode = (UI3_mode + 4) % 3; + } + */ + switch (UI3_mode) { + case 0: + mode = &UI3_mode1; + break; + case 1: + mode = &UI3_mode2; + break; + default: + mode = &UI3_mode3; + break; + } + + if ((event == EV_enter_state) || (event == EV_2clicks)) { + set_level(levels[*mode]); + return EVENT_HANDLED; + } + // short click, long click: rotate through mode3/mode2/mode1 + /* + else if (event == EV_click1_hold) { + set_level(levels[*mode]); + return MISCHIEF_MANAGED; + } + */ + // hold: turbo + // Click, hold: ramp up + // release hold, hold again: ramp in opposite direction + return base_on_state(event, arg, mode, levels); } @@ -451,14 +521,6 @@ void setup() { } void loop() { - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - /* if (current_state == strobe_beacon_state) { switch(strobe_beacon_mode) { -- cgit v1.2.3 From eec75332c6f876277f4afa5b3200ae0682644deb Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 15:24:39 -0600 Subject: Added anduril UI diagram. --- spaghetti-monster/anduril/anduril-ui.png | Bin 0 -> 229400 bytes spaghetti-monster/anduril/anduril.svg | 3428 ++++++++++++++++++++++++++++++ 2 files changed, 3428 insertions(+) create mode 100644 spaghetti-monster/anduril/anduril-ui.png create mode 100644 spaghetti-monster/anduril/anduril.svg (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril-ui.png b/spaghetti-monster/anduril/anduril-ui.png new file mode 100644 index 0000000..555e9a4 Binary files /dev/null and b/spaghetti-monster/anduril/anduril-ui.png differ diff --git a/spaghetti-monster/anduril/anduril.svg b/spaghetti-monster/anduril/anduril.svg new file mode 100644 index 0000000..11ee01c --- /dev/null +++ b/spaghetti-monster/anduril/anduril.svg @@ -0,0 +1,3428 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Ramps: + Blinkies + Strobes + + + + + + + + + + BattCheck + + + + TempCheck + + + Beacon + + + + Good + + Night + + + ThermalCfg + + + + + Beacon + + Cfg + + + + Bike + + Flasher + + + Party + + Strobe + + + Tactical + + Strobe + + + Lightning + + Storm + + + + + + + Faster + + Slower + + Faster + + Slower + + Brighter + + Dimmer + + Ramp + Ceil + Floor + + + Turbo + + + + + Mem + Regulated Hybrid -------------- Direct Drive + + + + + + + + + + + Ramp Cfg + + 1. Floor (click N times for level N)2. Ceiling (click N times for Turbo-N)3. Number of steps (discrete ramp only) + 1. Temperature limit (click N times for 30 C + N) + 1. Beacon speed (click N times for N seconds per flash) + + + Ramp + + Cfg + + + + + + Actions + 1 Fast Click + Hold + 3 Fast Clicks + Other Action + + + + 2 Fast Clicks + Click, Hold + 4 Clicks + Andúril‎ UI + + + + + + OFF + + + + OFF + + + Smooth / Discrete + Smooth /Discrete + Thermal Cfg + Beacon Cfg + + + Lockout + + + + + Momentary + + Signalling + + + + + + 4 Clicks + 5 Clicks + Click, Click, Hold + + + -- cgit v1.2.3 From 3db29bba83565fb754914fa4a280ca31ae4d8471 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 16:32:29 -0600 Subject: Updated the FSM docs a bit, for things added or changed since the last time I touched the docs. --- spaghetti-monster/spaghetti-monster.txt | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/spaghetti-monster.txt b/spaghetti-monster/spaghetti-monster.txt index 8393652..0401224 100644 --- a/spaghetti-monster/spaghetti-monster.txt +++ b/spaghetti-monster/spaghetti-monster.txt @@ -70,6 +70,10 @@ Finite State Machine: function instead. Preferably with precautions taken to allow for cooperative multitasking. + If your State function takes longer than one WDT tick (16ms) once in a + while, the system won't break. Several events can be queued. But be + sure not to do it very often. + Several state management functions are provided: - set_state(new_state, arg): Replace the current state on the stack. @@ -82,6 +86,7 @@ Finite State Machine: - pop_state(): Get rid of (and return) the top-most state. Re-enter the state below. + Event types: Event types are defined in fsm-events.h. You may want to adjust these @@ -95,6 +100,11 @@ Event types: - EV_leave_state: Sent to a state immediately before it is removed from the stack. + - EV_reenter_state: If a State gets pushed on top of this one, and + then it pops off, a re-enter Event happens. This should handle + things like consuming the return value of a nested input handler + State. + Time passing: - EV_tick: This happens once per clock tick, which is 16ms or @@ -165,6 +175,12 @@ Event types: emit() them as necessary, and handle them in State functions the same as any other event. + One thing to note if you create your own Event types: The copy which + gets sent to States must be in the 'event_sequences' array, meaning + the State gets a const PROGMEM version of the Event. It cannot simply + send the dynamic 'current_event' object, because it has probably + already changed by the time the callback happens. + Cooperative multitasking: @@ -197,6 +213,36 @@ Cooperative multitasking: at once. +Persistent data in EEPROM: + + To save data which lasts after a battery change, use the eeprom + functions. Define an eeprom style (or two) at the top, define how + many bytes to allocate, and then use the relevant functions as + appropriate. + + - USE_EEPROM / USE_EEPROM_WL: Enable the eeprom-related functions. + With "WL", it uses wear-levelling. Without, it does not. Note: + Wear levelling is not necessarily better -- it uses more ROM, and + it writes more bytes per save(). So, use it only for a few bytes + which change frequently -- not for many bytes or infrequent + changes. + + - EEPROM_BYTES N / EEPROM_WL_BYTES N: Allocate N bytes for the + eeprom data. + + - load_eeprom() / load_eeprom_wl(): Load the stored data into the + eeprom[] or eeprom_wl[] arrays. + Returns 1 if data was found, 0 otherwise. + + - save_eeprom() / save_eeprom_wl(): Save the eeprom[] or eeprom_wl[] + array data to persistent storage. The WL version erases all old + values and writes new ones in a different part of the eeprom + space. The non-WL version updates values in place, and does not + overwrite values which didn't change. + + Note that all interrupts will be disabled during eeprom operations. + + Useful #defines: A variety of things can be #defined before including @@ -241,6 +287,18 @@ Useful #defines: becomes a "click" event? Basically, the maximum time between clicks in a double-click or triple-click. + - MAX_CLICKS N: Convenience define to limit the size of the + recognized Event arrays. Click sequences longer than N won't be + recognized or sent to State functions. + + - USE_BATTCHECK: Enable the battcheck function. Also define one of + the following to select a display style: + + - BATTCHECK_VpT: Volts, pause, tenths. + - BATTCHECK_4bars: Blink up to 4 times. + - BATTCHECK_6bars: Blink up to 6 times. + - BATTCHECK_8bars: Blink up to 8 times. + - ... and many others. Will try to document them over time, but they can be found by searching for pretty much anything in all-caps in the fsm-*.[ch] files. -- cgit v1.2.3 From 7bd30320d3d35e0d12a296107d6f362617208948 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 21:30:01 -0600 Subject: Forgot to include a copy of the GPL before. It's in other parts of the repo, but it should be in the FSM dir too. --- spaghetti-monster/COPYING | 674 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 spaghetti-monster/COPYING (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/COPYING b/spaghetti-monster/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/spaghetti-monster/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. -- cgit v1.2.3 From 520f5cdb983045c5518325f3c3665f59ca85435e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 24 Sep 2017 20:54:07 -0600 Subject: Added idle_mode() for slightly lower power use without turning off any regular functions. (PWM, ADC, WDT all still enabled; only useful in moon mode) Changed default ceilings in Anduril FW3A config. --- spaghetti-monster/anduril/anduril.c | 17 ++++++++++++++++- spaghetti-monster/fsm-main.c | 16 ++++++++++++++-- spaghetti-monster/fsm-standby.c | 17 +++++++++++++++++ spaghetti-monster/fsm-standby.h | 7 +++++++ 4 files changed, 54 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 22f6eb9..d15b5cb 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -34,6 +34,7 @@ #define MAX_CLICKS 5 #define USE_EEPROM #define EEPROM_BYTES 12 +#define USE_IDLE_MODE #include "spaghetti-monster.h" // Options specific to this UI (not inherited from SpaghettiMonster) @@ -86,9 +87,14 @@ uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete volatile uint8_t ramp_smooth_floor = 5; +#if PWM_CHANNELS == 3 +volatile uint8_t ramp_smooth_ceil = MAX_Nx7135; +volatile uint8_t ramp_discrete_ceil = MAX_Nx7135; +#else volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30; -volatile uint8_t ramp_discrete_floor = 20; volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30; +#endif +volatile uint8_t ramp_discrete_floor = 20; volatile uint8_t ramp_discrete_steps = 7; uint8_t ramp_discrete_step_size; // don't set this @@ -323,6 +329,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (!(arg & 7)) gradual_tick(); //if (!(arg & 3)) gradual_tick(); //gradual_tick(); + #ifdef USE_IDLE_MODE + // go_to_idle = 1; // use less power when nothing is happening + #endif return MISCHIEF_MANAGED; } #endif @@ -939,6 +948,12 @@ void setup() { void loop() { + #ifdef USE_IDLE_MODE + if (current_state == steady_state) { + idle_mode(); + } else + #endif + if (current_state == strobe_state) { // party / tactical strobe if (strobe_type < 2) { diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index d526455..a47a4bf 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -72,8 +72,8 @@ int main() { PORTB = (1 << SWITCH_PIN); // e-switch is the only input PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin - // configure sleep mode - set_sleep_mode(SLEEP_MODE_PWR_DOWN); + //// configure sleep mode + //set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Read config values and saved state @@ -133,8 +133,20 @@ int main() { standby_mode(); } + #ifdef USE_IDLE_MODE + /* + // enter idle mode if requested + // (works better if deferred like this) + if (go_to_idle) { + go_to_idle = 0; + idle_mode(); + } + */ + #endif + // give the recipe some time slices loop(); + } } diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c index 44a2e0a..5312f94 100644 --- a/spaghetti-monster/fsm-standby.c +++ b/spaghetti-monster/fsm-standby.c @@ -40,6 +40,9 @@ void sleep_until_eswitch_pressed() PCINT_on(); // wake on e-switch event + // configure sleep mode + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + sleep_enable(); sleep_bod_disable(); sleep_cpu(); // wait here @@ -57,4 +60,18 @@ void sleep_until_eswitch_pressed() WDT_on(); } +#ifdef USE_IDLE_MODE +void idle_mode() +{ + // configure sleep mode + set_sleep_mode(SLEEP_MODE_IDLE); + + sleep_enable(); + sleep_cpu(); // wait here + + // something happened; wake up + sleep_disable(); +} +#endif + #endif diff --git a/spaghetti-monster/fsm-standby.h b/spaghetti-monster/fsm-standby.h index a23bd0c..3a917fd 100644 --- a/spaghetti-monster/fsm-standby.h +++ b/spaghetti-monster/fsm-standby.h @@ -28,6 +28,13 @@ volatile uint8_t go_to_standby = 0; #define standby_mode sleep_until_eswitch_pressed void sleep_until_eswitch_pressed(); +#ifdef USE_IDLE_MODE +// deferred "idle" state trigger +// stops processing until next click or timer tick +//volatile uint8_t go_to_idle = 0; +void idle_mode(); +#endif + // TODO: half-sleep "twilight" mode with WDT on but running slowly #endif -- cgit v1.2.3 From 08082855cbc7aa0a07f9ddfe6daa86e6c96d5b57 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 24 Sep 2017 20:56:52 -0600 Subject: Removed unused code from first idle_mode(); experiment. --- spaghetti-monster/anduril/anduril.c | 3 --- spaghetti-monster/fsm-main.c | 20 -------------------- spaghetti-monster/fsm-standby.h | 3 +-- 3 files changed, 1 insertion(+), 25 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index d15b5cb..b7c4421 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -329,9 +329,6 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (!(arg & 7)) gradual_tick(); //if (!(arg & 3)) gradual_tick(); //gradual_tick(); - #ifdef USE_IDLE_MODE - // go_to_idle = 1; // use less power when nothing is happening - #endif return MISCHIEF_MANAGED; } #endif diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index a47a4bf..bc41cd6 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -39,8 +39,6 @@ ISR(TIMER1_COMPA_vect) { int main() { // Don't allow interrupts while booting cli(); - //WDT_off(); - //PCINT_off(); // configure PWM channels #if PWM_CHANNELS >= 1 @@ -72,13 +70,6 @@ int main() { PORTB = (1 << SWITCH_PIN); // e-switch is the only input PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin - //// configure sleep mode - //set_sleep_mode(SLEEP_MODE_PWR_DOWN); - - // Read config values and saved state - - // TODO: handle long press vs short press (or even medium press)? - #ifdef USE_DEBUG_BLINK //debug_blink(1); #endif @@ -133,17 +124,6 @@ int main() { standby_mode(); } - #ifdef USE_IDLE_MODE - /* - // enter idle mode if requested - // (works better if deferred like this) - if (go_to_idle) { - go_to_idle = 0; - idle_mode(); - } - */ - #endif - // give the recipe some time slices loop(); diff --git a/spaghetti-monster/fsm-standby.h b/spaghetti-monster/fsm-standby.h index 3a917fd..0b410fa 100644 --- a/spaghetti-monster/fsm-standby.h +++ b/spaghetti-monster/fsm-standby.h @@ -29,9 +29,8 @@ volatile uint8_t go_to_standby = 0; void sleep_until_eswitch_pressed(); #ifdef USE_IDLE_MODE -// deferred "idle" state trigger // stops processing until next click or timer tick -//volatile uint8_t go_to_idle = 0; +// (I think) void idle_mode(); #endif -- cgit v1.2.3 From 36fc1e22964795922370008adda1b9a1732905a8 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 24 Sep 2017 21:08:51 -0600 Subject: Added idle to goodnight mode, for 2mA less power at all levels. Reduced ROM size slightly by avoiding repeated access to the volatile current_state var. --- spaghetti-monster/anduril/anduril.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index b7c4421..8784bc5 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -945,13 +945,18 @@ void setup() { void loop() { + StatePtr state = current_state; + + if (0) {} + #ifdef USE_IDLE_MODE - if (current_state == steady_state) { + else if ((state == steady_state) + || (state == goodnight_state)) { idle_mode(); - } else + } #endif - if (current_state == strobe_state) { + else if (state == strobe_state) { // party / tactical strobe if (strobe_type < 2) { set_level(MAX_LEVEL); @@ -1025,17 +1030,17 @@ void loop() { } #ifdef USE_BATTCHECK - else if (current_state == battcheck_state) { + else if (state == battcheck_state) { battcheck(); } - else if (current_state == tempcheck_state) { + else if (state == tempcheck_state) { blink_num(temperature>>2); nice_delay_ms(1000); } // TODO: blink out therm_ceil during thermal_config_state #endif - else if (current_state == beacon_state) { + else if (state == beacon_state) { set_level(memorized_level); if (! nice_delay_ms(500)) return; set_level(0); -- cgit v1.2.3 From 9bde4b7bd0588b1d74f9b89f89419e8f11e3f25f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 24 Sep 2017 22:45:59 -0600 Subject: Cut moon power from ~4mA to ~2.5mA by running clock at half speed on very low modes. Adjusted default FET+1 ramp to compensate for speed bump. Added define(s) for whether to blink at ramp floor/ceiling/channel edges. Lowered default moon level to 1, since slower speed makes it brighter and more stable. --- spaghetti-monster/anduril/anduril.c | 33 ++++++++++++++++++++++++++++++--- spaghetti-monster/fsm-main.c | 6 ++++++ spaghetti-monster/fsm-ramping.h | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 8784bc5..a1c9f5b 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -29,12 +29,16 @@ #define USE_RAMPING #define USE_SET_LEVEL_GRADUALLY #define RAMP_LENGTH 150 +#define BLINK_AT_RAMP_BOUNDARIES +//#define BLINK_AT_RAMP_FLOOR #define USE_BATTCHECK #define BATTCHECK_VpT #define MAX_CLICKS 5 #define USE_EEPROM #define EEPROM_BYTES 12 #define USE_IDLE_MODE +//#define HALFSPEED_LEVEL 30 // looks good, but sounds bad +#define HALFSPEED_LEVEL 14 #include "spaghetti-monster.h" // Options specific to this UI (not inherited from SpaghettiMonster) @@ -86,7 +90,7 @@ void save_config(); uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete -volatile uint8_t ramp_smooth_floor = 5; +volatile uint8_t ramp_smooth_floor = 1; #if PWM_CHANNELS == 3 volatile uint8_t ramp_smooth_ceil = MAX_Nx7135; volatile uint8_t ramp_discrete_ceil = MAX_Nx7135; @@ -226,6 +230,16 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { ramp_step_size = ramp_discrete_step_size; } + if (actual_level < HALFSPEED_LEVEL) { + // run at half speed + CLKPR = 1<= 3 || (memorized_level == MAX_Nx7135) #endif - || (memorized_level == mode_min))) { + #ifdef BLINK_AT_RAMP_FLOOR + || (memorized_level == mode_min) + #endif + )) { set_level(0); delay_4ms(8/4); } + #endif set_level(memorized_level); return MISCHIEF_MANAGED; } @@ -952,11 +973,17 @@ void loop() { #ifdef USE_IDLE_MODE else if ((state == steady_state) || (state == goodnight_state)) { + // doze until next clock tick idle_mode(); } + else { + // run at full speed + CLKPR = 1<= 1 DDRB |= (1 << PWM1_PIN); diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index 04beb31..0914186 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -66,7 +66,7 @@ void gradual_tick(); // ../../bin/level_calc.py 1 65 7135 1 0.8 150 // ... mixed with this: // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 - PROGMEM const uint8_t pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,11,12,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; #define MAX_1x7135 65 #endif -- cgit v1.2.3 From fd9081b1587793e279c9c071b88769d1d90a7067 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 24 Sep 2017 22:50:32 -0600 Subject: Moved halfspeed thing so it'll affect goodnight mode too. --- spaghetti-monster/anduril/anduril.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index a1c9f5b..1cc9d7c 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -230,16 +230,6 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { ramp_step_size = ramp_discrete_step_size; } - if (actual_level < HALFSPEED_LEVEL) { - // run at half speed - CLKPR = 1<> 1); } diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index 5448feb..e965224 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -116,8 +116,26 @@ inline void interrupt_nice_delays() { nice_delay_interrupt = 1; } // 1: normal completion uint8_t nice_delay_ms(uint16_t ms) { StatePtr old_state = current_state; + /* // delay_zero() implementation + if (ms == 0) { + CLKPR = 1< 0) { + #ifdef USE_DYNAMIC_UNDERCLOCKING + // underclock MCU to save power + CLKPR = 1< 0) { + // underclock MCU to save power + CLKPR = 1< 1) + if (bike_flasher_brightness > 2) bike_flasher_brightness --; set_level(bike_flasher_brightness); } @@ -958,7 +959,6 @@ void loop() { #ifdef USE_DYNAMIC_UNDERCLOCKING auto_clock_speed(); #endif - if (0) {} #ifdef USE_IDLE_MODE @@ -1034,6 +1034,7 @@ void loop() { // bike flasher else if (strobe_type == 3) { uint8_t burst = bike_flasher_brightness << 1; + if (burst > MAX_LEVEL) burst = MAX_LEVEL; for(uint8_t i=0; i<4; i++) { set_level(burst); if (! nice_delay_ms(5)) return; -- cgit v1.2.3 From 3507d67ee4499a56fb5a38723c00486b19b70c45 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 28 Sep 2017 16:08:27 -0600 Subject: Fleshed out options to blink at floor, ceiling, and channel boundaries. --- spaghetti-monster/anduril/anduril.c | 50 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 18 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 5a6b6e0..a47e284 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -28,8 +28,9 @@ #define USE_SET_LEVEL_GRADUALLY #define RAMP_LENGTH 150 #define MAX_BIKING_LEVEL 120 // should be 127 or less -#define BLINK_AT_RAMP_BOUNDARIES +#define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR +#define BLINK_AT_RAMP_CEILING #define USE_BATTCHECK #define BATTCHECK_VpT #define MAX_CLICKS 5 @@ -289,14 +290,22 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif - #ifdef BLINK_AT_RAMP_BOUNDARIES + #if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_CHANNEL_BOUNDARIES) // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - #if PWM_CHANNELS >= 3 - || (memorized_level == MAX_Nx7135) - #endif - || (memorized_level == mode_max))) { + if ((memorized_level != actual_level) && ( + #ifdef BLINK_AT_CHANNEL_BOUNDARIES + (memorized_level == MAX_1x7135) + #if PWM_CHANNELS >= 3 + || (memorized_level == MAX_Nx7135) + #endif + #ifdef BLINK_AT_RAMP_CEILING + || + #endif + #endif + #ifdef BLINK_AT_RAMP_CEILING + (memorized_level == mode_max) + #endif + )) { set_level(0); delay_4ms(8/4); } @@ -315,17 +324,22 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif - #ifdef BLINK_AT_RAMP_BOUNDARIES + #if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_CHANNEL_BOUNDARIES) // only blink once for each threshold - if ((memorized_level != actual_level) - && ((memorized_level == MAX_1x7135) - #if PWM_CHANNELS >= 3 - || (memorized_level == MAX_Nx7135) - #endif - #ifdef BLINK_AT_RAMP_FLOOR - || (memorized_level == mode_min) - #endif - )) { + if ((memorized_level != actual_level) && ( + #ifdef BLINK_AT_CHANNEL_BOUNDARIES + (memorized_level == MAX_1x7135) + #if PWM_CHANNELS >= 3 + || (memorized_level == MAX_Nx7135) + #endif + #ifdef BLINK_AT_RAMP_FLOOR + || + #endif + #endif + #ifdef BLINK_AT_RAMP_FLOOR + (memorized_level == mode_min) + #endif + )) { set_level(0); delay_4ms(8/4); } -- cgit v1.2.3 From 8d8e9df4331f48fba51eb5f169ac904fba42cc9d Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 28 Sep 2017 16:12:57 -0600 Subject: Fixed issue where double-click-from-off followed by double-click-to-true-turbo would overwrite memory with the ceiling level. --- spaghetti-monster/anduril/anduril.c | 1 - 1 file changed, 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index a47e284..ab7d16d 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -249,7 +249,6 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // 2 clicks: go to/from highest level else if (event == EV_2clicks) { if (actual_level < MAX_LEVEL) { - memorized_level = actual_level; // in case we're on moon #ifdef USE_THERMAL_REGULATION target_level = MAX_LEVEL; #endif -- cgit v1.2.3 From 3a7c25ceaccaeca36c2ba5db2deba975ec54aa68 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 28 Sep 2017 16:25:18 -0600 Subject: Rearranged some build options and made sure the build still works if some are turned off. --- spaghetti-monster/anduril/anduril.c | 41 +++++++++++++++++++++++++++---------- spaghetti-monster/fsm-standby.c | 2 ++ 2 files changed, 32 insertions(+), 11 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index ab7d16d..010f060 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -18,21 +18,29 @@ * along with this program. If not, see . */ +/********* User-configurable options *********/ +// Physical driver type #define FSM_EMISAR_D4_DRIVER //#define FSM_FW3A_DRIVER + #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 50 -#define USE_DELAY_ZERO -#define USE_RAMPING #define USE_SET_LEVEL_GRADUALLY -#define RAMP_LENGTH 150 -#define MAX_BIKING_LEVEL 120 // should be 127 or less #define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR #define BLINK_AT_RAMP_CEILING -#define USE_BATTCHECK #define BATTCHECK_VpT +#define USE_LIGHTNING_MODE +#define GOODNIGHT_TIME 60 // minutes (approximately) +#define GOODNIGHT_LEVEL 24 // ~11 lm + +/********* Configure SpaghettiMonster *********/ +#define USE_DELAY_ZERO +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define MAX_BIKING_LEVEL 120 // should be 127 or less +#define USE_BATTCHECK #define MAX_CLICKS 5 #define USE_EEPROM #define EEPROM_BYTES 12 @@ -40,11 +48,6 @@ #define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency #include "spaghetti-monster.h" -// Options specific to this UI (not inherited from SpaghettiMonster) -#define USE_LIGHTNING_MODE -// set this a bit high, since the bottom 2 ramp levels might not emit any light at all -#define GOODNIGHT_TIME 65 // minutes (approximately) -#define GOODNIGHT_LEVEL 24 // ~11 lm // FSM states uint8_t off_state(EventPtr event, uint16_t arg); @@ -60,6 +63,8 @@ uint8_t strobe_state(EventPtr event, uint16_t arg); #endif #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); +#endif +#ifdef USE_THERMAL_REGULATION uint8_t tempcheck_state(EventPtr event, uint16_t arg); uint8_t thermal_config_state(EventPtr event, uint16_t arg); #endif @@ -481,7 +486,9 @@ uint8_t battcheck_state(EventPtr event, uint16_t arg) { } return EVENT_NOT_HANDLED; } +#endif +#ifdef USE_THERMAL_REGULATION uint8_t tempcheck_state(EventPtr event, uint16_t arg) { // 1 click: off if (event == EV_1click) { @@ -511,7 +518,11 @@ uint8_t beacon_state(EventPtr event, uint16_t arg) { } // 2 clicks: tempcheck mode else if (event == EV_2clicks) { + #ifdef USE_THERMAL_REGULATION set_state(tempcheck_state, 0); + #else + set_state(battcheck_state, 0); + #endif return MISCHIEF_MANAGED; } // 3 clicks: beacon config mode @@ -704,6 +715,7 @@ uint8_t ramp_config_state(EventPtr event, uint16_t arg) { } +#ifdef USE_THERMAL_REGULATION uint8_t thermal_config_state(EventPtr event, uint16_t arg) { static uint8_t done = 0; if (event == EV_enter_state) { @@ -729,6 +741,7 @@ uint8_t thermal_config_state(EventPtr event, uint16_t arg) { } return EVENT_NOT_HANDLED; } +#endif uint8_t beacon_config_state(EventPtr event, uint16_t arg) { @@ -910,7 +923,9 @@ void load_config() { strobe_delays[1] = eeprom[8]; bike_flasher_brightness = eeprom[9]; beacon_seconds = eeprom[10]; + #ifdef USE_THERMAL_REGULATION therm_ceil = eeprom[11]; + #endif } } @@ -926,7 +941,9 @@ void save_config() { eeprom[8] = strobe_delays[1]; eeprom[9] = bike_flasher_brightness; eeprom[10] = beacon_seconds; + #ifdef USE_THERMAL_REGULATION eeprom[11] = therm_ceil; + #endif save_eeprom(); } @@ -1062,11 +1079,13 @@ void loop() { else if (state == battcheck_state) { battcheck(); } + #endif + #ifdef USE_THERMAL_REGULATION + // TODO: blink out therm_ceil during thermal_config_state else if (state == tempcheck_state) { blink_num(temperature>>2); nice_delay_ms(1000); } - // TODO: blink out therm_ceil during thermal_config_state #endif else if (state == beacon_state) { diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c index 5312f94..5ef666f 100644 --- a/spaghetti-monster/fsm-standby.c +++ b/spaghetti-monster/fsm-standby.c @@ -50,8 +50,10 @@ void sleep_until_eswitch_pressed() // something happened; wake up sleep_disable(); + #ifdef USE_THERMAL_REGULATION // forget what the temperature was last time we were on reset_thermal_history = 1; + #endif // go back to normal running mode //PCINT_on(); // should be on already -- cgit v1.2.3 From 2ec533abd4592958bef3ec818870e3fb41b64b29 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 28 Sep 2017 17:03:58 -0600 Subject: Added reversing to Anduril. Made EV_tick always send 0 while in "hold" state. Reversing is a build-time option. --- spaghetti-monster/anduril/anduril.c | 34 ++++++++++++++++++++++++++++++++-- spaghetti-monster/fsm-wdt.c | 11 ++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 010f060..6e7b0b9 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -34,6 +34,7 @@ #define USE_LIGHTNING_MODE #define GOODNIGHT_TIME 60 // minutes (approximately) #define GOODNIGHT_LEVEL 24 // ~11 lm +#define USE_REVERSING /********* Configure SpaghettiMonster *********/ #define USE_DELAY_ZERO @@ -228,6 +229,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t mode_min = ramp_smooth_floor; uint8_t mode_max = ramp_smooth_ceil; uint8_t ramp_step_size = 1; + #ifdef USE_REVERSING + static int8_t ramp_direction = 1; + #endif if (ramp_style) { mode_min = ramp_discrete_floor; mode_max = ramp_discrete_ceil; @@ -289,8 +293,16 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { return MISCHIEF_MANAGED; } - // TODO? make it ramp down instead, if already at max? + #ifdef USE_REVERSING + // make it ramp down instead, if already at max + if ((arg <= 1) && (actual_level >= mode_max)) { + ramp_direction = -1; + } + memorized_level = nearest_level((int16_t)actual_level \ + + (ramp_step_size * ramp_direction)); + #else memorized_level = nearest_level((int16_t)actual_level + ramp_step_size); + #endif #ifdef USE_THERMAL_REGULATION target_level = memorized_level; #endif @@ -309,6 +321,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef BLINK_AT_RAMP_CEILING (memorized_level == mode_max) #endif + #if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR) + || (memorized_level == mode_min) + #endif )) { set_level(0); delay_4ms(8/4); @@ -317,8 +332,17 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return MISCHIEF_MANAGED; } + #ifdef USE_REVERSING + // reverse ramp direction on hold release + else if (event == EV_click1_hold_release) { + ramp_direction = -ramp_direction; + } + #endif // click, hold: change brightness (dimmer) else if (event == EV_click2_hold) { + #ifdef USE_REVERSING + ramp_direction = 1; + #endif // ramp slower in discrete mode if (ramp_style && (arg % HOLD_TIMEOUT != 0)) { return MISCHIEF_MANAGED; @@ -351,11 +375,17 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return MISCHIEF_MANAGED; } - #ifdef USE_SET_LEVEL_GRADUALLY + #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING) else if (event == EV_tick) { + #ifdef USE_SET_LEVEL_GRADUALLY if (!(arg & 7)) gradual_tick(); //if (!(arg & 3)) gradual_tick(); //gradual_tick(); + #endif + #ifdef USE_REVERSING + // un-reverse after 1 second + if (arg == TICKS_PER_SECOND) ramp_direction = 1; + #endif return MISCHIEF_MANAGED; } #endif diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c index afcf467..7cbe0d2 100644 --- a/spaghetti-monster/fsm-wdt.c +++ b/spaghetti-monster/fsm-wdt.c @@ -50,9 +50,6 @@ ISR(WDT_vect) { ticks_since_last_event = (ticks_since_last_event + 1) \ | (ticks_since_last_event & 0x8000); - // 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 @@ -64,6 +61,14 @@ ISR(WDT_vect) { if (le_num >= 1) last_event = current_event[le_num-1]; if (le_num >= 2) prev_event = current_event[le_num-2]; + // callback on each timer tick + if (last_event == A_HOLD) { + emit(EV_tick, 0); // override tick counter while holding button + } + else { + emit(EV_tick, ticks_since_last_event); + } + // user held button long enough to count as a long click? if (last_event == A_PRESS) { if (ticks_since_last_event >= HOLD_TIMEOUT) { -- cgit v1.2.3 From 9678f9ea82d44de0ce52184de2ab7ee6fd1b7cb2 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 28 Sep 2017 20:10:09 -0600 Subject: Make sure hold-from-off ramps up, not down. --- spaghetti-monster/anduril/anduril.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 6e7b0b9..89712fe 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -248,6 +248,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { target_level = arg; #endif set_level(nearest_level(arg)); + #ifdef USE_REVERSING + ramp_direction = 1; + #endif return MISCHIEF_MANAGED; } // 1 click: off -- cgit v1.2.3 From 976c5724bf0a3ddbe6d4b385841a0a5bf38f4b25 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 29 Sep 2017 17:31:10 -0600 Subject: Added option to start at memorized level, for momentary use on dual-switch lights. Renamed WL versions of eeprom functions, for naming consistency. --- spaghetti-monster/anduril/anduril.c | 46 ++++++++++++++++++++++++++++++++++++- spaghetti-monster/fsm-eeprom.c | 4 ++-- spaghetti-monster/fsm-eeprom.h | 4 ++-- 3 files changed, 49 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 89712fe..84cc27c 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -35,6 +35,7 @@ #define GOODNIGHT_TIME 60 // minutes (approximately) #define GOODNIGHT_LEVEL 24 // ~11 lm #define USE_REVERSING +//#define START_AT_MEMORIZED_LEVEL /********* Configure SpaghettiMonster *********/ #define USE_DELAY_ZERO @@ -45,6 +46,10 @@ #define MAX_CLICKS 5 #define USE_EEPROM #define EEPROM_BYTES 12 +#ifdef START_AT_MEMORIZED_LEVEL +#define USE_EEPROM_WL +#define EEPROM_WL_BYTES 1 +#endif #define USE_IDLE_MODE #define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency #include "spaghetti-monster.h" @@ -90,6 +95,9 @@ void blink_confirm(uint8_t num); // remember stuff even after battery was changed void load_config(); void save_config(); +#ifdef START_AT_MEMORIZED_LEVEL +void save_config_wl(); +#endif // brightness control uint8_t memorized_level = MAX_1x7135; @@ -280,6 +288,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { ramp_style ^= 1; memorized_level = nearest_level(memorized_level); save_config(); + #ifdef START_AT_MEMORIZED_LEVEL + save_config_wl(); + #endif set_level(0); delay_4ms(20/4); set_level(memorized_level); @@ -335,10 +346,16 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return MISCHIEF_MANAGED; } - #ifdef USE_REVERSING + #if defined(USE_REVERSING) || defined(START_AT_MEMORIZED_LEVEL) // reverse ramp direction on hold release else if (event == EV_click1_hold_release) { + #ifdef USE_REVERSING ramp_direction = -ramp_direction; + #endif + #ifdef START_AT_MEMORIZED_LEVEL + save_config_wl(); + #endif + return MISCHIEF_MANAGED; } #endif // click, hold: change brightness (dimmer) @@ -378,6 +395,13 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { set_level(memorized_level); return MISCHIEF_MANAGED; } + #ifdef START_AT_MEMORIZED_LEVEL + // click, release, hold, release: save new ramp level (if necessary) + else if (event == EV_click2_hold_release) { + save_config_wl(); + return MISCHIEF_MANAGED; + } + #endif #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING) else if (event == EV_tick) { #ifdef USE_SET_LEVEL_GRADUALLY @@ -960,6 +984,11 @@ void load_config() { therm_ceil = eeprom[11]; #endif } + #ifdef START_AT_MEMORIZED_LEVEL + if (load_eeprom_wl()) { + memorized_level = eeprom_wl[0]; + } + #endif } void save_config() { @@ -981,6 +1010,12 @@ void save_config() { save_eeprom(); } +#ifdef START_AT_MEMORIZED_LEVEL +void save_config_wl() { + eeprom_wl[0] = memorized_level; + save_eeprom_wl(); +} +#endif void low_voltage() { // "step down" from strobe to something low @@ -1004,6 +1039,14 @@ void low_voltage() { void setup() { + #ifdef START_AT_MEMORIZED_LEVEL + // dual switch: e-switch + power clicky + // power clicky acts as a momentary mode + load_config(); + push_state(steady_state, memorized_level); + + #else + // blink at power-on to let user know power is connected set_level(RAMP_SIZE/8); delay_4ms(3); @@ -1012,6 +1055,7 @@ void setup() { load_config(); push_state(off_state, 0); + #endif } diff --git a/spaghetti-monster/fsm-eeprom.c b/spaghetti-monster/fsm-eeprom.c index e464785..45cd3fa 100644 --- a/spaghetti-monster/fsm-eeprom.c +++ b/spaghetti-monster/fsm-eeprom.c @@ -55,7 +55,7 @@ void save_eeprom() { uint8_t eeprom_wl[EEPROM_WL_BYTES]; EEP_OFFSET_T eep_wl_prev_offset; -uint8_t load_wl_eeprom() { +uint8_t load_eeprom_wl() { cli(); // check if eeprom has been initialized; abort if it hasn't uint8_t found = 0; @@ -80,7 +80,7 @@ uint8_t load_wl_eeprom() { return found; } -void save_wl_eeprom() { +void save_eeprom_wl() { cli(); // erase old state EEP_OFFSET_T offset = eep_wl_prev_offset; diff --git a/spaghetti-monster/fsm-eeprom.h b/spaghetti-monster/fsm-eeprom.h index c35c822..b6e1aea 100644 --- a/spaghetti-monster/fsm-eeprom.h +++ b/spaghetti-monster/fsm-eeprom.h @@ -47,8 +47,8 @@ void save_eeprom(); #error Requested EEPROM_WL_BYTES too big. #endif uint8_t eeprom_wl[EEPROM_WL_BYTES]; -uint8_t load_wl_eeprom(); // returns 1 for success, 0 for no data found -void save_wl_eeprom(); +uint8_t load_eeprom_wl(); // returns 1 for success, 0 for no data found +void save_eeprom_wl(); #define EEP_WL_SIZE (EEPSIZE/2) #endif -- cgit v1.2.3 From 6affa025b829b2d7b212fdf42f6d85aac7a00367 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 29 Sep 2017 17:37:16 -0600 Subject: Made dual-switch startup optionally go to moon if e-switch is held. --- spaghetti-monster/anduril/anduril.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 84cc27c..1fdf457 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -1043,7 +1043,12 @@ void setup() { // dual switch: e-switch + power clicky // power clicky acts as a momentary mode load_config(); - push_state(steady_state, memorized_level); + if (button_is_pressed()) + // hold button to go to moon + push_state(steady_state, 1); + else + // otherwise use memory + push_state(steady_state, memorized_level); #else -- cgit v1.2.3 From 4a640e385f63e747adfafafa450335d9eb0526ab Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 1 Oct 2017 18:19:34 -0600 Subject: Use either full or quarter speed in nice delays, based on output level. This fixes issues with some blinky modes being audible (particularly biking and lightning modes), without sacrificing the power savings for other blinkies (like beacon and battcheck). --- spaghetti-monster/fsm-events.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c index e965224..b8e9018 100644 --- a/spaghetti-monster/fsm-events.c +++ b/spaghetti-monster/fsm-events.c @@ -125,16 +125,34 @@ uint8_t nice_delay_ms(uint16_t ms) { */ while(ms-- > 0) { #ifdef USE_DYNAMIC_UNDERCLOCKING + #ifdef USE_RAMPING + uint8_t level = actual_level; // volatile, avoid repeat access + if (level < QUARTERSPEED_LEVEL) { + CLKPR = 1<>2); + blink_num(temperature>>1); nice_delay_ms(1000); } #endif -- cgit v1.2.3 From c3ade0cf9b7f344be2fcf14c1ba05cf49eded954 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 6 Oct 2017 00:34:34 -0600 Subject: Made HOLD_TIMEOUT and RELEASE_TIMEOUT define-able in the UI code. --- spaghetti-monster/fsm-events.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 074b459..453ad0b 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -46,8 +46,12 @@ uint8_t current_event[EV_MAX_LEN]; static volatile uint16_t ticks_since_last_event = 0; // timeout durations in ticks (each tick 1/60th s) +#ifndef HOLD_TIMEOUT #define HOLD_TIMEOUT 24 +#endif +#ifndef RELEASE_TIMEOUT #define RELEASE_TIMEOUT 24 +#endif #define A_ENTER_STATE 1 #define A_LEAVE_STATE 2 -- cgit v1.2.3 From 56316ceb551bc807fc1c20a2344a4b09e99e5c4c Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 6 Oct 2017 00:35:08 -0600 Subject: Updated more temperature bits to be 14.1 instead of 13.2. --- spaghetti-monster/fsm-adc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 5be3061..839fba8 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -151,10 +151,10 @@ ISR(ADC_vect) { for(uint8_t i=0; i> 2); + } else + #endif if (event == EV_tick) { ticks_spent_awake ++; if (ticks_spent_awake > 180) { ticks_spent_awake = 0; go_to_standby = 1; + #ifdef USE_INDICATOR_LED + indicator_led(indicator_led_mode >> 2); + #endif } return MISCHIEF_MANAGED; } + #ifdef USE_INDICATOR_LED + // 3 clicks: rotate through indicator LED modes (lockout mode) + else if (event == EV_3clicks) { + uint8_t mode = indicator_led_mode >> 2; + mode = (mode + 1) % 3; + indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03); + indicator_led(mode); + save_config(); + return MISCHIEF_MANAGED; + } + // 3 clicks: rotate through indicator LED modes (off mode) + else if (event == EV_click3_hold) { + uint8_t mode = (arg >> 5) % 3; + indicator_led_mode = (indicator_led_mode & 0b11111100) | mode; + indicator_led(mode); + save_config(); + return MISCHIEF_MANAGED; + } + #endif // 4 clicks: exit else if (event == EV_4clicks) { blink_confirm(1); @@ -967,6 +1016,26 @@ uint8_t pseudo_rand() { #endif +#ifdef USE_INDICATOR_LED +void indicator_led(uint8_t lvl) { + switch (lvl) { + case 0: // indicator off + DDRB &= 0xff ^ (1 << AUXLED_PIN); + PORTB &= 0xff ^ (1 << AUXLED_PIN); + break; + case 1: // indicator low + DDRB &= 0xff ^ (1 << AUXLED_PIN); + PORTB |= (1 << AUXLED_PIN); + break; + default: // indicator high + DDRB |= (1 << AUXLED_PIN); + PORTB |= (1 << AUXLED_PIN); + break; + } +} +#endif // USE_INDICATOR_LED + + void load_config() { if (load_eeprom()) { ramp_style = eeprom[0]; @@ -983,6 +1052,9 @@ void load_config() { #ifdef USE_THERMAL_REGULATION therm_ceil = eeprom[11]; #endif + #ifdef USE_INDICATOR_LED + indicator_led_mode = eeprom[12]; + #endif } #ifdef START_AT_MEMORIZED_LEVEL if (load_eeprom_wl()) { @@ -1006,6 +1078,9 @@ void save_config() { #ifdef USE_THERMAL_REGULATION eeprom[11] = therm_ceil; #endif + #ifdef USE_INDICATOR_LED + eeprom[12] = indicator_led_mode; + #endif save_eeprom(); } @@ -1061,6 +1136,7 @@ void setup() { push_state(off_state, 0); #endif + } @@ -1087,6 +1163,7 @@ void loop() { // party / tactical strobe if (strobe_type < 2) { set_level(MAX_LEVEL); + CLKPR = 1<> 5) % 3; indicator_led_mode = (indicator_led_mode & 0b11111100) | mode; indicator_led(mode); + //save_config(); + return MISCHIEF_MANAGED; + } + // click, click, hold, release: save indicator LED mode (off mode) + else if (event == EV_click3_hold_release) { save_config(); return MISCHIEF_MANAGED; } diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 453ad0b..28f1b10 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -178,7 +178,6 @@ Event EV_click3_hold[] = { A_PRESS, A_HOLD, 0 }; -/* Event EV_click3_hold_release[] = { A_PRESS, A_RELEASE, @@ -188,7 +187,6 @@ Event EV_click3_hold_release[] = { A_HOLD, A_RELEASE, 0 }; - */ Event EV_click3_release[] = { A_PRESS, A_RELEASE, @@ -426,7 +424,7 @@ EventPtr event_sequences[] = { #if MAX_CLICKS >= 3 EV_click3_press, EV_click3_hold, - //EV_click3_hold_release, + EV_click3_hold_release, EV_click3_release, EV_click3_complete, #endif -- cgit v1.2.3 From 2dcb0165cd01a230d678f290f80380dfa63e6dcd Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 20 Oct 2017 08:20:10 -0600 Subject: Made indicator LED turn on/off in lightning mode along with main LED. --- spaghetti-monster/anduril/anduril.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 4daec1d..330f107 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -1038,6 +1038,12 @@ void indicator_led(uint8_t lvl) { break; } } + +void indicator_led_auto() { + if (actual_level > MAX_1x7135) indicator_led(2); + else if (actual_level > 0) indicator_led(1); + else indicator_led(0); +} #endif // USE_INDICATOR_LED @@ -1193,6 +1199,9 @@ void loop() { brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) if (brightness > MAX_LEVEL) brightness = MAX_LEVEL; set_level(brightness); + #ifdef USE_INDICATOR_LED + indicator_led_auto(); + #endif if (! nice_delay_ms(rand_time)) return; // decrease the brightness somewhat more gradually, like lightning @@ -1203,6 +1212,9 @@ void loop() { brightness -= stepdown; if (brightness < 0) brightness = 0; set_level(brightness); + #ifdef USE_INDICATOR_LED + indicator_led_auto(); + #endif /* if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) { brightness <<= 1; @@ -1221,6 +1233,9 @@ void loop() { rand_time = 1<<(pseudo_rand()%13); rand_time += pseudo_rand()%rand_time; set_level(0); + #ifdef USE_INDICATOR_LED + indicator_led_auto(); + #endif nice_delay_ms(rand_time); } -- cgit v1.2.3 From 8d967e13f18cee2bc13faa502cfac0946ae2a0d9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 21 Oct 2017 06:46:07 -0600 Subject: Delay un-setting go_to_standby until the last moment, in case set_level() wants to use it. --- spaghetti-monster/fsm-main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c index 5efcd4c..8f21846 100644 --- a/spaghetti-monster/fsm-main.c +++ b/spaghetti-monster/fsm-main.c @@ -110,7 +110,6 @@ int main() { // enter standby mode if requested // (works better if deferred like this) if (go_to_standby) { - go_to_standby = 0; #ifdef USE_RAMPING set_level(0); #else @@ -127,6 +126,7 @@ int main() { PWM4_LVL = 255; // inverted :( #endif #endif + go_to_standby = 0; standby_mode(); } -- cgit v1.2.3 From a154ddbda5477a9b5a66333b8777fd6d1ee99d39 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 21 Oct 2017 06:51:27 -0600 Subject: Moved indicator_led() from Anduril into FSM. Made it auto-set indicator level based on ramp level. Made Anduril configure voltage fudge and indicator LED functions based on driver type. Removed "ticks_spent_awake" thing since it's redundant. EV_tick arg does the same thing in fewer bytes. Rearranged some clauses in off_state to put events in a more coherent order. --- spaghetti-monster/anduril/anduril.c | 114 ++++++++++++------------------------ spaghetti-monster/fsm-misc.c | 28 +++++++++ spaghetti-monster/fsm-misc.h | 5 ++ spaghetti-monster/fsm-ramping.c | 7 +++ 4 files changed, 79 insertions(+), 75 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 330f107..3559235 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -20,14 +20,15 @@ /********* User-configurable options *********/ // Physical driver type -#define FSM_EMISAR_D4_DRIVER +//#define FSM_EMISAR_D4_DRIVER +#define FSM_BLF_Q8_DRIVER //#define FSM_FW3A_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 50 #define USE_SET_LEVEL_GRADUALLY -#define BLINK_AT_CHANNEL_BOUNDARIES +//#define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR #define BLINK_AT_RAMP_CEILING #define BATTCHECK_VpT @@ -36,7 +37,6 @@ #define GOODNIGHT_LEVEL 24 // ~11 lm #define USE_REVERSING //#define START_AT_MEMORIZED_LEVEL -#define USE_INDICATOR_LED /********* Configure SpaghettiMonster *********/ #define USE_DELAY_ZERO @@ -45,6 +45,18 @@ #define MAX_BIKING_LEVEL 120 // should be 127 or less #define USE_BATTCHECK #define MAX_CLICKS 5 +#define USE_IDLE_MODE +#define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency + +// specific settings for known driver types +#ifdef FSM_BLF_Q8_DRIVER +#define USE_INDICATOR_LED +#define VOLTAGE_FUDGE_FACTOR 7 // add 0.35V +#elif defined(FSM_EMISAR_D4_DRIVER) +#define VOLTAGE_FUDGE_FACTOR 5 // add 0.25V +#endif + +// try to auto-detect how many eeprom bytes #define USE_EEPROM #ifdef USE_INDICATOR_LED #define EEPROM_BYTES 13 @@ -57,8 +69,6 @@ #define USE_EEPROM_WL #define EEPROM_WL_BYTES 1 #endif -#define USE_IDLE_MODE -#define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency #include "spaghetti-monster.h" @@ -99,10 +109,6 @@ volatile uint8_t number_entry_value; void blink_confirm(uint8_t num); -#ifdef USE_INDICATOR_LED -void indicator_led(uint8_t lvl); -#endif - // remember stuff even after battery was changed void load_config(); void save_config(); @@ -161,7 +167,6 @@ volatile uint8_t beacon_seconds = 2; uint8_t off_state(EventPtr event, uint16_t arg) { - static uint8_t ticks_spent_awake = 0; // turn emitter off when entering state if (event == EV_enter_state) { set_level(0); @@ -170,16 +175,15 @@ uint8_t off_state(EventPtr event, uint16_t arg) { #endif // sleep while off (lower power use) go_to_standby = 1; - ticks_spent_awake = 0; return MISCHIEF_MANAGED; } // go back to sleep eventually if we got bumped but didn't leave "off" state - // FIXME: can I just use arg instead of ticks_spent_awake? else if (event == EV_tick) { - ticks_spent_awake ++; - if (ticks_spent_awake > 240) { - ticks_spent_awake = 0; + if (arg > TICKS_PER_SECOND*2) { go_to_standby = 1; + #ifdef USE_INDICATOR_LED + indicator_led(indicator_led_mode & 0x03); + #endif } return MISCHIEF_MANAGED; } @@ -188,6 +192,20 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_level(nearest_level(1)); return MISCHIEF_MANAGED; } + // hold: go to lowest level + else if (event == EV_click1_hold) { + // don't start ramping immediately; + // give the user time to release at moon level + if (arg >= HOLD_TIMEOUT) { + set_state(steady_state, 1); + } + return MISCHIEF_MANAGED; + } + // hold, release quickly: go to lowest level + else if (event == EV_click1_hold_release) { + set_state(steady_state, 1); + return MISCHIEF_MANAGED; + } // 1 click (before timeout): go to memorized level, but allow abort for double click else if (event == EV_click1_release) { set_level(nearest_level(memorized_level)); @@ -203,6 +221,11 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_level(0); return MISCHIEF_MANAGED; } + // click, hold: go to highest level (for ramping down) + else if (event == EV_click2_hold) { + set_state(steady_state, MAX_LEVEL); + return MISCHIEF_MANAGED; + } // 2 clicks: highest mode else if (event == EV_2clicks) { set_state(steady_state, nearest_level(MAX_LEVEL)); @@ -232,25 +255,6 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(momentary_state, 0); return MISCHIEF_MANAGED; } - // hold: go to lowest level - else if (event == EV_click1_hold) { - // don't start ramping immediately; - // give the user time to release at moon level - if (arg >= HOLD_TIMEOUT) { - set_state(steady_state, 1); - } - return MISCHIEF_MANAGED; - } - // hold, release quickly: go to lowest level - else if (event == EV_click1_hold_release) { - set_state(steady_state, 1); - return MISCHIEF_MANAGED; - } - // click, hold: go to highest level (for ramping down) - else if (event == EV_click2_hold) { - set_state(steady_state, MAX_LEVEL); - return MISCHIEF_MANAGED; - } return EVENT_NOT_HANDLED; } @@ -654,8 +658,6 @@ uint8_t goodnight_state(EventPtr event, uint16_t arg) { uint8_t lockout_state(EventPtr event, uint16_t arg) { - static uint8_t ticks_spent_awake = 0; - #ifdef MOON_DURING_LOCKOUT_MODE // momentary(ish) moon mode during lockout // not all presses will be counted; @@ -668,7 +670,6 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { uint8_t lvl = ramp_smooth_floor; if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor; set_level(lvl); - ticks_spent_awake = 0; } else if ((last == A_RELEASE) || (last == A_RELEASE_TIMEOUT)) { set_level(0); @@ -686,9 +687,7 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { } else #endif if (event == EV_tick) { - ticks_spent_awake ++; - if (ticks_spent_awake > 180) { - ticks_spent_awake = 0; + if (arg > TICKS_PER_SECOND*2) { go_to_standby = 1; #ifdef USE_INDICATOR_LED indicator_led(indicator_led_mode >> 2); @@ -1021,32 +1020,6 @@ uint8_t pseudo_rand() { #endif -#ifdef USE_INDICATOR_LED -void indicator_led(uint8_t lvl) { - switch (lvl) { - case 0: // indicator off - DDRB &= 0xff ^ (1 << AUXLED_PIN); - PORTB &= 0xff ^ (1 << AUXLED_PIN); - break; - case 1: // indicator low - DDRB &= 0xff ^ (1 << AUXLED_PIN); - PORTB |= (1 << AUXLED_PIN); - break; - default: // indicator high - DDRB |= (1 << AUXLED_PIN); - PORTB |= (1 << AUXLED_PIN); - break; - } -} - -void indicator_led_auto() { - if (actual_level > MAX_1x7135) indicator_led(2); - else if (actual_level > 0) indicator_led(1); - else indicator_led(0); -} -#endif // USE_INDICATOR_LED - - void load_config() { if (load_eeprom()) { ramp_style = eeprom[0]; @@ -1199,9 +1172,6 @@ void loop() { brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias) if (brightness > MAX_LEVEL) brightness = MAX_LEVEL; set_level(brightness); - #ifdef USE_INDICATOR_LED - indicator_led_auto(); - #endif if (! nice_delay_ms(rand_time)) return; // decrease the brightness somewhat more gradually, like lightning @@ -1212,9 +1182,6 @@ void loop() { brightness -= stepdown; if (brightness < 0) brightness = 0; set_level(brightness); - #ifdef USE_INDICATOR_LED - indicator_led_auto(); - #endif /* if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) { brightness <<= 1; @@ -1233,9 +1200,6 @@ void loop() { rand_time = 1<<(pseudo_rand()%13); rand_time += pseudo_rand()%rand_time; set_level(0); - #ifdef USE_INDICATOR_LED - indicator_led_auto(); - #endif nice_delay_ms(rand_time); } diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c index 1b8f864..acef28c 100644 --- a/spaghetti-monster/fsm-misc.c +++ b/spaghetti-monster/fsm-misc.c @@ -109,4 +109,32 @@ uint8_t blink_num(uint8_t num) { } #endif +#ifdef USE_INDICATOR_LED +void indicator_led(uint8_t lvl) { + switch (lvl) { + case 0: // indicator off + DDRB &= 0xff ^ (1 << AUXLED_PIN); + PORTB &= 0xff ^ (1 << AUXLED_PIN); + break; + case 1: // indicator low + DDRB &= 0xff ^ (1 << AUXLED_PIN); + PORTB |= (1 << AUXLED_PIN); + break; + default: // indicator high + DDRB |= (1 << AUXLED_PIN); + PORTB |= (1 << AUXLED_PIN); + break; + } +} + +/* +void indicator_led_auto() { + if (actual_level > MAX_1x7135) indicator_led(2); + else if (actual_level > 0) indicator_led(1); + else indicator_led(0); +} +*/ +#endif // USE_INDICATOR_LED + + #endif diff --git a/spaghetti-monster/fsm-misc.h b/spaghetti-monster/fsm-misc.h index 5e4f3d6..4e0eb4f 100644 --- a/spaghetti-monster/fsm-misc.h +++ b/spaghetti-monster/fsm-misc.h @@ -42,4 +42,9 @@ uint8_t blink(uint8_t num, uint8_t speed); #endif */ +#ifdef USE_INDICATOR_LED +void indicator_led(uint8_t lvl); +#endif + + #endif diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c index 11e56a5..ec132ae 100644 --- a/spaghetti-monster/fsm-ramping.c +++ b/spaghetti-monster/fsm-ramping.c @@ -28,6 +28,13 @@ void set_level(uint8_t level) { #ifdef USE_SET_LEVEL_GRADUALLY gradual_target = level; #endif + #ifdef USE_INDICATOR_LED + if (! go_to_standby) + indicator_led((level > 0) + (level > MAX_1x7135)); + //if (level > MAX_1x7135) indicator_led(2); + //else if (level > 0) indicator_led(1); + //else if (! go_to_standby) indicator_led(0); + #endif //TCCR0A = PHASE; if (level == 0) { #if PWM_CHANNELS >= 1 -- cgit v1.2.3 From 9ae4f570a4b56fb3eb60f4c244931c771415944b Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 6 Nov 2017 08:51:57 -0700 Subject: Made FSM eeprom more atomic by writing marker byte last. --- spaghetti-monster/fsm-eeprom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-eeprom.c b/spaghetti-monster/fsm-eeprom.c index 45cd3fa..012e088 100644 --- a/spaghetti-monster/fsm-eeprom.c +++ b/spaghetti-monster/fsm-eeprom.c @@ -41,12 +41,14 @@ uint8_t load_eeprom() { void save_eeprom() { cli(); - eeprom_update_byte((uint8_t *)EEP_START, EEP_MARKER); // save the actual data for(uint8_t i=0; i EEP_WL_SIZE-EEPROM_WL_BYTES-1) offset = 0; eep_wl_prev_offset = offset; // marker byte + // FIXME: write the marker last, to signal completed transaction eeprom_update_byte((uint8_t *)offset, EEP_MARKER); offset ++; // user data -- cgit v1.2.3 From c71a30fb6ab512177aa2cfe4e4a17d83d0a5e144 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 6 Nov 2017 08:52:43 -0700 Subject: Added an incredibly simple muggle mode... 1-mode, on/off only, disconnect power to exit. --- spaghetti-monster/anduril/anduril.c | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 3559235..4c28fcb 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -37,6 +37,7 @@ #define GOODNIGHT_LEVEL 24 // ~11 lm #define USE_REVERSING //#define START_AT_MEMORIZED_LEVEL +#define USE_MUGGLE_MODE /********* Configure SpaghettiMonster *********/ #define USE_DELAY_ZERO @@ -44,7 +45,11 @@ #define RAMP_LENGTH 150 #define MAX_BIKING_LEVEL 120 // should be 127 or less #define USE_BATTCHECK +#ifdef USE_MUGGLE_MODE +#define MAX_CLICKS 6 +#else #define MAX_CLICKS 5 +#endif #define USE_IDLE_MODE #define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency @@ -101,6 +106,10 @@ uint8_t beacon_config_state(EventPtr event, uint16_t arg); uint8_t lockout_state(EventPtr event, uint16_t arg); // momentary / signalling mode uint8_t momentary_state(EventPtr event, uint16_t arg); +#ifdef USE_MUGGLE_MODE +// muggle mode, super-simple, hard to exit +uint8_t muggle_state(EventPtr event, uint16_t arg); +#endif // general helper function for config modes uint8_t number_entry_state(EventPtr event, uint16_t arg); @@ -255,6 +264,14 @@ uint8_t off_state(EventPtr event, uint16_t arg) { set_state(momentary_state, 0); return MISCHIEF_MANAGED; } + #ifdef USE_MUGGLE_MODE + // 6 clicks: muggle mode + else if (event == EV_6clicks) { + blink_confirm(1); + set_state(muggle_state, 0); + return MISCHIEF_MANAGED; + } + #endif return EVENT_NOT_HANDLED; } @@ -760,6 +777,48 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { } +#ifdef USE_MUGGLE_MODE +uint8_t muggle_state(EventPtr event, uint16_t arg) { + //static uint8_t lvl = 0; + //uint8_t lvls[] = {MAX_1x7135/2, MAX_1x7135, MAX_1x7135+10}; + + if (event == EV_click1_press) { + /* + if (actual_level == 0) { + set_level(lvls[lvl]); + lvl = (lvl + 1) % sizeof(lvls); + } + else { // turn off + } + */ + if (actual_level == 0) { + set_level(MAX_1x7135); + } + else { // turn off + set_level(0); + } + empty_event_sequence(); + return MISCHIEF_MANAGED; + } + + else if (event == EV_release) { + empty_event_sequence(); // don't attempt to parse multiple clicks + return MISCHIEF_MANAGED; + } + + // Sleep, dammit! + else if ((event == EV_tick) && (actual_level == 0)) { + if (arg > TICKS_PER_SECOND*1) { // sleep after 1 second + go_to_standby = 1; // sleep while light is off + } + return MISCHIEF_MANAGED; + } + + return EVENT_NOT_HANDLED; +} +#endif + + uint8_t ramp_config_state(EventPtr event, uint16_t arg) { static uint8_t config_step; static uint8_t num_config_steps; -- cgit v1.2.3 From d0a5b654b7c030bca21c16f69d09b36d7d994ef5 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 8 Nov 2017 12:18:47 -0700 Subject: Added a candle flicker mode to Anduril. Easy to enable/disable at compile time. --- spaghetti-monster/anduril/anduril.c | 93 +++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 8 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 4c28fcb..85fe493 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -33,6 +33,7 @@ #define BLINK_AT_RAMP_CEILING #define BATTCHECK_VpT #define USE_LIGHTNING_MODE +#define USE_CANDLE_MODE #define GOODNIGHT_TIME 60 // minutes (approximately) #define GOODNIGHT_LEVEL 24 // ~11 lm #define USE_REVERSING @@ -74,6 +75,25 @@ #define USE_EEPROM_WL #define EEPROM_WL_BYTES 1 #endif + +// auto-configure other stuff... +#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE) +#define USE_PSEUDO_RAND +#endif +// count the strobe modes (seems like there should be an easier way to do this) +#define NUM_STROBES_BASE 3 +#ifdef USE_LIGHTNING_MODE +#define ADD_LIGHTNING_STROBE 1 +#else +#define ADD_LIGHTNING_STROBE 0 +#endif +#ifdef USE_CANDLE_MODE +#define ADD_CANDLE_MODE 1 +#else +#define ADD_CANDLE_MODE 0 +#endif +#define NUM_STROBES (NUM_STROBES_BASE+ADD_LIGHTNING_STROBE+ADD_CANDLE_MODE) + #include "spaghetti-monster.h" @@ -84,11 +104,6 @@ uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t ramp_config_state(EventPtr event, uint16_t arg); // party and tactical strobes uint8_t strobe_state(EventPtr event, uint16_t arg); -#ifdef USE_LIGHTNING_MODE -#define NUM_STROBES 4 -#else -#define NUM_STROBES 3 -#endif #ifdef USE_BATTCHECK uint8_t battcheck_state(EventPtr event, uint16_t arg); #endif @@ -166,11 +181,15 @@ volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 // bike mode config options volatile uint8_t bike_flasher_brightness = MAX_1x7135; -#ifdef USE_LIGHTNING_MODE +#ifdef USE_PSEUDO_RAND volatile uint8_t pseudo_rand_seed = 0; uint8_t pseudo_rand(); #endif +#ifdef USE_CANDLE_MODE +uint8_t triangle_wave(uint8_t phase); +#endif + // beacon timing volatile uint8_t beacon_seconds = 2; @@ -508,6 +527,16 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t strobe_state(EventPtr event, uint16_t arg) { + #ifdef USE_CANDLE_MODE + //#define MAX_CANDLE_LEVEL (RAMP_SIZE-8-6-4) + #define MAX_CANDLE_LEVEL (RAMP_SIZE/2) + static uint8_t candle_wave1 = 0; + static uint8_t candle_wave2 = 0; + static uint8_t candle_wave3 = 0; + static uint8_t candle_wave2_speed = 0; + static uint8_t candle_mode_brightness = 12; + #endif + if (event == EV_enter_state) { return MISCHIEF_MANAGED; } @@ -537,6 +566,13 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { bike_flasher_brightness ++; set_level(bike_flasher_brightness); } + #ifdef USE_CANDLE_MODE + // candle mode brighter + else if (strobe_type == 4) { + if (candle_mode_brightness < MAX_CANDLE_LEVEL) + candle_mode_brightness ++; + } + #endif return MISCHIEF_MANAGED; } // click, hold: change speed (go slower) @@ -553,6 +589,13 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { bike_flasher_brightness --; set_level(bike_flasher_brightness); } + #ifdef USE_CANDLE_MODE + // candle mode dimmer + else if (strobe_type == 4) { + if (candle_mode_brightness > 1) + candle_mode_brightness --; + } + #endif return MISCHIEF_MANAGED; } // release hold: save new strobe settings @@ -561,10 +604,32 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { save_config(); return MISCHIEF_MANAGED; } - #ifdef USE_LIGHTNING_MODE + #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE) // clock tick: bump the random seed else if (event == EV_tick) { + #ifdef USE_LIGHTNING_MODE pseudo_rand_seed += arg; + #endif + #ifdef USE_CANDLE_MODE + if (strobe_type == 4) { + // 3-oscillator synth for a relatively organic pattern + uint8_t add; + add = ((triangle_wave(candle_wave1) * 8) >> 8) + + ((triangle_wave(candle_wave2) * 7) >> 8) + + ((triangle_wave(candle_wave3) * 4) >> 8); + set_level(candle_mode_brightness + add); + // slow LFO + if ((arg & 1) == 0) candle_wave1 += pseudo_rand()&1; + // faster LFO + //candle_wave2 += pseudo_rand()%13; + candle_wave2 += candle_wave2_speed; + // erratic fast wave + candle_wave3 += pseudo_rand()%37; + // S&H on wave2 frequency to make it more erratic + if ((pseudo_rand()>>3) == 0) + candle_wave2_speed = pseudo_rand()%13; + } + #endif return MISCHIEF_MANAGED; } #endif @@ -1068,7 +1133,7 @@ void blink_confirm(uint8_t num) { } -#ifdef USE_LIGHTNING_MODE +#ifdef USE_PSEUDO_RAND uint8_t pseudo_rand() { static uint16_t offset = 1024; // loop from 1024 to 4095 @@ -1079,6 +1144,15 @@ uint8_t pseudo_rand() { #endif +#ifdef USE_CANDLE_MODE +uint8_t triangle_wave(uint8_t phase) { + uint8_t result = phase << 1; + if (phase > 127) result = 255 - result; + return result; +} +#endif + + void load_config() { if (load_eeprom()) { ramp_style = eeprom[0]; @@ -1205,6 +1279,9 @@ void loop() { if (state == strobe_state) { // party / tactical strobe if (strobe_type < 2) { + // FIXME: for tactical strobe, use max Nx7135 level? + // (perhaps a compile option) + // (is relevant for FW3A) set_level(MAX_LEVEL); CLKPR = 1< 0 +#ifdef EEPROM_OVERRIDE +uint8_t *eeprom; +#else uint8_t eeprom[EEPROM_BYTES]; +#endif uint8_t load_eeprom() { cli(); diff --git a/spaghetti-monster/fsm-eeprom.h b/spaghetti-monster/fsm-eeprom.h index b6e1aea..a668588 100644 --- a/spaghetti-monster/fsm-eeprom.h +++ b/spaghetti-monster/fsm-eeprom.h @@ -36,7 +36,11 @@ #if EEPROM_BYTES >= (EEPSIZE/2) #error Requested EEPROM_BYTES too big. #endif +#ifdef EEPROM_OVERRIDE +uint8_t *eeprom; +#else uint8_t eeprom[EEPROM_BYTES]; +#endif uint8_t load_eeprom(); // returns 1 for success, 0 for no data found void save_eeprom(); #define EEP_START (EEPSIZE/2) -- cgit v1.2.3 From e52df9c3ac606b24bda70287bc62323b46b7b4c4 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 11 Dec 2017 15:03:17 -0700 Subject: Working toward a FW3A release now. Made strobes use Nx7135 (when available) instead of FET. Added some ideas. Simplified some syntax. --- spaghetti-monster/anduril/anduril.c | 52 +++++++++++++++++++++++-------------- spaghetti-monster/fsm-ramping.h | 6 ++++- 2 files changed, 38 insertions(+), 20 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 85fe493..4915b39 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -21,14 +21,14 @@ /********* User-configurable options *********/ // Physical driver type //#define FSM_EMISAR_D4_DRIVER -#define FSM_BLF_Q8_DRIVER -//#define FSM_FW3A_DRIVER +//#define FSM_BLF_Q8_DRIVER +#define FSM_FW3A_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 50 #define USE_SET_LEVEL_GRADUALLY -//#define BLINK_AT_CHANNEL_BOUNDARIES +#define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR #define BLINK_AT_RAMP_CEILING #define BATTCHECK_VpT @@ -60,6 +60,8 @@ #define VOLTAGE_FUDGE_FACTOR 7 // add 0.35V #elif defined(FSM_EMISAR_D4_DRIVER) #define VOLTAGE_FUDGE_FACTOR 5 // add 0.25V +#elif defined(FSM_FW3A_DRIVER) +#define VOLTAGE_FUDGE_FACTOR 5 // add 0.25V #endif // try to auto-detect how many eeprom bytes @@ -94,6 +96,14 @@ #endif #define NUM_STROBES (NUM_STROBES_BASE+ADD_LIGHTNING_STROBE+ADD_CANDLE_MODE) +// full FET strobe can be a bit much... use max regulated level instead, +// if there's a bright enough regulated level +#ifdef MAX_Nx7135 +#define STROBE_BRIGHTNESS MAX_Nx7135 +#else +#define STROBE_BRIGHTNESS MAX_LEVEL +#endif + #include "spaghetti-monster.h" @@ -385,17 +395,15 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_CHANNEL_BOUNDARIES) // only blink once for each threshold if ((memorized_level != actual_level) && ( + 0 // for easier syntax below #ifdef BLINK_AT_CHANNEL_BOUNDARIES - (memorized_level == MAX_1x7135) + || (memorized_level == MAX_1x7135) #if PWM_CHANNELS >= 3 || (memorized_level == MAX_Nx7135) #endif - #ifdef BLINK_AT_RAMP_CEILING - || - #endif #endif #ifdef BLINK_AT_RAMP_CEILING - (memorized_level == mode_max) + || (memorized_level == mode_max) #endif #if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR) || (memorized_level == mode_min) @@ -437,17 +445,15 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_CHANNEL_BOUNDARIES) // only blink once for each threshold if ((memorized_level != actual_level) && ( + 0 // for easier syntax below #ifdef BLINK_AT_CHANNEL_BOUNDARIES - (memorized_level == MAX_1x7135) + || (memorized_level == MAX_1x7135) #if PWM_CHANNELS >= 3 || (memorized_level == MAX_Nx7135) #endif - #ifdef BLINK_AT_RAMP_FLOOR - || - #endif #endif #ifdef BLINK_AT_RAMP_FLOOR - (memorized_level == mode_min) + || (memorized_level == mode_min) #endif )) { set_level(0); @@ -467,6 +473,15 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING) else if (event == EV_tick) { #ifdef USE_SET_LEVEL_GRADUALLY + /* TODO: make thermal adjustment speed scale with magnitude + if (ticks_since_adjust > ticks_per_adjust) { + gradual_tick(); + ticks_since_adjust = 0; + ticks_per_adjust = (TICKS_PER_SECOND*2) \ + / (diff(actual_level, target_level)) \ + + 8 - log2(actual_level); + } + */ if (!(arg & 7)) gradual_tick(); //if (!(arg & 3)) gradual_tick(); //gradual_tick(); @@ -527,6 +542,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t strobe_state(EventPtr event, uint16_t arg) { + // FIXME: re-order the strobes so candle and lightning are adjacent #ifdef USE_CANDLE_MODE //#define MAX_CANDLE_LEVEL (RAMP_SIZE-8-6-4) #define MAX_CANDLE_LEVEL (RAMP_SIZE/2) @@ -534,7 +550,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { static uint8_t candle_wave2 = 0; static uint8_t candle_wave3 = 0; static uint8_t candle_wave2_speed = 0; - static uint8_t candle_mode_brightness = 12; + static uint8_t candle_mode_brightness = 20; #endif if (event == EV_enter_state) { @@ -813,6 +829,7 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { uint8_t momentary_state(EventPtr event, uint16_t arg) { + // TODO: momentary strobe here? (for light painting) if (event == EV_click1_press) { set_level(memorized_level); empty_event_sequence(); // don't attempt to parse multiple clicks @@ -1014,7 +1031,6 @@ uint8_t number_entry_state(EventPtr event, uint16_t arg) { blinks_left = arg; entry_step = 0; wait_ticks = 0; - // TODO: blink out the 'arg' to show which option this is return MISCHIEF_MANAGED; } // advance through the process: @@ -1279,10 +1295,8 @@ void loop() { if (state == strobe_state) { // party / tactical strobe if (strobe_type < 2) { - // FIXME: for tactical strobe, use max Nx7135 level? - // (perhaps a compile option) - // (is relevant for FW3A) - set_level(MAX_LEVEL); + // TODO: make tac strobe brightness configurable? + set_level(STROBE_BRIGHTNESS); CLKPR = 1<= 1) last_event = current_event[le_num-1]; + uint8_t pressed = button_is_pressed(); + uint8_t was_pressed_before = (last_event == A_PRESS) || (last_event == A_HOLD); + //if (pressed != button_last_state) { + if (pressed != was_pressed_before) { + PCINT_inner(pressed); + /* + uint8_t pushed; + if (pressed) { + pushed = push_event(A_PRESS); + } else { + pushed = push_event(A_RELEASE); + } + + // check if sequence matches any defined sequences + // if so, send event to current state callback + if (pushed) { + button_last_state = pressed; + emit_current_event(0); + } + */ + } + PCINT_since_WDT = 0; + // if time since last event exceeds timeout, // append timeout to current event sequence, then // send event to current state callback // preload recent events - uint8_t le_num = last_event_num(); - uint8_t last_event = 0; + //uint8_t le_num = last_event_num(); + le_num = last_event_num(); + //uint8_t last_event = 0; + 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]; -- cgit v1.2.3 From 8c13fd82557dadbe81023c534cd19c31ec40bde4 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 12 Dec 2017 15:22:42 -0700 Subject: Debouncing finally works (at least, it does on my two test hosts). --- spaghetti-monster/fsm-pcint.c | 40 +++++++++++++++++----------------------- spaghetti-monster/fsm-pcint.h | 1 - spaghetti-monster/fsm-standby.c | 2 +- spaghetti-monster/fsm-wdt.c | 38 +++++++------------------------------- 4 files changed, 25 insertions(+), 56 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-pcint.c b/spaghetti-monster/fsm-pcint.c index c04375a..722cb88 100644 --- a/spaghetti-monster/fsm-pcint.c +++ b/spaghetti-monster/fsm-pcint.c @@ -24,20 +24,19 @@ #include 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; + while ((readings != 0) && (readings != 0xFFFFFFFF)); + button_last_state = readings; + return readings; } inline void PCINT_on() { @@ -59,20 +58,15 @@ ISR(PCINT0_vect) { //DEBUG_FLASH; - /* - uint8_t pressed; + // as it turns out, it's more reliable to detect pin changes from WDT + // because PCINT itself tends to double-tap when connected to a + // noisy / bouncy switch (so the content of this function has been + // moved to a separate function, called from WDT only) + // PCINT_inner(button_is_pressed()); - // add event to current sequence - pressed = button_is_pressed(); - PCINT_inner(pressed); - */ - if (! PCINT_since_WDT) { - PCINT_since_WDT = 1; - PCINT_inner(button_is_pressed()); - } } -// should only be called from PCINT and WDT +// should only be called from PCINT and/or WDT // (is a separate function to reduce code duplication) void PCINT_inner(uint8_t pressed) { uint8_t pushed; diff --git a/spaghetti-monster/fsm-pcint.h b/spaghetti-monster/fsm-pcint.h index a94fc82..ec3ae4b 100644 --- a/spaghetti-monster/fsm-pcint.h +++ b/spaghetti-monster/fsm-pcint.h @@ -23,7 +23,6 @@ //static volatile uint8_t button_was_pressed; #define BP_SAMPLES 32 volatile uint8_t button_last_state; -volatile uint8_t PCINT_since_WDT; uint8_t button_is_pressed(); inline void PCINT_on(); inline void PCINT_off(); diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c index eb631d6..b90ccea 100644 --- a/spaghetti-monster/fsm-standby.c +++ b/spaghetti-monster/fsm-standby.c @@ -37,7 +37,7 @@ void sleep_until_eswitch_pressed() // make sure switch isn't currently pressed while (button_is_pressed()) {} empty_event_sequence(); // cancel pending input on suspend - PCINT_since_WDT = 0; // ensure PCINT won't ignore itself + //PCINT_since_WDT = 0; // ensure PCINT won't ignore itself PCINT_on(); // wake on e-switch event diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c index bee2914..777bef0 100644 --- a/spaghetti-monster/fsm-wdt.c +++ b/spaghetti-monster/fsm-wdt.c @@ -45,47 +45,23 @@ inline void WDT_off() // clock tick -- this runs every 16ms (62.5 fps) ISR(WDT_vect) { + // detect and emit button change events + uint8_t was_pressed = button_last_state; + uint8_t pressed = button_is_pressed(); + if (was_pressed != pressed) PCINT_inner(pressed); + //if (ticks_since_last_event < 0xff) ticks_since_last_event ++; // increment, but loop from max back to half ticks_since_last_event = (ticks_since_last_event + 1) \ | (ticks_since_last_event & 0x8000); - // just in case the pin change interrupt missed something - uint8_t le_num = last_event_num(); - uint8_t last_event = 0; - if (le_num >= 1) last_event = current_event[le_num-1]; - uint8_t pressed = button_is_pressed(); - uint8_t was_pressed_before = (last_event == A_PRESS) || (last_event == A_HOLD); - //if (pressed != button_last_state) { - if (pressed != was_pressed_before) { - PCINT_inner(pressed); - /* - uint8_t pushed; - if (pressed) { - pushed = push_event(A_PRESS); - } else { - pushed = push_event(A_RELEASE); - } - - // check if sequence matches any defined sequences - // if so, send event to current state callback - if (pushed) { - button_last_state = pressed; - emit_current_event(0); - } - */ - } - PCINT_since_WDT = 0; - // if time since last event exceeds timeout, // append timeout to current event sequence, then // send event to current state callback // preload recent events - //uint8_t le_num = last_event_num(); - le_num = last_event_num(); - //uint8_t last_event = 0; - last_event = 0; + 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]; -- cgit v1.2.3 From 148b2f04e528a13051fd81c5054a7268e0dc4dac Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 4 Jan 2018 00:10:42 -0700 Subject: Made thermal adjustment speed change depending on how far it needs to go. (faster for large changes, much slower for smaller changes) (actual thermal behavior not tested yet) Made lockout mode moon match the current ramp instead of using the lowest of both. Made ramp-type toggle stop doing gradual adjustments back to the previous ramp's level (I hope). Added LVP to muggle mode. --- spaghetti-monster/anduril/anduril.c | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 4915b39..40809af 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -359,6 +359,12 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { else if (event == EV_3clicks) { ramp_style ^= 1; memorized_level = nearest_level(memorized_level); + #ifdef USE_THERMAL_REGULATION + target_level = memorized_level; + #ifdef USE_SET_LEVEL_GRADUALLY + //set_level_gradually(lvl); + #endif + #endif save_config(); #ifdef START_AT_MEMORIZED_LEVEL save_config_wl(); @@ -482,7 +488,15 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { + 8 - log2(actual_level); } */ - if (!(arg & 7)) gradual_tick(); + uint8_t diff; + if (target_level > actual_level) diff = target_level - actual_level; + else diff = actual_level - target_level; + if (! diff) diff = 1; + uint8_t ticks_per_adjust = (TICKS_PER_SECOND*2) / diff; + if (!(arg % ticks_per_adjust)) gradual_tick(); + + // adjust every N frames + //if (!(arg & 7)) gradual_tick(); //if (!(arg & 3)) gradual_tick(); //gradual_tick(); #endif @@ -766,7 +780,13 @@ uint8_t lockout_state(EventPtr event, uint16_t arg) { if ((last == A_PRESS) || (last == A_HOLD)) { // detect moon level and activate it uint8_t lvl = ramp_smooth_floor; + #ifdef LOCKOUT_MOON_LOWEST + // Use lowest moon configured if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor; + #else + // Use moon from current ramp + if (ramp_style) lvl = ramp_discrete_floor; + #endif set_level(lvl); } else if ((last == A_RELEASE) || (last == A_RELEASE_TIMEOUT)) { @@ -1226,14 +1246,24 @@ void save_config_wl() { #endif void low_voltage() { + StatePtr state = current_state; + // "step down" from strobe to something low - if (current_state == strobe_state) { + if (state == strobe_state) { set_state(steady_state, RAMP_SIZE/6); } - // in normal mode, step down by half or turn off - else if (current_state == steady_state) { + // in normal or muggle mode, step down by half or turn off + else if ((state == steady_state) || (state == muggle_state)) { if (actual_level > 1) { - set_level((actual_level >> 1) + (actual_level >> 2)); + uint8_t lvl = (actual_level >> 1) + (actual_level >> 2); + set_level(lvl); + #ifdef USE_THERMAL_REGULATION + target_level = lvl; + #ifdef USE_SET_LEVEL_GRADUALLY + // not needed? + //set_level_gradually(lvl); + #endif + #endif } else { set_state(off_state, 0); -- cgit v1.2.3 From 30b13cc13baba55ac1610ad471ca737a6c817683 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 18:27:44 -0700 Subject: Work around issues related to ADC interrupt auto-triggering itself. (was firing off 1000 times faster than desired, causing several issues) (now only executes when explicitly requested by the WDT) --- spaghetti-monster/fsm-adc.c | 25 +++++++++++++++++-------- spaghetti-monster/fsm-adc.h | 1 + spaghetti-monster/fsm-wdt.c | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 839fba8..d863b94 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -44,6 +44,13 @@ inline void ADC_off() { #endif // TODO: is this better done in main() or WDT()? ISR(ADC_vect) { + // For some reason, the ADC interrupt is getting called a *lot* + // more often than it should be, like it's auto-triggering after each + // measurement, but I don't know why, or how to turn that off... + // So, skip every call except when explicitly requested. + if (! adcint_enable) return; + adcint_enable = 0; + static uint8_t adc_step = 0; // LVP declarations @@ -218,11 +225,13 @@ ISR(ADC_vect) { // average prediction to reduce noise int16_t avg_projected_temperature = 0; - for (uint8_t i = 0; + uint8_t i; + for (i = 0; (i < NUM_THERMAL_PROJECTED_HISTORY) && (avg_projected_temperature < 16000); i++) avg_projected_temperature += projected_temperature_history[i]; avg_projected_temperature /= NUM_THERMAL_PROJECTED_HISTORY; + //avg_projected_temperature /= i; // cancel counters if appropriate if (pt > THERM_FLOOR) { @@ -240,15 +249,15 @@ ISR(ADC_vect) { if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) { overheat_lowpass ++; } else { + // reset counters + overheat_lowpass = 0; + temperature_timer = TEMPERATURE_TIMER_START; // how far above the ceiling? int16_t howmuch = (avg_projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION; if (howmuch > 0) { // try to send out a warning emit(EV_temperature_high, howmuch); } - // reset counters - temperature_timer = TEMPERATURE_TIMER_START; - overheat_lowpass = 0; } } @@ -257,6 +266,9 @@ ISR(ADC_vect) { if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) { underheat_lowpass ++; } else { + // reset counters + underheat_lowpass = 0; + temperature_timer = TEMPERATURE_TIMER_START; // how far below the floor? int16_t howmuch = (THERM_FLOOR - avg_projected_temperature) >> THERM_DIFF_ATTENUATION; if (howmuch > 0) { @@ -265,9 +277,6 @@ ISR(ADC_vect) { if (voltage > VOLTAGE_LOW) emit(EV_temperature_low, howmuch); } - // reset counters - temperature_timer = TEMPERATURE_TIMER_START; - underheat_lowpass = 0; } } } @@ -275,7 +284,7 @@ ISR(ADC_vect) { #endif // ifdef USE_THERMAL_REGULATION - // start another measurement for next time + // set the correct type of measurement for next time #ifdef USE_THERMAL_REGULATION #ifdef USE_LVP if (adc_step < 2) ADMUX = ADMUX_VCC; diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index b9462f7..5ffbb73 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -35,6 +35,7 @@ #define VOLTAGE_FUDGE_FACTOR 5 #endif volatile uint8_t voltage; +volatile uint8_t adcint_enable; // kludge, because adc auto-retrigger won't turn off void low_voltage(); #ifdef USE_BATTCHECK void battcheck(); diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c index 777bef0..ff96ffb 100644 --- a/spaghetti-monster/fsm-wdt.c +++ b/spaghetti-monster/fsm-wdt.c @@ -107,9 +107,9 @@ ISR(WDT_vect) { // start a new ADC measurement every 4 ticks static uint8_t adc_trigger = 0; adc_trigger ++; - if (adc_trigger > 3) { - adc_trigger = 0; + if (0 == (adc_trigger & 3)) { ADCSRA |= (1 << ADSC) | (1 << ADIE); + adcint_enable = 1; } #endif } -- cgit v1.2.3 From 8267c1f121d9766e37d2056004bd7bb47b512288 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 18:28:29 -0700 Subject: Save a few bytes by changing how PCINT is defined. Minor comment cleaning. --- spaghetti-monster/fsm-events.h | 2 -- spaghetti-monster/fsm-pcint.c | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h index 28f1b10..2721303 100644 --- a/spaghetti-monster/fsm-events.h +++ b/spaghetti-monster/fsm-events.h @@ -61,10 +61,8 @@ static volatile uint16_t ticks_since_last_event = 0; #define A_HOLD 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 diff --git a/spaghetti-monster/fsm-pcint.c b/spaghetti-monster/fsm-pcint.c index 722cb88..a79572d 100644 --- a/spaghetti-monster/fsm-pcint.c +++ b/spaghetti-monster/fsm-pcint.c @@ -54,6 +54,8 @@ inline void PCINT_off() { } //void button_change_interrupt() { +EMPTY_INTERRUPT(PCINT0_vect); +/* ISR(PCINT0_vect) { //DEBUG_FLASH; @@ -65,6 +67,7 @@ ISR(PCINT0_vect) { // PCINT_inner(button_is_pressed()); } +*/ // should only be called from PCINT and/or WDT // (is a separate function to reduce code duplication) -- cgit v1.2.3 From 653c370cb4f243e2ea8c46bd5d8582f42244cb47 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 18:31:37 -0700 Subject: Thermal regulation adjustments... - Don't step down lower than 1x7135 level. - Change speed of gradual adjustments based on how far it needs to go, but in all cases slower than before. (may need further adjusting) (is currently a mess, but I wanted to check it in before I tweak it further) --- spaghetti-monster/anduril/anduril.c | 40 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 40809af..1a490ce 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -20,13 +20,14 @@ /********* User-configurable options *********/ // Physical driver type -//#define FSM_EMISAR_D4_DRIVER +#define FSM_EMISAR_D4_DRIVER //#define FSM_BLF_Q8_DRIVER -#define FSM_FW3A_DRIVER +//#define FSM_FW3A_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION #define DEFAULT_THERM_CEIL 50 +#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to #define USE_SET_LEVEL_GRADUALLY #define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR @@ -488,11 +489,28 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { + 8 - log2(actual_level); } */ + // [int(62*3 / (x**0.75)) for x in (1,2,4,8,16,32,64,128)] + //uint8_t intervals[] = {186, 110, 65, 39, 23, 13, 8, 4}; + // [int(62*4 / (x**0.66666)) for x in (1,2,4,8,16,32,64,128)] + uint8_t intervals[] = {248, 156, 98, 62, 39, 24, 15, 9}; uint8_t diff; + //static uint8_t ticks_since_adjust = 0; + //ticks_since_adjust ++; if (target_level > actual_level) diff = target_level - actual_level; else diff = actual_level - target_level; - if (! diff) diff = 1; - uint8_t ticks_per_adjust = (TICKS_PER_SECOND*2) / diff; + //if (! diff) diff = 1; + //uint8_t ticks_per_adjust = (TICKS_PER_SECOND*4) / diff; + uint8_t magnitude = 0; + while (diff) { + magnitude ++; + diff >>= 1; + } + uint8_t ticks_per_adjust = intervals[magnitude]; + //if (ticks_since_adjust > ticks_per_adjust) + //{ + // gradual_tick(); + // ticks_since_adjust = 0; + //} if (!(arg % ticks_per_adjust)) gradual_tick(); // adjust every N frames @@ -510,15 +528,15 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION // overheating: drop by an amount proportional to how far we are above the ceiling else if (event == EV_temperature_high) { - /* + #if 0 uint8_t foo = actual_level; set_level(0); delay_4ms(2); set_level(foo); - */ - if (actual_level > MAX_LEVEL/3) { + #endif + if (actual_level > MIN_THERM_STEPDOWN) { int16_t stepdown = actual_level - arg; - if (stepdown < MAX_LEVEL/3) stepdown = MAX_LEVEL/3; + if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN; else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL; #ifdef USE_SET_LEVEL_GRADUALLY set_level_gradually(stepdown); @@ -531,17 +549,17 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { // underheating: increase slowly if we're lower than the target // (proportional to how low we are) else if (event == EV_temperature_low) { - /* + #if 0 uint8_t foo = actual_level; set_level(0); delay_4ms(2); set_level(foo); - */ + #endif if (actual_level < target_level) { //int16_t stepup = actual_level + (arg>>1); int16_t stepup = actual_level + arg; if (stepup > target_level) stepup = target_level; - else if (stepup < MAX_LEVEL/3) stepup = MAX_LEVEL/3; + else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN; #ifdef USE_SET_LEVEL_GRADUALLY set_level_gradually(stepup); #else -- cgit v1.2.3 From 5ee5845d4614cb8285cdf0bf529c26bec7ce7217 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 19:43:00 -0700 Subject: FSM: added ability to adjust temperature calibration in UI (lower-case therm_cal_offset var). --- spaghetti-monster/fsm-adc.c | 2 +- spaghetti-monster/fsm-adc.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index d863b94..c6f5d2a 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -150,7 +150,7 @@ ISR(ADC_vect) { // temperature else if (adc_step == 3) { // Convert ADC units to Celsius (ish) - int16_t temp = measurement - 275 + THERM_CAL_OFFSET; + int16_t temp = measurement - 275 + THERM_CAL_OFFSET + therm_cal_offset; // prime on first execution if (reset_thermal_history) { diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index 5ffbb73..1b16d01 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -66,11 +66,12 @@ void battcheck(); #ifndef THERM_CAL_OFFSET #define THERM_CAL_OFFSET 0 #endif -// temperature now, in C (ish) +// temperature now, in C (ish) * 2 (14.1 fixed-point) volatile int16_t temperature; -// temperature in a few seconds, in C (ish) * 4 (13.2 fixed-point) +// temperature in a few seconds, in C (ish) * 2 (14.1 fixed-point) volatile int16_t projected_temperature; // Fight the future! -volatile uint8_t therm_ceil = DEFAULT_THERM_CEIL; +uint8_t therm_ceil = DEFAULT_THERM_CEIL; +int8_t therm_cal_offset = 0; //void low_temperature(); //void high_temperature(); volatile uint8_t reset_thermal_history = 1; -- cgit v1.2.3 From b005fc57c07d2acd7debedb4195450b691452642 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 19:44:31 -0700 Subject: Anduril: Added room-temperature calibration function. Slowed down thermal regulation speed for small adjustments. --- spaghetti-monster/anduril/anduril.c | 87 +++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 43 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 1a490ce..eae671a 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -66,11 +66,12 @@ #endif // try to auto-detect how many eeprom bytes +// FIXME: detect this better, and assign offsets better, for various configs #define USE_EEPROM #ifdef USE_INDICATOR_LED -#define EEPROM_BYTES 13 +#define EEPROM_BYTES 14 #elif defined(USE_THERMAL_REGULATION) -#define EEPROM_BYTES 12 +#define EEPROM_BYTES 13 #else #define EEPROM_BYTES 11 #endif @@ -479,48 +480,32 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { #endif #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING) else if (event == EV_tick) { + #ifdef USE_REVERSING + // un-reverse after 1 second + if (arg == TICKS_PER_SECOND) ramp_direction = 1; + #endif #ifdef USE_SET_LEVEL_GRADUALLY - /* TODO: make thermal adjustment speed scale with magnitude - if (ticks_since_adjust > ticks_per_adjust) { - gradual_tick(); - ticks_since_adjust = 0; - ticks_per_adjust = (TICKS_PER_SECOND*2) \ - / (diff(actual_level, target_level)) \ - + 8 - log2(actual_level); - } - */ - // [int(62*3 / (x**0.75)) for x in (1,2,4,8,16,32,64,128)] - //uint8_t intervals[] = {186, 110, 65, 39, 23, 13, 8, 4}; - // [int(62*4 / (x**0.66666)) for x in (1,2,4,8,16,32,64,128)] - uint8_t intervals[] = {248, 156, 98, 62, 39, 24, 15, 9}; + // make thermal adjustment speed scale with magnitude + if (arg & 1) return MISCHIEF_MANAGED; // adjust slower + // [int(62*4 / (x**0.8)) for x in (1,2,4,8,16,32,64,128)] + uint8_t intervals[] = {248, 142, 81, 46, 26, 15, 8, 5}; uint8_t diff; - //static uint8_t ticks_since_adjust = 0; - //ticks_since_adjust ++; + static uint8_t ticks_since_adjust = 0; + ticks_since_adjust ++; if (target_level > actual_level) diff = target_level - actual_level; else diff = actual_level - target_level; - //if (! diff) diff = 1; - //uint8_t ticks_per_adjust = (TICKS_PER_SECOND*4) / diff; uint8_t magnitude = 0; while (diff) { magnitude ++; diff >>= 1; } uint8_t ticks_per_adjust = intervals[magnitude]; - //if (ticks_since_adjust > ticks_per_adjust) - //{ - // gradual_tick(); - // ticks_since_adjust = 0; - //} - if (!(arg % ticks_per_adjust)) gradual_tick(); - - // adjust every N frames - //if (!(arg & 7)) gradual_tick(); - //if (!(arg & 3)) gradual_tick(); - //gradual_tick(); - #endif - #ifdef USE_REVERSING - // un-reverse after 1 second - if (arg == TICKS_PER_SECOND) ramp_direction = 1; + if (ticks_since_adjust > ticks_per_adjust) + { + gradual_tick(); + ticks_since_adjust = 0; + } + //if (!(arg % ticks_per_adjust)) gradual_tick(); #endif return MISCHIEF_MANAGED; } @@ -1006,26 +991,40 @@ uint8_t ramp_config_state(EventPtr event, uint16_t arg) { #ifdef USE_THERMAL_REGULATION uint8_t thermal_config_state(EventPtr event, uint16_t arg) { - static uint8_t done = 0; + static uint8_t config_step; if (event == EV_enter_state) { set_level(0); - done = 0; + config_step = 0; return MISCHIEF_MANAGED; } // advance forward through config steps else if (event == EV_tick) { // ask the user for a number - if (! done) push_state(number_entry_state, 0); + if (config_step < 2) { + push_state(number_entry_state, config_step + 1); + } // return to original mode - else set_state(tempcheck_state, 0); + else { + save_config(); + set_state(tempcheck_state, 0); + } return MISCHIEF_MANAGED; } // an option was set (return from number_entry_state) else if (event == EV_reenter_state) { - if (number_entry_value) therm_ceil = 30 + number_entry_value; + if (number_entry_value) { + // calibrate room temperature + if (config_step == 0) { + int8_t rawtemp = (temperature >> 1) - therm_cal_offset; + therm_cal_offset = number_entry_value - rawtemp; + } + // set maximum heat limit + else { + therm_ceil = 30 + number_entry_value; + } + } if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; - save_config(); - done = 1; + config_step ++; return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -1222,9 +1221,10 @@ void load_config() { beacon_seconds = eeprom[10]; #ifdef USE_THERMAL_REGULATION therm_ceil = eeprom[11]; + therm_cal_offset = eeprom[12]; #endif #ifdef USE_INDICATOR_LED - indicator_led_mode = eeprom[12]; + indicator_led_mode = eeprom[13]; #endif } #ifdef START_AT_MEMORIZED_LEVEL @@ -1248,9 +1248,10 @@ void save_config() { eeprom[10] = beacon_seconds; #ifdef USE_THERMAL_REGULATION eeprom[11] = therm_ceil; + eeprom[12] = therm_cal_offset; #endif #ifdef USE_INDICATOR_LED - eeprom[12] = indicator_led_mode; + eeprom[13] = indicator_led_mode; #endif save_eeprom(); -- cgit v1.2.3 From e7ba9ebb7ddbad952da046c70853a835c737d4a7 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 20:06:11 -0700 Subject: Anduril: Re-ordered strobe modes. Reduced compiled size a bit by caching volatile vars. --- spaghetti-monster/anduril/anduril.c | 84 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 35 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index eae671a..2509218 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -188,7 +188,12 @@ uint8_t target_level = 0; // strobe timing volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe -volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 == lightning storm, 3 == bike flasher +// 0 == bike flasher +// 1 == party strobe +// 2 == tactical strobe +// 3 == lightning storm +// 4 == candle mode +volatile uint8_t strobe_type = 4; // bike mode config options volatile uint8_t bike_flasher_brightness = MAX_1x7135; @@ -560,6 +565,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t strobe_state(EventPtr event, uint16_t arg) { // FIXME: re-order the strobes so candle and lightning are adjacent + uint8_t st = strobe_type; #ifdef USE_CANDLE_MODE //#define MAX_CANDLE_LEVEL (RAMP_SIZE-8-6-4) #define MAX_CANDLE_LEVEL (RAMP_SIZE/2) @@ -580,7 +586,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { } // 2 clicks: rotate through strobe/flasher modes else if (event == EV_2clicks) { - strobe_type = (strobe_type + 1) % NUM_STROBES; + strobe_type = (st + 1) % NUM_STROBES; interrupt_nice_delays(); save_config(); return MISCHIEF_MANAGED; @@ -588,20 +594,23 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { // hold: change speed (go faster) // or change brightness (brighter) else if (event == EV_click1_hold) { - if (strobe_type < 2) { - if ((arg & 1) == 0) { - if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --; - } - } // biking mode brighter - else if (strobe_type == 3) { + if (st == 0) { if (bike_flasher_brightness < MAX_BIKING_LEVEL) bike_flasher_brightness ++; set_level(bike_flasher_brightness); } + // strobe faster + else if (st < 3) { + if ((arg & 1) == 0) { + if (strobe_delays[st-1] > 8) strobe_delays[st-1] --; + } + } + // lightning has no adjustments + // else if (st == 3) {} #ifdef USE_CANDLE_MODE // candle mode brighter - else if (strobe_type == 4) { + else if (st == 4) { if (candle_mode_brightness < MAX_CANDLE_LEVEL) candle_mode_brightness ++; } @@ -611,20 +620,23 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { // click, hold: change speed (go slower) // or change brightness (dimmer) else if (event == EV_click2_hold) { - if (strobe_type < 2) { - if ((arg & 1) == 0) { - if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++; - } - } // biking mode dimmer - else if (strobe_type == 3) { + if (st == 0) { if (bike_flasher_brightness > 2) bike_flasher_brightness --; set_level(bike_flasher_brightness); } + // strobe slower + else if (st < 3) { + if ((arg & 1) == 0) { + if (strobe_delays[st-1] < 255) strobe_delays[st-1] ++; + } + } + // lightning has no adjustments + // else if (st == 3) {} #ifdef USE_CANDLE_MODE // candle mode dimmer - else if (strobe_type == 4) { + else if (st == 4) { if (candle_mode_brightness > 1) candle_mode_brightness --; } @@ -644,7 +656,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { pseudo_rand_seed += arg; #endif #ifdef USE_CANDLE_MODE - if (strobe_type == 4) { + if (st == 4) { // 3-oscillator synth for a relatively organic pattern uint8_t add; add = ((triangle_wave(candle_wave1) * 8) >> 8) @@ -1342,23 +1354,37 @@ void loop() { #endif if (state == strobe_state) { + uint8_t st = strobe_type; + // bike flasher + if (st == 0) { + uint8_t burst = bike_flasher_brightness << 1; + if (burst > MAX_LEVEL) burst = MAX_LEVEL; + for(uint8_t i=0; i<4; i++) { + set_level(burst); + if (! nice_delay_ms(5)) return; + set_level(bike_flasher_brightness); + if (! nice_delay_ms(65)) return; + } + if (! nice_delay_ms(720)) return; + } // party / tactical strobe - if (strobe_type < 2) { + else if (st < 3) { + uint8_t del = strobe_delays[st-1]; // TODO: make tac strobe brightness configurable? set_level(STROBE_BRIGHTNESS); CLKPR = 1<> 1); + nice_delay_ms(del >> 1); } set_level(0); - nice_delay_ms(strobe_delays[strobe_type]); + nice_delay_ms(del); } #ifdef USE_LIGHTNING_MODE // lightning storm - else if (strobe_type == 2) { + else if (st == 3) { int16_t brightness; uint16_t rand_time; @@ -1403,18 +1429,6 @@ void loop() { } #endif - // bike flasher - else if (strobe_type == 3) { - uint8_t burst = bike_flasher_brightness << 1; - if (burst > MAX_LEVEL) burst = MAX_LEVEL; - for(uint8_t i=0; i<4; i++) { - set_level(burst); - if (! nice_delay_ms(5)) return; - set_level(bike_flasher_brightness); - if (! nice_delay_ms(65)) return; - } - if (! nice_delay_ms(720)) return; - } } #ifdef USE_BATTCHECK -- cgit v1.2.3 From 729d7e2cecf19f9de18e5a2ef45cd28b369334ab Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 20:45:10 -0700 Subject: Made candle mode more candle-like and more complex. --- spaghetti-monster/anduril/anduril.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 2509218..71c3d62 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -573,6 +573,8 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { static uint8_t candle_wave2 = 0; static uint8_t candle_wave3 = 0; static uint8_t candle_wave2_speed = 0; + static uint8_t candle_wave2_depth = 7; + static uint8_t candle_wave3_depth = 4; static uint8_t candle_mode_brightness = 20; #endif @@ -660,19 +662,32 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { // 3-oscillator synth for a relatively organic pattern uint8_t add; add = ((triangle_wave(candle_wave1) * 8) >> 8) - + ((triangle_wave(candle_wave2) * 7) >> 8) - + ((triangle_wave(candle_wave3) * 4) >> 8); + + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8) + + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8); set_level(candle_mode_brightness + add); - // slow LFO + // wave1: slow random LFO if ((arg & 1) == 0) candle_wave1 += pseudo_rand()&1; - // faster LFO - //candle_wave2 += pseudo_rand()%13; + // wave2: medium-speed erratic LFO candle_wave2 += candle_wave2_speed; - // erratic fast wave + // wave3: erratic fast wave candle_wave3 += pseudo_rand()%37; // S&H on wave2 frequency to make it more erratic - if ((pseudo_rand()>>3) == 0) + if ((pseudo_rand()>>2) == 0) candle_wave2_speed = pseudo_rand()%13; + // downward sawtooth on wave2 depth to simulate stabilizing + if ((candle_wave2_depth > 0) && ((pseudo_rand()>>2) == 0)) + candle_wave2_depth --; + // random sawtooth retrigger + if ((pseudo_rand()) == 0) { + candle_wave2_depth = 6; + //candle_wave3_depth = 5; + candle_wave2 = 0; + } + // downward sawtooth on wave3 depth to simulate stabilizing + if ((candle_wave3_depth > 2) && ((pseudo_rand()>>3) == 0)) + candle_wave3_depth --; + if ((pseudo_rand()>>1) == 0) + candle_wave3_depth = 4; } #endif return MISCHIEF_MANAGED; -- cgit v1.2.3 From e28b53c50d967a291b29c713ed9e637edd072e30 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 24 Jan 2018 21:07:08 -0700 Subject: Minor candle mode tweaks for a better appearance. --- spaghetti-monster/anduril/anduril.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 71c3d62..9d7e0d8 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -575,7 +575,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { static uint8_t candle_wave2_speed = 0; static uint8_t candle_wave2_depth = 7; static uint8_t candle_wave3_depth = 4; - static uint8_t candle_mode_brightness = 20; + static uint8_t candle_mode_brightness = 24; #endif if (event == EV_enter_state) { @@ -679,7 +679,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { candle_wave2_depth --; // random sawtooth retrigger if ((pseudo_rand()) == 0) { - candle_wave2_depth = 6; + candle_wave2_depth = 7; //candle_wave3_depth = 5; candle_wave2 = 0; } @@ -687,7 +687,7 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { if ((candle_wave3_depth > 2) && ((pseudo_rand()>>3) == 0)) candle_wave3_depth --; if ((pseudo_rand()>>1) == 0) - candle_wave3_depth = 4; + candle_wave3_depth = 5; } #endif return MISCHIEF_MANAGED; -- cgit v1.2.3 From 07ca00069778798296170a14bfe1c33e8b2e360e Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 25 Jan 2018 04:32:33 -0700 Subject: Updated Anduril UI text/diagram to match current code. --- spaghetti-monster/anduril/anduril-ui.png | Bin 229400 -> 237749 bytes spaghetti-monster/anduril/anduril.svg | 1264 +++++++++++++++++++----------- spaghetti-monster/anduril/anduril.txt | 10 +- 3 files changed, 835 insertions(+), 439 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril-ui.png b/spaghetti-monster/anduril/anduril-ui.png index 555e9a4..2776696 100644 Binary files a/spaghetti-monster/anduril/anduril-ui.png and b/spaghetti-monster/anduril/anduril-ui.png differ diff --git a/spaghetti-monster/anduril/anduril.svg b/spaghetti-monster/anduril/anduril.svg index 11ee01c..b72e2f8 100644 --- a/spaghetti-monster/anduril/anduril.svg +++ b/spaghetti-monster/anduril/anduril.svg @@ -12,14 +12,14 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="18.056446cm" - height="27.595072cm" + height="26.675991cm" id="svg2" version="1.1" inkscape:version="0.92.1 r15371" sodipodi:docname="anduril.svg" inkscape:export-filename="/tmp/anduril-ui.png" - inkscape:export-xdpi="110.4545" - inkscape:export-ydpi="110.4545"> + inkscape:export-xdpi="114.26005" + inkscape:export-ydpi="114.26005"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + units="cm" + inkscape:snap-global="false" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true"> + originy="-33.268489" /> @@ -2215,12 +2401,12 @@ Blinkies - - + - + + id="g14799" + transform="translate(-1.0079016)"> + BattCheck + id="g14764"> + TempCheck + id="g14731"> + Beacon - - Good - - Night + id="g21356"> + + + Sunset + + id="g14689" + transform="translate(0.47473145)"> ThermalCfg + style="fill:none;stroke:#ff0000;stroke-width:2.39758635;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + id="g14683"> Beacon + sodipodi:role="line">BeaconCfg - Cfg + style="fill:none;stroke:#000000;stroke-width:2.39758635;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> - - Bike - - Flasher - - - Party - - Strobe - - - Tactical - - Strobe - - - Lightning - - Storm - - - - Faster + x="1764.0304" + y="1209.2281">Faster Slower + x="1758.0332" + y="1294.8982">Slower Faster + x="1534.7776" + y="1275.1989">Faster Slower + x="1636.3998" + y="1275.9468">Slower Brighter + x="1452.4137" + y="1372.4056">Brighter Dimmer + x="1564.822" + y="1377.1671">Dimmer @@ -2873,103 +2896,111 @@ sodipodi:nodetypes="cc" /> Ramp CfgRamp Cfg 1. Floor (click N times for level N)2. Ceiling (click N times for Turbo-N) (click N times for 1+Turbo-N)3. Number of steps (discrete ramp only) + x="1444.4535" + y="886.88184" + id="tspan43997"> (stepped ramp only) 1. Temperature limit1. Current temperature (click N times for N deg C)2. Temperature limit (click N times for 30 C + N) 1. Beacon speed1. Beacon speed (click N times for N seconds per flash) Andúril‎ UI @@ -3180,7 +3211,7 @@ sodipodi:nodetypes="cc" /> @@ -3245,7 +3276,7 @@ id="textPath56258" style="font-size:18.66666603px;stroke-width:0.90419513px"> Smooth / Discrete + style="font-size:18.66666603px;stroke-width:0.90419513px"> Smooth / Stepped Discrete + id="tspan56275">Stepped Thermal Cfg Beacon Cfg + transform="translate(-260.15697,-113.49087)"> @@ -3322,14 +3353,14 @@ sodipodi:sides="7" sodipodi:cx="-309.93027" sodipodi:cy="215.58585" - sodipodi:r1="84.671265" + sodipodi:r1="69.471764" sodipodi:r2="50.343449" - sodipodi:arg1="0" - sodipodi:arg2="0.68976368" + sodipodi:arg1="-0.13301265" + sodipodi:arg2="0.55675103" inkscape:flatsided="false" inkscape:rounded="0.007" inkscape:randomized="0" - d="m -225.259,215.58585 c -0.0935,0.38012 -45.66671,31.68359 -45.83651,32.0363 -0.11206,0.23275 14.1915,34.05419 13.95691,34.16236 -0.35549,0.16391 -53.24395,-15.94928 -53.62558,-15.86212 -0.25184,0.0575 -17.77639,32.3278 -18.00722,32.21184 -0.34979,-0.17574 -20.72741,-51.57201 -21.0335,-51.81604 -0.20198,-0.16104 -36.35829,6.25792 -36.41154,6.00514 -0.0807,-0.38305 27.39728,-48.35997 27.39724,-48.75142 -3e-5,-0.25833 -27.56167,-24.52431 -27.39724,-24.72355 0.24917,-0.30192 54.89127,-8.73189 55.1973,-8.97599 0.20194,-0.16109 1.98946,-36.83923 2.24774,-36.8349 0.39141,0.007 41.05102,37.47148 41.43267,37.55854 0.25185,0.0574 30.04248,-21.41346 30.20013,-21.20882 0.23891,0.3101 -3.70149,55.45806 -3.5316,55.81073 0.11211,0.23273 35.47289,10.13708 35.4112,10.38793 z" + d="m -241.07216,206.37245 c -0.0335,0.30848 -26.02137,35.52146 -26.11771,35.81642 -0.0494,0.15121 7.5296,21.40277 7.39526,21.48795 -0.26206,0.16616 -43.99586,1.80294 -44.28653,1.91153 -0.14902,0.0557 -12.03874,19.23129 -12.1891,19.17936 -0.2933,-0.10128 -28.84056,-33.27323 -29.10669,-33.43278 -0.13644,-0.0818 -22.54165,2.57825 -22.59481,2.42832 -0.10367,-0.29247 8.03227,-43.29399 7.99108,-43.60153 -0.0211,-0.15767 -16.07025,-16.01627 -15.98616,-16.1513 0.16401,-0.26341 38.85663,-20.71348 39.0714,-20.93744 0.1101,-0.11481 2.50238,-22.5502 2.66038,-22.56865 0.3082,-0.036 40.42117,17.4647 40.73017,17.49298 0.15841,0.0145 19.19067,-12.10338 19.30361,-11.99136 0.2203,0.21851 11.54773,42.4916 11.71828,42.75082 0.0874,0.13289 21.42799,7.45753 21.41082,7.61568 z" transform="matrix(0.92047755,0,0,0.7899176,640.59003,565.68064)" inkscape:transform-center-x="-3.8589074" /> + sodipodi:nodetypes="csc" /> @@ -3382,47 +3413,406 @@ 4 Clicks + inkscape:transform-center-y="-2.5788507" + transform="translate(11.068361,12.736197)"> 4 Clicks 5 Clicks + inkscape:transform-center-y="-2.4072198" + transform="translate(3.9941843,-3.1212455)"> 5 Clicks Click, Click, Hold + inkscape:transform-center-y="-1.9746618" + transform="translate(4.5029255)"> Click, Click, Hold + + + + + + + + + Bike + + Flasher + + + Party + + Strobe + + + Tactical + + Strobe + + + Lightning + + Storm + + + Candle + + + + + + + Brighter/ Faster + + + + Dimmer/ Slower + + + + + + Muggle + + Momentary + + + + + 6 Clicks + diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index 2fb74bf..54f8b8c 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -9,7 +9,8 @@ From off: Blinkies: * 3 clicks: specials (battcheck, goodnight, beacon, tempcheck) * Click, click, hold: strobes - (bike flasher, party strobe, tactical strobe, lightning storm mode) + (candle mode, bike flasher, party strobe, tactical strobe, + lightning storm mode) (remembers which you last used) Other: @@ -82,7 +83,12 @@ Tempcheck mode: - Hold: thermal calibration mode Thermal config mode: - * At buzz, click N times to set thermal limit to roughly 30 C + N. + * Setting 1: calibrate sensor: + At buzz, click N times for N degrees C. For example, if the light + is current at "room temperature" of 22 C, click 22 times. Is + intended to only be done once upon initial setup, or not at all. + * Setting 2: temperature limit: + At buzz, click N times to set thermal limit to roughly 30 C + N. Thermal calibration mode: - Hold until hot: set new ceiling value -- cgit v1.2.3 From b5ecf384ef9c4217196e47dc192d5b936f2685fa Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 23 Apr 2018 21:44:45 -0600 Subject: Experiment: make ramp blinks configurable by blinking at every fixed-ramp step during the smooth ramp. (spoiler: it's annoying) --- spaghetti-monster/anduril/anduril.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 9d7e0d8..6323679 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -32,6 +32,7 @@ #define BLINK_AT_CHANNEL_BOUNDARIES //#define BLINK_AT_RAMP_FLOOR #define BLINK_AT_RAMP_CEILING +//#define BLINK_AT_STEPS #define BATTCHECK_VpT #define USE_LIGHTNING_MODE #define USE_CANDLE_MODE @@ -426,6 +427,21 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { delay_4ms(8/4); } #endif + #if defined(BLINK_AT_STEPS) + uint8_t foo = ramp_style; + ramp_style = 1; + uint8_t nearest = nearest_level((int16_t)actual_level); + ramp_style = foo; + // only blink once for each threshold + if ((memorized_level != actual_level) && + (ramp_style == 0) && + (memorized_level == nearest) + ) + { + set_level(0); + delay_4ms(8/4); + } + #endif set_level(memorized_level); return MISCHIEF_MANAGED; } @@ -473,6 +489,21 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { delay_4ms(8/4); } #endif + #if defined(BLINK_AT_STEPS) + uint8_t foo = ramp_style; + ramp_style = 1; + uint8_t nearest = nearest_level((int16_t)actual_level); + ramp_style = foo; + // only blink once for each threshold + if ((memorized_level != actual_level) && + (ramp_style == 0) && + (memorized_level == nearest) + ) + { + set_level(0); + delay_4ms(8/4); + } + #endif set_level(memorized_level); return MISCHIEF_MANAGED; } @@ -564,7 +595,8 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { uint8_t strobe_state(EventPtr event, uint16_t arg) { - // FIXME: re-order the strobes so candle and lightning are adjacent + // 'st' reduces ROM size by avoiding access to a volatile var + // (maybe I should just make it nonvolatile?) uint8_t st = strobe_type; #ifdef USE_CANDLE_MODE //#define MAX_CANDLE_LEVEL (RAMP_SIZE-8-6-4) @@ -890,6 +922,7 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { set_level(0); empty_event_sequence(); // don't attempt to parse multiple clicks //go_to_standby = 1; // sleep while light is off + // TODO: lighted button should use lockout config? return MISCHIEF_MANAGED; } @@ -1298,7 +1331,7 @@ void low_voltage() { if (state == strobe_state) { set_state(steady_state, RAMP_SIZE/6); } - // in normal or muggle mode, step down by half or turn off + // in normal or muggle mode, step down or turn off else if ((state == steady_state) || (state == muggle_state)) { if (actual_level > 1) { uint8_t lvl = (actual_level >> 1) + (actual_level >> 2); -- cgit v1.2.3 From fc04792a45b0707e90b6b6560cf30cf19b84adc1 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 23 Apr 2018 21:45:04 -0600 Subject: Added more stuff to do. --- spaghetti-monster/anduril/anduril.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index 54f8b8c..7ede740 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -105,11 +105,17 @@ Momentary mode: TODO: * save settings in eeprom - - decide on "hold until hot" or "click N times" for thermal config mode + * decide on "hold until hot" or "click N times" for thermal config mode * test thermal regulation on an actual light * improve thermal regulation - a way to blink out the firmware version? - - indicator LED support - - a way to configure indicator LED behavior? + * indicator LED support + * a way to configure indicator LED behavior? * add goodnight mode? * add lightning mode? + - muggle mode: smooth ramp + - candle mode timer, with three clicks to add 30 minutes + - candle mode: smoother adjustments? + - make sunset mode timer and brightness configurable? + - move all config menus to four clicks + - diagram updates for 3-click special actions -- cgit v1.2.3 From 81a6faed1f8ddb4ce940d16851532797e532f7f0 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 23 Apr 2018 23:31:54 -0600 Subject: Converted muggle mode to a smooth ramp style. Made it remember this in eeprom to persist across battery changes. This uses quite a bit of extra space, so I may need to refactor something else to make more room. Also fixed a bug where ramp style could get stuck at "discrete/stepped ramp" if eeprom got corrupted. --- spaghetti-monster/anduril/anduril.c | 158 ++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 32 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 6323679..2a4a2f5 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -50,6 +50,8 @@ #define USE_BATTCHECK #ifdef USE_MUGGLE_MODE #define MAX_CLICKS 6 +#define MUGGLE_FLOOR 22 +#define MUGGLE_CEILING (MAX_1x7135+20) #else #define MAX_CLICKS 5 #endif @@ -70,11 +72,11 @@ // FIXME: detect this better, and assign offsets better, for various configs #define USE_EEPROM #ifdef USE_INDICATOR_LED -#define EEPROM_BYTES 14 +#define EEPROM_BYTES 15 #elif defined(USE_THERMAL_REGULATION) -#define EEPROM_BYTES 13 +#define EEPROM_BYTES 14 #else -#define EEPROM_BYTES 11 +#define EEPROM_BYTES 12 #endif #ifdef START_AT_MEMORIZED_LEVEL #define USE_EEPROM_WL @@ -137,6 +139,7 @@ uint8_t momentary_state(EventPtr event, uint16_t arg); #ifdef USE_MUGGLE_MODE // muggle mode, super-simple, hard to exit uint8_t muggle_state(EventPtr event, uint16_t arg); +uint8_t muggle_mode_active = 0; #endif // general helper function for config modes @@ -365,7 +368,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 3 clicks: toggle smooth vs discrete ramping else if (event == EV_3clicks) { - ramp_style ^= 1; + ramp_style = !ramp_style; memorized_level = nearest_level(memorized_level); #ifdef USE_THERMAL_REGULATION target_level = memorized_level; @@ -944,37 +947,118 @@ uint8_t momentary_state(EventPtr event, uint16_t arg) { #ifdef USE_MUGGLE_MODE uint8_t muggle_state(EventPtr event, uint16_t arg) { - //static uint8_t lvl = 0; - //uint8_t lvls[] = {MAX_1x7135/2, MAX_1x7135, MAX_1x7135+10}; + static int8_t ramp_direction; + static int8_t muggle_off_mode; - if (event == EV_click1_press) { - /* - if (actual_level == 0) { - set_level(lvls[lvl]); - lvl = (lvl + 1) % sizeof(lvls); + // turn LED off when we first enter the mode + if (event == EV_enter_state) { + muggle_mode_active = 1; + save_config(); + + muggle_off_mode = 1; + ramp_direction = 1; + //memorized_level = MAX_1x7135; + memorized_level = (MUGGLE_FLOOR + MUGGLE_CEILING) / 2; + return MISCHIEF_MANAGED; + } + // initial press: moon hint + else if (event == EV_click1_press) { + if (muggle_off_mode) + set_level(MUGGLE_FLOOR); + } + // initial release: direct to memorized level + else if (event == EV_click1_release) { + if (muggle_off_mode) + set_level(memorized_level); + } + // if the user keeps pressing, turn off + else if (event == EV_click2_press) { + muggle_off_mode = 1; + set_level(0); + } + // 1 click: on/off + else if (event == EV_1click) { + muggle_off_mode ^= 1; + if (muggle_off_mode) { + set_level(0); } - else { // turn off + /* + else { + set_level(memorized_level); } */ - if (actual_level == 0) { - set_level(MAX_1x7135); + return MISCHIEF_MANAGED; + } + // hold: change brightness + else if (event == EV_click1_hold) { + // ramp at half speed + if (arg & 1) return MISCHIEF_MANAGED; + + // if off, start at bottom + if (muggle_off_mode) { + muggle_off_mode = 0; + ramp_direction = 1; + set_level(MUGGLE_FLOOR); } - else { // turn off - set_level(0); + else { + uint8_t m; + m = actual_level; + // ramp down if already at ceiling + if ((arg <= 1) && (m >= MUGGLE_CEILING)) ramp_direction = -1; + // ramp + m += ramp_direction; + if (m < MUGGLE_FLOOR) + m = MUGGLE_FLOOR; + if (m > MUGGLE_CEILING) + m = MUGGLE_CEILING; + memorized_level = m; + set_level(m); } - empty_event_sequence(); return MISCHIEF_MANAGED; } - - else if (event == EV_release) { - empty_event_sequence(); // don't attempt to parse multiple clicks + // reverse ramp direction on hold release + else if (event == EV_click1_hold_release) { + ramp_direction = -ramp_direction; + return MISCHIEF_MANAGED; + } + /* + // click, hold: change brightness (dimmer) + else if (event == EV_click2_hold) { + ramp_direction = 1; + if (memorized_level > MUGGLE_FLOOR) + memorized_level = actual_level - 1; + set_level(memorized_level); + return MISCHIEF_MANAGED; + } + */ + // 6 clicks: exit muggle mode + else if (event == EV_6clicks) { + blink_confirm(1); + muggle_mode_active = 0; + save_config(); + set_state(off_state, 0); return MISCHIEF_MANAGED; } + // tick: housekeeping + else if (event == EV_tick) { + // un-reverse after 1 second + if (arg == TICKS_PER_SECOND) ramp_direction = 1; - // Sleep, dammit! - else if ((event == EV_tick) && (actual_level == 0)) { - if (arg > TICKS_PER_SECOND*1) { // sleep after 1 second - go_to_standby = 1; // sleep while light is off + // turn off, but don't go to the main "off" state + if (muggle_off_mode) { + if (arg > TICKS_PER_SECOND*1) { // sleep after 1 second + go_to_standby = 1; // sleep while light is off + } + } + return MISCHIEF_MANAGED; + } + // low voltage is handled specially in muggle mode + else if(event == EV_voltage_low) { + uint8_t lvl = (actual_level >> 1) + (actual_level >> 2); + if (lvl >= MUGGLE_FLOOR) { + set_level(lvl); + } else { + muggle_off_mode = 1; } return MISCHIEF_MANAGED; } @@ -1279,12 +1363,15 @@ void load_config() { strobe_delays[1] = eeprom[8]; bike_flasher_brightness = eeprom[9]; beacon_seconds = eeprom[10]; + #ifdef USE_MUGGLE_MODE + muggle_mode_active = eeprom[11]; + #endif #ifdef USE_THERMAL_REGULATION - therm_ceil = eeprom[11]; - therm_cal_offset = eeprom[12]; + therm_ceil = eeprom[12]; + therm_cal_offset = eeprom[13]; #endif #ifdef USE_INDICATOR_LED - indicator_led_mode = eeprom[13]; + indicator_led_mode = eeprom[14]; #endif } #ifdef START_AT_MEMORIZED_LEVEL @@ -1306,12 +1393,15 @@ void save_config() { eeprom[8] = strobe_delays[1]; eeprom[9] = bike_flasher_brightness; eeprom[10] = beacon_seconds; + #ifdef USE_MUGGLE_MODE + eeprom[11] = muggle_mode_active; + #endif #ifdef USE_THERMAL_REGULATION - eeprom[11] = therm_ceil; - eeprom[12] = therm_cal_offset; + eeprom[12] = therm_ceil; + eeprom[13] = therm_cal_offset; #endif #ifdef USE_INDICATOR_LED - eeprom[13] = indicator_led_mode; + eeprom[14] = indicator_led_mode; #endif save_eeprom(); @@ -1332,7 +1422,8 @@ void low_voltage() { set_state(steady_state, RAMP_SIZE/6); } // in normal or muggle mode, step down or turn off - else if ((state == steady_state) || (state == muggle_state)) { + //else if ((state == steady_state) || (state == muggle_state)) { + else if (state == steady_state) { if (actual_level > 1) { uint8_t lvl = (actual_level >> 1) + (actual_level >> 2); set_level(lvl); @@ -1376,7 +1467,10 @@ void setup() { load_config(); - push_state(off_state, 0); + if (muggle_mode_active) + push_state(muggle_state, 0); + else + push_state(off_state, 0); #endif } -- cgit v1.2.3 From e0f9fe6a2eae4a8ee803f4913ce6a5e595129235 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 24 Apr 2018 01:05:53 -0600 Subject: First pass at refactoring config modes to save space. Also, moved all config menus to 4 clicks. --- spaghetti-monster/anduril/anduril.c | 177 ++++++++++++++++++---------------- spaghetti-monster/anduril/anduril.txt | 5 +- 2 files changed, 95 insertions(+), 87 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 2a4a2f5..aea2a50 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -114,6 +114,11 @@ // FSM states uint8_t off_state(EventPtr event, uint16_t arg); +// simple numeric entry config menu +uint8_t config_state_base(EventPtr event, uint16_t arg); +#define MAX_CONFIG_VALUES 3 +uint8_t config_state_values[MAX_CONFIG_VALUES]; +volatile uint8_t config_state_done = 0; // ramping mode and its related config mode uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t ramp_config_state(EventPtr event, uint16_t arg); @@ -760,8 +765,8 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg) { set_state(battcheck_state, 0); return MISCHIEF_MANAGED; } - // 3 clicks: thermal config mode - else if (event == EV_3clicks) { + // 4 clicks: thermal config mode + else if (event == EV_4clicks) { set_state(thermal_config_state, 0); return MISCHIEF_MANAGED; } @@ -785,8 +790,8 @@ uint8_t beacon_state(EventPtr event, uint16_t arg) { #endif return MISCHIEF_MANAGED; } - // 3 clicks: beacon config mode - else if (event == EV_3clicks) { + // 4 clicks: beacon config mode + else if (event == EV_4clicks) { set_state(beacon_config_state, 0); return MISCHIEF_MANAGED; } @@ -1068,17 +1073,13 @@ uint8_t muggle_state(EventPtr event, uint16_t arg) { #endif -uint8_t ramp_config_state(EventPtr event, uint16_t arg) { +uint8_t config_state_base(EventPtr event, uint16_t arg) { static uint8_t config_step; static uint8_t num_config_steps; if (event == EV_enter_state) { + config_state_done = 0; config_step = 0; - if (ramp_style) { - num_config_steps = 3; - } - else { - num_config_steps = 2; - } + num_config_steps = arg; set_level(0); return MISCHIEF_MANAGED; } @@ -1088,87 +1089,95 @@ uint8_t ramp_config_state(EventPtr event, uint16_t arg) { push_state(number_entry_state, config_step + 1); } else { - save_config(); + config_state_done = 1; // TODO: blink out some sort of success pattern - // return to steady mode - set_state(steady_state, memorized_level); } return MISCHIEF_MANAGED; } // an option was set (return from number_entry_state) else if (event == EV_reenter_state) { - #if 0 - // FIXME? this is a kludge which relies on the vars being consecutive - // in RAM and in the same order as declared - // ... and it doesn't work; it seems they're not consecutive :( - volatile uint8_t *dest; - if (ramp_style) dest = (&ramp_discrete_floor) + config_step; - else dest = (&ramp_smooth_floor) + config_step; - if (number_entry_value) - *dest = number_entry_value; - #else - switch (config_step) { - case 0: - if (number_entry_value) { - if (ramp_style) ramp_discrete_floor = number_entry_value; - else ramp_smooth_floor = number_entry_value; - } - break; - case 1: - if (number_entry_value) { - if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value; - else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value; - } - break; - case 2: - if (number_entry_value) - ramp_discrete_steps = number_entry_value; - break; - } - #endif + config_state_values[config_step] = number_entry_value; config_step ++; return MISCHIEF_MANAGED; } + //return EVENT_NOT_HANDLED; + // eat all other events; don't pass any through to parent + return EVENT_HANDLED; +} + +uint8_t ramp_config_state(EventPtr event, uint16_t arg) { + if (event == EV_enter_state) { + uint8_t num_config_steps; + num_config_steps = 2 + ramp_style; + return config_state_base(event, num_config_steps); + } + // let generic function handle everything except completion + else if (!config_state_done) { + return config_state_base(event, arg); + } + else if (event == EV_tick) { + // parse values + uint8_t val; + if (ramp_style) { // discrete / stepped ramp + + val = config_state_values[0]; + if (val) { ramp_discrete_floor = val; } + + val = config_state_values[1]; + if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; } + + val = config_state_values[2]; + if (val) ramp_discrete_steps = val; + + } else { // smooth ramp + + val = config_state_values[0]; + if (val) { ramp_smooth_floor = val; } + + val = config_state_values[1]; + if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; } + + } + + // save values and return + save_config(); + set_state(steady_state, memorized_level); + return MISCHIEF_MANAGED; + } return EVENT_NOT_HANDLED; } #ifdef USE_THERMAL_REGULATION uint8_t thermal_config_state(EventPtr event, uint16_t arg) { - static uint8_t config_step; if (event == EV_enter_state) { - set_level(0); - config_step = 0; - return MISCHIEF_MANAGED; + return config_state_base(event, 2); + } + // let generic function handle everything except completion + else if (!config_state_done) { + return config_state_base(event, arg); } - // advance forward through config steps else if (event == EV_tick) { - // ask the user for a number - if (config_step < 2) { - push_state(number_entry_state, config_step + 1); + // parse values + uint8_t val; + + // calibrate room temperature + val = config_state_values[0]; + if (val) { + int8_t rawtemp = (temperature >> 1) - therm_cal_offset; + therm_cal_offset = val - rawtemp; } - // return to original mode - else { - save_config(); - set_state(tempcheck_state, 0); - } - return MISCHIEF_MANAGED; - } - // an option was set (return from number_entry_state) - else if (event == EV_reenter_state) { - if (number_entry_value) { - // calibrate room temperature - if (config_step == 0) { - int8_t rawtemp = (temperature >> 1) - therm_cal_offset; - therm_cal_offset = number_entry_value - rawtemp; - } + + val = config_state_values[1]; + if (val) { // set maximum heat limit - else { - therm_ceil = 30 + number_entry_value; - } + therm_ceil = 30 + val; } if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; - config_step ++; + + // save values and return + save_config(); + set_state(tempcheck_state, 0); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -1177,25 +1186,23 @@ uint8_t thermal_config_state(EventPtr event, uint16_t arg) { uint8_t beacon_config_state(EventPtr event, uint16_t arg) { - static uint8_t done = 0; if (event == EV_enter_state) { - set_level(0); - done = 0; - return MISCHIEF_MANAGED; + return config_state_base(event, 1); } - // advance forward through config steps - else if (event == EV_tick) { - // ask the user for a number - if (! done) push_state(number_entry_state, 0); - // return to original mode - else set_state(beacon_state, 0); - return MISCHIEF_MANAGED; + // let generic function handle everything except completion + else if (!config_state_done) { + return config_state_base(event, arg); } - // an option was set (return from number_entry_state) - else if (event == EV_reenter_state) { - if (number_entry_value) beacon_seconds = number_entry_value; + else if (event == EV_tick) { + // parse values + uint8_t val = config_state_values[0]; + if (val) { + beacon_seconds = val; + } + + // save values and return save_config(); - done = 1; + set_state(beacon_state, 0); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index 7ede740..6d27151 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -113,9 +113,10 @@ TODO: * a way to configure indicator LED behavior? * add goodnight mode? * add lightning mode? - - muggle mode: smooth ramp + * muggle mode: smooth ramp + + refactor to make config modes smaller - candle mode timer, with three clicks to add 30 minutes - candle mode: smoother adjustments? - make sunset mode timer and brightness configurable? - - move all config menus to four clicks + * move all config menus to four clicks - diagram updates for 3-click special actions -- cgit v1.2.3 From d0d3ef50905cfdee2564c1e4ec6db3766510cf15 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 24 Apr 2018 01:40:25 -0600 Subject: Second pass at making config modes smaller. Seems much better now. --- spaghetti-monster/anduril/anduril.c | 159 +++++++++++++++------------------- spaghetti-monster/anduril/anduril.txt | 2 +- 2 files changed, 69 insertions(+), 92 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index aea2a50..be9404d 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -115,10 +115,11 @@ // FSM states uint8_t off_state(EventPtr event, uint16_t arg); // simple numeric entry config menu -uint8_t config_state_base(EventPtr event, uint16_t arg); +uint8_t config_state_base(EventPtr event, uint16_t arg, + uint8_t num_config_steps, + void (*savefunc)()); #define MAX_CONFIG_VALUES 3 uint8_t config_state_values[MAX_CONFIG_VALUES]; -volatile uint8_t config_state_done = 0; // ramping mode and its related config mode uint8_t steady_state(EventPtr event, uint16_t arg); uint8_t ramp_config_state(EventPtr event, uint16_t arg); @@ -335,7 +336,11 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // turn LED on when we first enter the mode - if (event == EV_enter_state) { + if ((event == EV_enter_state) || (event == EV_reenter_state)) { + // if we just got back from config mode, go back to memorized level + if (event == EV_reenter_state) { + arg = memorized_level; + } // remember this level, unless it's moon or turbo if ((arg > mode_min) && (arg < mode_max)) memorized_level = arg; @@ -392,7 +397,7 @@ uint8_t steady_state(EventPtr event, uint16_t arg) { } // 4 clicks: configure this ramp mode else if (event == EV_4clicks) { - set_state(ramp_config_state, 0); + push_state(ramp_config_state, 0); return MISCHIEF_MANAGED; } // hold: change brightness (brighter) @@ -767,7 +772,7 @@ uint8_t tempcheck_state(EventPtr event, uint16_t arg) { } // 4 clicks: thermal config mode else if (event == EV_4clicks) { - set_state(thermal_config_state, 0); + push_state(thermal_config_state, 0); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -792,7 +797,7 @@ uint8_t beacon_state(EventPtr event, uint16_t arg) { } // 4 clicks: beacon config mode else if (event == EV_4clicks) { - set_state(beacon_config_state, 0); + push_state(beacon_config_state, 0); return MISCHIEF_MANAGED; } return EVENT_NOT_HANDLED; @@ -1073,13 +1078,13 @@ uint8_t muggle_state(EventPtr event, uint16_t arg) { #endif -uint8_t config_state_base(EventPtr event, uint16_t arg) { +// ask the user for a sequence of numbers, then save them and return to caller +uint8_t config_state_base(EventPtr event, uint16_t arg, + uint8_t num_config_steps, + void (*savefunc)()) { static uint8_t config_step; - static uint8_t num_config_steps; if (event == EV_enter_state) { - config_state_done = 0; config_step = 0; - num_config_steps = arg; set_level(0); return MISCHIEF_MANAGED; } @@ -1089,8 +1094,10 @@ uint8_t config_state_base(EventPtr event, uint16_t arg) { push_state(number_entry_state, config_step + 1); } else { - config_state_done = 1; // TODO: blink out some sort of success pattern + savefunc(); + //set_state(retstate, retval); + pop_state(); } return MISCHIEF_MANAGED; } @@ -1105,107 +1112,77 @@ uint8_t config_state_base(EventPtr event, uint16_t arg) { return EVENT_HANDLED; } -uint8_t ramp_config_state(EventPtr event, uint16_t arg) { - if (event == EV_enter_state) { - uint8_t num_config_steps; - num_config_steps = 2 + ramp_style; - return config_state_base(event, num_config_steps); - } - // let generic function handle everything except completion - else if (!config_state_done) { - return config_state_base(event, arg); - } - else if (event == EV_tick) { - // parse values - uint8_t val; - if (ramp_style) { // discrete / stepped ramp - - val = config_state_values[0]; - if (val) { ramp_discrete_floor = val; } +void ramp_config_save() { + // parse values + uint8_t val; + if (ramp_style) { // discrete / stepped ramp - val = config_state_values[1]; - if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; } + val = config_state_values[0]; + if (val) { ramp_discrete_floor = val; } - val = config_state_values[2]; - if (val) ramp_discrete_steps = val; + val = config_state_values[1]; + if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; } - } else { // smooth ramp + val = config_state_values[2]; + if (val) ramp_discrete_steps = val; - val = config_state_values[0]; - if (val) { ramp_smooth_floor = val; } + } else { // smooth ramp - val = config_state_values[1]; - if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; } + val = config_state_values[0]; + if (val) { ramp_smooth_floor = val; } - } + val = config_state_values[1]; + if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; } - // save values and return - save_config(); - set_state(steady_state, memorized_level); - return MISCHIEF_MANAGED; } - return EVENT_NOT_HANDLED; } +uint8_t ramp_config_state(EventPtr event, uint16_t arg) { + uint8_t num_config_steps; + num_config_steps = 2 + ramp_style; + return config_state_base(event, arg, + num_config_steps, ramp_config_save); +} -#ifdef USE_THERMAL_REGULATION -uint8_t thermal_config_state(EventPtr event, uint16_t arg) { - if (event == EV_enter_state) { - return config_state_base(event, 2); - } - // let generic function handle everything except completion - else if (!config_state_done) { - return config_state_base(event, arg); - } - else if (event == EV_tick) { - // parse values - uint8_t val; - // calibrate room temperature - val = config_state_values[0]; - if (val) { - int8_t rawtemp = (temperature >> 1) - therm_cal_offset; - therm_cal_offset = val - rawtemp; - } +#ifdef USE_THERMAL_REGULATION +void thermal_config_save() { + // parse values + uint8_t val; - val = config_state_values[1]; - if (val) { - // set maximum heat limit - therm_ceil = 30 + val; - } - if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; + // calibrate room temperature + val = config_state_values[0]; + if (val) { + int8_t rawtemp = (temperature >> 1) - therm_cal_offset; + therm_cal_offset = val - rawtemp; + } - // save values and return - save_config(); - set_state(tempcheck_state, 0); - return MISCHIEF_MANAGED; + val = config_state_values[1]; + if (val) { + // set maximum heat limit + therm_ceil = 30 + val; } - return EVENT_NOT_HANDLED; + if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL; +} + +uint8_t thermal_config_state(EventPtr event, uint16_t arg) { + return config_state_base(event, arg, + 2, thermal_config_save); } #endif -uint8_t beacon_config_state(EventPtr event, uint16_t arg) { - if (event == EV_enter_state) { - return config_state_base(event, 1); - } - // let generic function handle everything except completion - else if (!config_state_done) { - return config_state_base(event, arg); +void beacon_config_save() { + // parse values + uint8_t val = config_state_values[0]; + if (val) { + beacon_seconds = val; } - else if (event == EV_tick) { - // parse values - uint8_t val = config_state_values[0]; - if (val) { - beacon_seconds = val; - } +} - // save values and return - save_config(); - set_state(beacon_state, 0); - return MISCHIEF_MANAGED; - } - return EVENT_NOT_HANDLED; +uint8_t beacon_config_state(EventPtr event, uint16_t arg) { + return config_state_base(event, arg, + 1, beacon_config_save); } diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index 6d27151..7efbf04 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -114,7 +114,7 @@ TODO: * add goodnight mode? * add lightning mode? * muggle mode: smooth ramp - + refactor to make config modes smaller + * refactor to make config modes smaller - candle mode timer, with three clicks to add 30 minutes - candle mode: smoother adjustments? - make sunset mode timer and brightness configurable? -- cgit v1.2.3 From 83be9c40b0a8919dfaba14024943a28fcec672bc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 16:41:20 -0600 Subject: updated Anduril diagram based on recent UI changes --- spaghetti-monster/anduril/anduril-ui.png | Bin 237749 -> 238915 bytes spaghetti-monster/anduril/anduril.svg | 288 ++++++++++++++++++++++++------- 2 files changed, 223 insertions(+), 65 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril-ui.png b/spaghetti-monster/anduril/anduril-ui.png index 2776696..191dbd1 100644 Binary files a/spaghetti-monster/anduril/anduril-ui.png and b/spaghetti-monster/anduril/anduril-ui.png differ diff --git a/spaghetti-monster/anduril/anduril.svg b/spaghetti-monster/anduril/anduril.svg index b72e2f8..ff9878a 100644 --- a/spaghetti-monster/anduril/anduril.svg +++ b/spaghetti-monster/anduril/anduril.svg @@ -1737,22 +1737,6 @@ style="fill-rule:evenodd;stroke:#000000;stroke-width:0.42666668pt" inkscape:connector-curvature="0" /> - - - + + + + + + + + + + + + + + + + + + + + + - + + 4 Clicks + + + + 4 Clicks + + Click, Hold - 4 Clicks - Smooth / Stepped - Smooth /Stepped + + + Smooth + + + + 4 Clicks + + + + + Stepped + + -- cgit v1.2.3 From a93c243386fe48595a981e0fb8bca4f6b3d518a8 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 16:49:13 -0600 Subject: Added missing ifdef for muggle mode behavior at boot. --- spaghetti-monster/anduril/anduril.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index be9404d..8e298b9 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -1451,9 +1451,11 @@ void setup() { load_config(); + #ifdef USE_MUGGLE_MODE if (muggle_mode_active) push_state(muggle_state, 0); else + #endif push_state(off_state, 0); #endif -- cgit v1.2.3 From d030518a314e838a0386182fbdfb65b56322cb6f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 19:52:39 -0600 Subject: Use separate voltage adjustment value for pin7 readings. --- spaghetti-monster/fsm-adc.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h index 1b16d01..6256e2c 100644 --- a/spaghetti-monster/fsm-adc.h +++ b/spaghetti-monster/fsm-adc.h @@ -32,8 +32,12 @@ #endif // MCU sees voltage 0.X volts lower than actual, add X/2 to readings #ifndef VOLTAGE_FUDGE_FACTOR +#ifdef USE_VOLTAGE_DIVIDER +#define VOLTAGE_FUDGE_FACTOR 0 +#else #define VOLTAGE_FUDGE_FACTOR 5 #endif +#endif volatile uint8_t voltage; volatile uint8_t adcint_enable; // kludge, because adc auto-retrigger won't turn off void low_voltage(); -- cgit v1.2.3 From 850b0d57143b0d052c61abb237e4ca9fd9f02f3b Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 19:53:31 -0600 Subject: Added BLF GT 150-level ramp. Is a bit of a mess though. --- spaghetti-monster/fsm-ramping.h | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index 1657e4a..86742f6 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -60,17 +60,32 @@ void gradual_tick(); PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,5,7,10,13,16,19,23,26,30,34,38,42,47,51,56,61,66,72,77,83,89,95,101,108,115,122,129,136,144,152,160,168,177,186,195,204,214,224,234,244,255 }; #define MAX_1x7135 33 #elif RAMP_LENGTH == 150 - // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 - //PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,30,32,34,37,40,43,46,49,52,56,59,63,67,71,76,80,85,90,95,100,106,112,118,124,130,137,144,151,158,166,173,181,190,198,207,216,225,235,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; - //PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; - // ../../bin/level_calc.py 1 65 7135 1 0.8 150 - // ... mixed with this: - // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 - PROGMEM const uint8_t pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; - PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; - #define MAX_1x7135 65 - #define HALFSPEED_LEVEL 14 - #define QUARTERSPEED_LEVEL 5 + #ifdef FSM_BLF_GT_DRIVER + // First 60 values: level_calc.py 1 60 7135 4 5.0 255 + // Remainder: all 255 (buck driver at 100% duty cycle) + PROGMEM const uint8_t pwm1_levels[] = { 4,5,6,6,7,8,9,11,12,13,15,16,18,19,21,23,25,27,30,32,34,37,40,43,46,49,52,55,59,63,66,70,75,79,83,88,93,98,103,108,114,119,125,131,137,144,150,157,164,171,179,186,194,202,210,219,228,236,246,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 }; + // First 60 values: all 25 (buck driver at 10% power) + // Remainder: values 61-150 of level_calc.py 1 150 7135 1 3 3000 + PROGMEM const uint8_t pwm2_levels[] = { 25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25, + 26,27,28,29,30,31,32,33,35,36,37,38,40,41,42,44,45,47,48,50,51,53,54,56,58,59,61,63,65,67,69,70,72,74,76,79,81,83,85,87,89,92,94,96,99,101,104,106,109,112,114,117,120,123,125,128,131,134,137,140,143,147,150,153,156,160,163,167,170,174,177,181,184,188,192,196,200,204,208,212,216,220,224,228,233,237,241,246,250,255 }; + #define POWER_80PX 138 // 2.0 Amps out of maximum 2.5 Amps + #define MAX_1x7135 60 // where it switches from PWM to current control + #define HALFSPEED_LEVEL 17 + #define QUARTERSPEED_LEVEL 6 + #else + // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 + //PROGMEM const uint8_t pwm1_levels[] = { 4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,30,32,34,37,40,43,46,49,52,56,59,63,67,71,76,80,85,90,95,100,106,112,118,124,130,137,144,151,158,166,173,181,190,198,207,216,225,235,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + //PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; + // ../../bin/level_calc.py 1 65 7135 1 0.8 150 + // ... mixed with this: + // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500 + PROGMEM const uint8_t pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 }; + PROGMEM const uint8_t pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 }; + #define MAX_1x7135 65 + #define HALFSPEED_LEVEL 14 + #define QUARTERSPEED_LEVEL 5 + #endif #endif #elif PWM_CHANNELS == 3 #if RAMP_LENGTH == 50 -- cgit v1.2.3 From f62a4852be155cfb96231ee3272e082080895bf9 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 19:54:19 -0600 Subject: Made voltage ADC readings work on pin7 if USE_VOLTAGE_DIVIDER is defined. --- spaghetti-monster/fsm-adc.c | 48 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index c6f5d2a..6e9f19b 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -20,13 +20,25 @@ #ifndef FSM_ADC_C #define FSM_ADC_C +#ifdef USE_VOLTAGE_DIVIDER +// 1.1V / pin7 +#define ADMUX_VOLTAGE ADMUX_VOLTAGE_DIVIDER +#else +// VCC / 1.1V reference +#define ADMUX_VOLTAGE ADMUX_VCC +#endif + inline void ADC_on() { // read voltage on VCC by default + ADMUX = ADMUX_VOLTAGE; + #ifdef USE_VOLTAGE_DIVIDER + // disable digital input on divider pin to reduce power consumption + DIDR0 |= (1 << VOLTAGE_ADC_DIDR); + #else // 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; + #endif // enable, start, prescale ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL; } @@ -35,6 +47,16 @@ inline void ADC_off() { ADCSRA &= ~(1<> 2; + #ifdef USE_VOLTAGE_DIVIDER + voltage = calc_voltage_divider(total); + #else voltage = (uint16_t)(1.1*1024*10)/total + VOLTAGE_FUDGE_FACTOR; + #endif } #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; - voltage = ((uint16_t)(2*1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR) >> 1; + #ifdef USE_VOLTAGE_DIVIDER + voltage = calc_voltage_divider(measurement); + #else + // 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; + voltage = ((uint16_t)(2*1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR) >> 1; + #endif #endif // if low, callback EV_voltage_low / EV_voltage_critical // (but only if it has been more than N ticks since last call) @@ -287,14 +317,14 @@ ISR(ADC_vect) { // set the correct type of measurement for next time #ifdef USE_THERMAL_REGULATION #ifdef USE_LVP - if (adc_step < 2) ADMUX = ADMUX_VCC; + if (adc_step < 2) ADMUX = ADMUX_VOLTAGE; else ADMUX = ADMUX_THERM; #else ADMUX = ADMUX_THERM; #endif #else #ifdef USE_LVP - ADMUX = ADMUX_VCC; + ADMUX = ADMUX_VOLTAGE; #endif #endif } -- cgit v1.2.3 From 6a3a10faa447b0d9f53d2fe2e38fccab6d7cc439 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 1 May 2018 19:54:43 -0600 Subject: Added BLF GT support to Anduril. --- spaghetti-monster/anduril/anduril.c | 52 ++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 10 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 8e298b9..36d29ac 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -23,6 +23,7 @@ #define FSM_EMISAR_D4_DRIVER //#define FSM_BLF_Q8_DRIVER //#define FSM_FW3A_DRIVER +//#define FSM_BLF_GT_DRIVER #define USE_LVP #define USE_THERMAL_REGULATION @@ -62,10 +63,25 @@ #ifdef FSM_BLF_Q8_DRIVER #define USE_INDICATOR_LED #define VOLTAGE_FUDGE_FACTOR 7 // add 0.35V + #elif defined(FSM_EMISAR_D4_DRIVER) #define VOLTAGE_FUDGE_FACTOR 5 // add 0.25V + #elif defined(FSM_FW3A_DRIVER) #define VOLTAGE_FUDGE_FACTOR 5 // add 0.25V + +#elif defined(FSM_BLF_GT_DRIVER) +#define USE_INDICATOR_LED +#undef BLINK_AT_CHANNEL_BOUNDARIES +#undef BLINK_AT_RAMP_CEILING +#undef BLINK_AT_RAMP_FLOOR +//#undef USE_SET_LEVEL_GRADUALLY +#define RAMP_SMOOTH_FLOOR 1 +#define RAMP_SMOOTH_CEIL POWER_80PX +#define RAMP_DISCRETE_FLOOR 1 +#define RAMP_DISCRETE_CEIL POWER_80PX +#define RAMP_DISCRETE_STEPS 7 + #endif // try to auto-detect how many eeprom bytes @@ -162,20 +178,36 @@ void save_config(); void save_config_wl(); #endif +// default ramp options if not overridden earlier per-driver +#ifndef RAMP_SMOOTH_FLOOR + #define RAMP_SMOOTH_FLOOR 1 +#endif +#ifndef RAMP_SMOOTH_CEIL + #if PWM_CHANNELS == 3 + #define RAMP_SMOOTH_CEIL MAX_Nx7135 + #else + #define RAMP_SMOOTH_CEIL MAX_LEVEL - 30 + #endif +#endif +#ifndef RAMP_DISCRETE_FLOOR + #define RAMP_DISCRETE_FLOOR 20 +#endif +#ifndef RAMP_DISCRETE_CEIL + #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL +#endif +#ifndef RAMP_DISCRETE_STEPS + #define RAMP_DISCRETE_STEPS 7 +#endif + // brightness control uint8_t memorized_level = MAX_1x7135; // smooth vs discrete ramping volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete -volatile uint8_t ramp_smooth_floor = 1; -#if PWM_CHANNELS == 3 -volatile uint8_t ramp_smooth_ceil = MAX_Nx7135; -volatile uint8_t ramp_discrete_ceil = MAX_Nx7135; -#else -volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30; -volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30; -#endif -volatile uint8_t ramp_discrete_floor = 20; -volatile uint8_t ramp_discrete_steps = 7; +volatile uint8_t ramp_smooth_floor = RAMP_SMOOTH_FLOOR; +volatile uint8_t ramp_smooth_ceil = RAMP_SMOOTH_CEIL; +volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR; +volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL; +volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS; uint8_t ramp_discrete_step_size; // don't set this #ifdef USE_INDICATOR_LED -- cgit v1.2.3 From dc7d6b3724ca8e153a035eacddb95541986e39d5 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sun, 6 May 2018 17:51:39 -0600 Subject: Added "votive candle mode", a.k.a. candle mode timer. Off by default, but click three times to add 30m to the timer. Maximum timer length about 4.5 hours. --- spaghetti-monster/anduril/anduril-ui.png | Bin 238915 -> 244079 bytes spaghetti-monster/anduril/anduril.c | 48 +++++++++- spaghetti-monster/anduril/anduril.svg | 157 ++++++++++++++++++++++++++++++- spaghetti-monster/anduril/anduril.txt | 126 ++++++++++++++----------- 4 files changed, 275 insertions(+), 56 deletions(-) (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril-ui.png b/spaghetti-monster/anduril/anduril-ui.png index 191dbd1..b778a30 100644 Binary files a/spaghetti-monster/anduril/anduril-ui.png and b/spaghetti-monster/anduril/anduril-ui.png differ diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 36d29ac..26db6d8 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -653,9 +653,15 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { static uint8_t candle_wave2_depth = 7; static uint8_t candle_wave3_depth = 4; static uint8_t candle_mode_brightness = 24; + static uint8_t candle_mode_timer = 0; + #define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds + #define MINUTES_PER_CANDLE_HALFHOUR 27 // ish #endif if (event == EV_enter_state) { + #ifdef USE_CANDLE_MODE + candle_mode_timer = 0; // in case any time was left over from earlier + #endif return MISCHIEF_MANAGED; } // 1 click: off @@ -666,6 +672,9 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { // 2 clicks: rotate through strobe/flasher modes else if (event == EV_2clicks) { strobe_type = (st + 1) % NUM_STROBES; + #ifdef USE_CANDLE_MODE + candle_mode_timer = 0; // in case any time was left over from earlier + #endif interrupt_nice_delays(); save_config(); return MISCHIEF_MANAGED; @@ -728,6 +737,22 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { save_config(); return MISCHIEF_MANAGED; } + #if defined(USE_CANDLE_MODE) + // 3 clicks: add 30m to candle timer + else if (event == EV_3clicks) { + // candle mode only + if (st == 4) { + if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) { + // add 30m to the timer + candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR; + // blink to confirm + set_level(actual_level + 32); + delay_4ms(2); + } + } + return MISCHIEF_MANAGED; + } + #endif #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE) // clock tick: bump the random seed else if (event == EV_tick) { @@ -736,12 +761,33 @@ uint8_t strobe_state(EventPtr event, uint16_t arg) { #endif #ifdef USE_CANDLE_MODE if (st == 4) { + // self-timer dims the light during the final minute + uint8_t subtract = 0; + if (candle_mode_timer == 1) { + subtract = ((candle_mode_brightness+20) + * ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4)) + >> 8; + } + // we passed a minute mark, decrease timer if it's running + if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) { + if (candle_mode_timer > 0) { + candle_mode_timer --; + //set_level(0); delay_4ms(2); + // if the timer ran out, shut off + if (! candle_mode_timer) { + set_state(off_state, 0); + } + } + } // 3-oscillator synth for a relatively organic pattern uint8_t add; add = ((triangle_wave(candle_wave1) * 8) >> 8) + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8) + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8); - set_level(candle_mode_brightness + add); + int8_t brightness = candle_mode_brightness + add - subtract; + if (brightness < 0) { brightness = 0; } + set_level(brightness); + // wave1: slow random LFO if ((arg & 1) == 0) candle_wave1 += pseudo_rand()&1; // wave2: medium-speed erratic LFO diff --git a/spaghetti-monster/anduril/anduril.svg b/spaghetti-monster/anduril/anduril.svg index ff9878a..9fbb7d1 100644 --- a/spaghetti-monster/anduril/anduril.svg +++ b/spaghetti-monster/anduril/anduril.svg @@ -22,6 +22,30 @@ inkscape:export-ydpi="114.26005"> + + + + + + + + + + + + + + + + +30 min + + + + + + + + + + + + + diff --git a/spaghetti-monster/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt index 7efbf04..531609d 100644 --- a/spaghetti-monster/anduril/anduril.txt +++ b/spaghetti-monster/anduril/anduril.txt @@ -16,6 +16,7 @@ From off: Other: * 4 clicks: lock-out * 5 clicks: momentary mode (disconnect power to exit) + * 6 clicks: muggle mode In steady mode: * 1 click: off @@ -42,57 +43,69 @@ Discrete ramp config mode: (click N times to make discrete mode have N stair-steps) (minimum 2, maximum 150) -Bike flasher: - * 1 click: off - * 2 clicks: party strobe - * Hold: brighter - * Click, hold: dimmer - -Party / Tactical strobe modes: - * 1 click: off - * Hold: change speed (faster) - * Click, hold: change speed (slower) - * 2 clicks: next strobe mode - (bike flasher, party strobe, tactical strobe, lightning storm mode) - (TODO: random/police strobe?) - -Lightning storm mode: - * 1 click: off - * 2 clicks: bike flasher - -Battcheck mode: - * 1 click: off - * 2 clicks: goodnight mode - -Goodnight mode: - * 1 click: off - * 2 clicks: beacon mode - -Beacon mode: - * 1 click: off - * 2 clicks: tempcheck mode - * 3 clicks: configure time between pulses - -Beacon config mode: - * At buzz, click N times to set beacon frequency to N seconds. - -Tempcheck mode: - * 1 click: off - * 2 clicks: battcheck mode - * 3 clicks: thermal config mode - - Hold: thermal calibration mode - -Thermal config mode: - * Setting 1: calibrate sensor: - At buzz, click N times for N degrees C. For example, if the light - is current at "room temperature" of 22 C, click 22 times. Is - intended to only be done once upon initial setup, or not at all. - * Setting 2: temperature limit: - At buzz, click N times to set thermal limit to roughly 30 C + N. - -Thermal calibration mode: - - Hold until hot: set new ceiling value - - ... don't hold: blink out current ceiling value and exit +"Strobe" group modes: + + Candle mode: + * 1 click: off + * 2 clicks: next "strobe" group mode + * 3 clicks: add 30 minutes to the timer + (light will shut off when timer expires) + (default is no timer) + * Hold: brighter + * Click, hold: dimmer + + Bike flasher: + * 1 click: off + * 2 clicks: next "strobe" group mode + * Hold: brighter + * Click, hold: dimmer + + Party / Tactical strobe modes: + * 1 click: off + * Hold: change speed (faster) + * Click, hold: change speed (slower) + * 2 clicks: next "strobe" group mode + (TODO: random/police strobe?) + + Lightning storm mode: + * 1 click: off + * 2 clicks: next "strobe" group mode + +"Blinky" group modes: + + Battcheck mode: + * 1 click: off + * 2 clicks: goodnight mode + + Goodnight mode: + * 1 click: off + * 2 clicks: beacon mode + + Beacon mode: + * 1 click: off + * 2 clicks: tempcheck mode + * 3 clicks: configure time between pulses + + Beacon config mode: + * At buzz, click N times to set beacon frequency to N seconds. + + Tempcheck mode: + * 1 click: off + * 2 clicks: battcheck mode + * 3 clicks: thermal config mode + - Hold: thermal calibration mode + + Thermal config mode: + * Setting 1: calibrate sensor: + At buzz, click N times for N degrees C. For example, if the light + is current at "room temperature" of 22 C, click 22 times. Is + intended to only be done once upon initial setup, or not at all. + * Setting 2: temperature limit: + At buzz, click N times to set thermal limit to roughly 30 C + N. + + Thermal calibration mode: + - Hold until hot: set new ceiling value + - ... don't hold: blink out current ceiling value and exit Lockout mode: * Hold: momentary moon @@ -103,6 +116,11 @@ Momentary mode: * Release button: Light off. * To exit, disconnect power. (loosen/tighten the tailcap) +Muggle mode: + * 1 click: On / off. + * Hold: Ramp up / down. + * 6 clicks: Exit muggle mode. + TODO: * save settings in eeprom * decide on "hold until hot" or "click N times" for thermal config mode @@ -115,8 +133,8 @@ TODO: * add lightning mode? * muggle mode: smooth ramp * refactor to make config modes smaller - - candle mode timer, with three clicks to add 30 minutes - - candle mode: smoother adjustments? - - make sunset mode timer and brightness configurable? * move all config menus to four clicks + * candle mode timer, with three clicks to add 30 minutes - diagram updates for 3-click special actions + - candle mode: smoother adjustments? + - make sunset mode timer and brightness configurable? -- cgit v1.2.3