aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSelene ToyKeeper2017-08-18 18:12:17 -0600
committerSelene ToyKeeper2017-08-18 18:12:17 -0600
commite96562e36df96ca755d527e479e597ae4e4e09e1 (patch)
tree9d75c5eb9c8a68a6ea45ea06134c1a780bcbd416
parentmerged crescendo updates: added thermal regulation (diff)
downloadanduril-e96562e36df96ca755d527e479e597ae4e4e09e1.tar.gz
anduril-e96562e36df96ca755d527e479e597ae4e4e09e1.tar.bz2
anduril-e96562e36df96ca755d527e479e597ae4e4e09e1.zip
Some early ideas for Round Table. Nothing close to compile-able yet.
-rw-r--r--RoundTable/momentary.c68
-rw-r--r--RoundTable/round-table.c367
-rw-r--r--RoundTable/rt-ramping.h26
-rw-r--r--tk-attiny.h82
4 files changed, 542 insertions, 1 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
diff --git a/tk-attiny.h b/tk-attiny.h
index 72ed2f0..ebd997f 100644
--- a/tk-attiny.h
+++ b/tk-attiny.h
@@ -4,7 +4,7 @@
* Attiny portability header.
* This helps abstract away the differences between various attiny MCUs.
*
- * Copyright (C) 2015 Selene ToyKeeper
+ * 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
@@ -31,12 +31,25 @@
#define EEPSIZE 64
#define V_REF REFS0
#define BOGOMIPS 950
+ #define ADMUX_VCC 0b00001100
#elif (ATTINY == 25)
// TODO: Use 6.4 MHz instead of 8 MHz?
#define F_CPU 8000000UL
#define EEPSIZE 128
#define V_REF REFS1
#define BOGOMIPS (F_CPU/4000)
+ #define ADMUX_VCC 0b00001100
+ #define ADMUX_THERM 0b10001111
+#elif (ATTINY == 85)
+ // TODO: Use 6.4 MHz instead of 8 MHz?
+ #define F_CPU 8000000UL
+ #define EEPSIZE 512
+ #define V_REF REFS1
+ #define BOGOMIPS (F_CPU/4000)
+ // (1 << V_REF) | (0 << ADLAR) | (VCC_CHANNEL)
+ #define ADMUX_VCC 0b00001100
+ // (1 << V_REF) | (0 << ADLAR) | (THERM_CHANNEL)
+ #define ADMUX_THERM 0b10001111
#else
Hey, you need to define ATTINY.
#endif
@@ -145,6 +158,73 @@
#endif // NANJG_LAYOUT
+
+#ifdef RT_EMISAR_D4_LAYOUT
+/*
+ * ----
+ * Reset -|1 8|- VCC
+ * eswitch -|2 7|-
+ * AUX LED -|3 6|- PWM (FET)
+ * GND -|4 5|- PWM (1x7135)
+ * ----
+ */
+
+#define AUXLED_PIN PB4 // pin 3
+
+#define SWITCH_PIN PB3 // pin 2, OTC
+#define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4)
+#define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3
+
+#define PWM2_PIN PB1 // pin 6, FET PWM
+#define PWM2_LVL OCR0B // OCR0B is the output compare register for PB1
+#define PWM1_PIN PB0 // pin 5, 1x7135 PWM
+#define PWM1_LVL OCR0A // OCR0A is the output compare register for PB0
+
+#define VOLTAGE_PIN PB2 // pin 7, voltage ADC
+#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
+#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
+#define ADC_PRSCL 0x06 // clk/64
+
+//#define TEMP_DIDR ADC4D
+#define TEMP_CHANNEL 0b00001111
+
+#define FAST 0xA3 // fast PWM both channels
+#define PHASE 0xA1 // phase-correct PWM both channels
+
+#endif
+
+
+#ifdef RT_TKSABER_LAYOUT
+/*
+ * ----
+ * Reset -|1 8|- VCC
+ * PWM 4 (A) -|2 7|- e-switch
+ * PWM 3 (B) -|3 6|- PWM 2 (G)
+ * GND -|4 5|- PWM 1 (R)
+ * ----
+ */
+
+#define PWM1_PIN PB0 // pin 5
+#define PWM1_LVL OCR0A
+#define PWM2_PIN PB1 // pin 6
+#define PWM2_LVL OCR0B
+#define PWM3_PIN PB4 // pin 3
+#define PWM3_LVL OCR1B
+#define PWM4_PIN PB3 // pin 2
+#define PWM4_LVL OCR1A // FIXME: does this work?
+
+#define SWITCH_PIN PB2 // pin 7
+
+#define ADC_PRSCL 0x07 // clk/128 (no need to be super fast)
+// FIXME: What is the DIDR for pin 8?
+//#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
+
+#define FAST 0xA3 // fast PWM both channels
+#define PHASE 0xA1 // phase-correct PWM both channels
+
+#endif // TKSABER_LAYOUT
+
+
#ifndef PWM_LVL
Hey, you need to define an I/O pin layout.
#endif