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
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.