diff options
| author | Selene ToyKeeper | 2017-08-18 18:12:17 -0600 |
|---|---|---|
| committer | Selene ToyKeeper | 2017-08-18 18:12:17 -0600 |
| commit | e96562e36df96ca755d527e479e597ae4e4e09e1 (patch) | |
| tree | 9d75c5eb9c8a68a6ea45ea06134c1a780bcbd416 /RoundTable | |
| parent | merged crescendo updates: added thermal regulation (diff) | |
| download | anduril-e96562e36df96ca755d527e479e597ae4e4e09e1.tar.gz anduril-e96562e36df96ca755d527e479e597ae4e4e09e1.tar.bz2 anduril-e96562e36df96ca755d527e479e597ae4e4e09e1.zip | |
Some early ideas for Round Table. Nothing close to compile-able yet.
Diffstat (limited to 'RoundTable')
| -rw-r--r-- | RoundTable/momentary.c | 68 | ||||
| -rw-r--r-- | RoundTable/round-table.c | 367 | ||||
| -rw-r--r-- | RoundTable/rt-ramping.h | 26 |
3 files changed, 461 insertions, 0 deletions
diff --git a/RoundTable/momentary.c b/RoundTable/momentary.c new file mode 100644 index 0000000..120406e --- /dev/null +++ b/RoundTable/momentary.c @@ -0,0 +1,68 @@ +/* + * Momentary: Very simple example UI for RoundTable. + * Is intended to be the simplest possible RT e-switch UI. + * The light is in 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 <http://www.gnu.org/licenses/>. + */ + +#define RT_EMISAR_D4_LAYOUT +#include "round-table.c" + +volatile uint8_t brightness; + +void light_on() { + PWM1_LVL = brightness; + PWM2_LVL = 0; +} + +void light_off() { + PWM1_LVL = 0; + PWM2_LVL = 0; +} + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + switch(event) { + + case EV_press: + brightness = 255; + light_on(); + // reset current event queue + empty_event_sequence(); + return 0; + + case EV_release: + light_off(); + // reset current event queue + empty_event_sequence(); + return 0; + + // LVP / low-voltage protection + case EV_voltage_low: + case EV_voltage_critical: + if (brightness > 0) brightness >>= 1; + else { + light_off(); + standby_mode(); + } + return 0; + } + return 1; // event not handled +} + +void setup() { + set_state(momentary_state); +} diff --git a/RoundTable/round-table.c b/RoundTable/round-table.c new file mode 100644 index 0000000..6cd1b10 --- /dev/null +++ b/RoundTable/round-table.c @@ -0,0 +1,367 @@ +#include "tk-attiny.h" + +// +typedef uint8_t (*EventCallbackPtr)(EventPtr event, uint16_t arg); +typedef uint8_t EventCallback(EventPtr event, uint16_t arg); +// FIXME: Why does a state need a number? Why not just a function pointer? +// (I don't think it actually needs a number...) +typedef struct State { + uint8_t num; + EventCallback event_callback; +} State; +typedef struct State* StatePtr; + +volatile StatePtr current_state; +#define EV_MAX_LEN 16 +volatile uint8_t current_event[EV_MAX_LEN]; + +volatile int16_t voltage; +#ifdef USE_THERMAL_REGULATION +volatile int16_t temperature; +#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 + +// 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 +typedef PROGMEM const uint8_t Event; +typedef Event* EventPtr; +Event EV_enter_state[] = { + A_ENTER_STATE, + 0 } ; +Event EV_leave_state[] = { + A_LEAVE_STATE, + 0 } ; +Event EV_tick[] = { + A_TICK, + 0 } ; +Event EV_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_press_release[] = { + A_PRESS, + A_RELEASE, + 0 }; +#define EV_1click EV_press_release_timeout +Event EV_press_release_timeout[] = { + A_PRESS, + A_RELEASE, + A_RELEASE_TIMEOUT, + 0 }; +#define EV_hold EV_press_hold +// FIXME: Should holds use "start+tick" or just "tick" with a tick number? +// Or "start+tick" with a tick number? +Event EV_press_hold[] = { + A_PRESS, + A_HOLD_TIMEOUT, + 0 }; +Event EV_press_hold_release[] = { + A_PRESS, + A_HOLD_TIMEOUT, + A_RELEASE, + 0 }; +Event EV_press_release_press[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + 0 }; +Event EV_press_release_press_release[] = { + A_PRESS, + A_RELEASE, + A_PRESS, + A_RELEASE, + 0 }; +#define EV_2clicks EV_press_release_press_release_timeout +Event EV_press_release_press_release_timeout[] = { + 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_press, + EV_press_release, + EV_press_release_timeout, + EV_press_release_press, + EV_press_release_press_release, + EV_press_release_press_release_timeout, + // ... +}; + +// TODO: move this to a separate UI-specific file +/* +State states[] = { + +}; +*/ + +// TODO? add events to a queue when inside an interrupt +// instead of calling the event functions directly? +// (then empty the queue in main loop?) + +// TODO? new delay() functions which handle queue consumption? +// TODO? new interruptible delay() functions? + +//static uint8_t ticks_since_last_event = 0; // TODO: 16 bits? + +void WDT_tick() { + timer ++; + + //static uint8_t hold_ticks = 0; // TODO: 16 bits? + + // callback on each timer tick + emit(EV_tick, timer); + + // if time since last event exceeds timeout, + // append timeout to current event sequence, then + // send event to current state callback + // //hold_event(ticks) + // //emit(EV_press_hold, hold_ticks); + // emit_current(hold_ticks); + // or + // //release_timeout() + // //emit(EV_press_release_timeout, 0); + // emit_current(0); + + // add 4-step voltage / temperature thing? + // with averaged values, + // once every N ticks? +} + +void button_change_interrupt() { + // TODO: debounce a little + + //ticks_since_last_event = 0; // something happened + + // TODO: add event to current sequence + + // check if sequence matches any defined sequences + // if so, send event to current state callback + emit_current(0); +} + +uint8_t emit_current(uint16_t arg) { + uint8_t err = 1; + for (uint8_t i=0; i<sizeof(event_sequences); i++) { + if (events_match(event_sequences[i], current_event)) { + err = emit(event_sequences[i], arg); + break; + } + } + return err; +} + +#define events_match(a,b) compare_event_sequences(a,b) +uint8_t compare_event_sequences(uint8_t *a, uint8_t *b) { +} + +void empty_event_sequence() { + for(uint8_t i=0; i<EV_MAX_LEN; i++) current_event[i] = 0; +} + +void append_event(uint8_t ev_type) { + ticks_since_last_event = 0; // something happened + uint8_t i; + for(i=0; current_event[i], i<EV_MAX_LEN; i++); + if (i < EV_MAX_LEN) { + current_event[i] = ev_type; + } else { + // TODO: ... something? + } +} + +// TODO: stack for states, to allow shared utility states like "input a number" +// and such, which return to the previous state after finishing +// 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" +uint8_t emit(EventPtr event, uint16_t arg) { + // TODO: implement + for(;;) { + uint8_t err = foo.callback(event, arg); + if (! err) return 0; + } + return 1; // event not handled +} + +uint8_t push_state(State *new_state) { + // TODO: implement +} + +State * pop_state() { + // TODO: implement +} + +uint8_t set_state(State *new_state) { + pop_state(); + return push_state(new_state); +} + +uint8_t _set_state(State *new_state) { + // call old state-exit hook (don't use stack) + current_state.callback(EV_leave_state, 0); + // set new state + current_state = new_state; + // call new state-enter hook (don't use stack) + current_state.callback(EV_enter_state, 0); +} + +// TODO: implement +ISR(WDT_vect) { +} + +// TODO: implement? (or is it better done in main()?) +ISR(ADC_vect) { + static uint8_t adc_step = 0; + static uint8_t lvp_timer = 0; + #define LVP_TIMER_START 255 // TODO: calibrate this value + + #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 + + int16_t measurement = ADC; // latest 10-bit ADC reading + + adc_step = (adc_step + 1) & (ADC_STEPS-1); + + // TODO: voltage + // TODO: 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 { + // if (voltage is low) { + // uint8_t err = emit(EV_voltage_low, 0); + // if (!err) lvp_timer = LVP_TIMER_START; + // } + // } + + // TODO: temperature + + // start another measurement for next time + #ifdef USE_THERMAL_REGULATION + if (adc_step < 2) ADMUX = ADMUX_VCC; + else ADMUX = ADMUX_THERM; + #else + ADMUX = ADMUX_VCC; + #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; +} + +// 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(); + + // TODO: make sure switch isn't currently pressed + while (switch_pressed()) {} + + PCINT_on(); // wake on e-switch event + + sleep_enable(); + sleep_bod_disable(); + sleep_cpu(); // wait here + + // something happened; wake up + sleep_disable(); + PCINT_off(); + ADC_on(); + WDT_on(); +} + +// boot-time tasks +/* Define this in your RoundTable recipe +void setup() { +} +*/ + +void main() { + // Don't allow interrupts while booting + cli(); + WDT_off(); + PCINT_off(); + + // TODO: configure PWM channels + #if PWM_CHANNELS == 1 + #elif PWM_CHANNELS == 2 + #elif PWM_CHANNELS == 3 + #elif PWM_CHANNELS == 4 + // FIXME: How exactly do we do PWM on channel 4? + #endif + + // TODO: turn on ADC? + // TODO: configure e-switch + //PORTB = (1 << SWITCH_PIN); // e-switch is the only input + //PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin + + // 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)? + + // TODO: call recipe's setup + setup(); + + // all booted -- turn interrupts back on + PCINT_on(); + WDT_on(); + sei(); + + // main loop + while (1) { + // TODO: update e-switch press state + // TODO: if wait queue not empty, process and pop first item in queue + // TODO: check voltage? + // TODO: check temperature? + } +} diff --git a/RoundTable/rt-ramping.h b/RoundTable/rt-ramping.h new file mode 100644 index 0000000..61561c4 --- /dev/null +++ b/RoundTable/rt-ramping.h @@ -0,0 +1,26 @@ +/* + * rt-ramping.h: Ramping functions for Round Table. + * 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 <http://www.gnu.org/licenses/>. + */ + +// TODO: ramp tables +// TODO: RAMP_SIZE / MAX_LVL +// TODO: actual_lvl +// TODO: target_lvl +// TODO: set_lvl +// TODO: set_lvl_smooth |
