aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster
diff options
context:
space:
mode:
authorSelene ToyKeeper2017-08-19 12:17:58 -0600
committerSelene ToyKeeper2017-08-19 12:17:58 -0600
commit8d2f442317fd3678213a14307f95d2f5b54c5623 (patch)
treecfcf8e6178c545b236ae8b9f3263ea78372b2834 /spaghetti-monster
parentAdded missing GPL header, fixed typo, fixed missing return. (diff)
downloadanduril-8d2f442317fd3678213a14307f95d2f5b54c5623.tar.gz
anduril-8d2f442317fd3678213a14307f95d2f5b54c5623.tar.bz2
anduril-8d2f442317fd3678213a14307f95d2f5b54c5623.zip
Renamed from RoundTable to SpaghettiMonster (FSM).
Diffstat (limited to 'spaghetti-monster')
-rw-r--r--spaghetti-monster/fsm-ramping.h26
-rw-r--r--spaghetti-monster/momentary.c85
-rw-r--r--spaghetti-monster/spaghetti-monster.h737
3 files changed, 848 insertions, 0 deletions
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 <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/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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "tk-attiny.h"
+
+#include <avr/pgmspace.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/wdt.h>
+#include <avr/eeprom.h>
+#include <avr/sleep.h>
+#include <util/delay_basic.h>
+
+// 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<EV_MAX_LEN) && (a[i] == pgm_read_byte(b+i)); i++) {
+ // end of zero-terminated sequence
+ if (a[i] == 0) return 1;
+ }
+ // if we ever fall out, that means something was different
+ // (or the sequence is too long)
+ return 0;
+}
+
+void empty_event_sequence() {
+ for(uint8_t i=0; i<EV_MAX_LEN; i++) current_event[i] = 0;
+}
+
+void push_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?
+ }
+}
+
+#define EMISSION_QUEUE_LEN 8
+// no comment about "volatile emissions"
+volatile Emission emissions[EMISSION_QUEUE_LEN];
+
+void append_emission(EventPtr event, uint16_t arg) {
+ uint8_t i;
+ // find last entry
+ for(i=0;
+ (i<EMISSION_QUEUE_LEN) && (emissions[i].event != NULL);
+ i++) { }
+ // add new entry
+ if (i < EMISSION_QUEUE_LEN) {
+ emissions[i].event = event;
+ emissions[i].arg = arg;
+ } else {
+ // TODO: if queue full, what should we do?
+ }
+}
+
+void delete_first_emission() {
+ uint8_t i;
+ for(i=0; i<EMISSION_QUEUE_LEN-1; i++) {
+ emissions[i].event = emissions[i+1].event;
+ emissions[i].arg = emissions[i+1].arg;
+ }
+ emissions[i].event = NULL;
+ emissions[i].arg = 0;
+}
+
+// TODO: 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;
+// 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"
+// Call stacked callbacks for the given event until one handles it.
+//#define emit_now emit
+uint8_t emit_now(EventPtr event, uint16_t arg) {
+ for(int8_t i=state_stack_len-1; 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; i++) {
+ // check current value
+ uint8_t bit = ((PINB & (1<<SWITCH_PIN)) == 0);
+ highcount += bit;
+ // wait a moment
+ _delay_loop_2(BOGOMIPS/64);
+ }
+ // use most common value
+ uint8_t result = (highcount > (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<NUM_VOLTAGE_VALUES; i++)
+ voltage_values[i] = measurement;
+ voltage = 42; // Life, the Universe, and Everything (*)
+ } else {
+ uint16_t total = 0;
+ uint8_t i;
+ for(i=0; i<NUM_VOLTAGE_VALUES-1; i++) {
+ voltage_values[i] = voltage_values[i+1];
+ total += voltage_values[i];
+ }
+ voltage_values[i] = measurement;
+ total += measurement;
+ total = total >> 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<<ADEN); //ADC off
+}
+
+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 WDT_on()
+{
+ // interrupt every 16ms
+ //cli(); // Disable interrupts
+ wdt_reset(); // Reset the WDT
+ WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
+ WDTCR = (1<<WDIE); // Enable interrupt every 16ms
+ //sei(); // Enable interrupts
+}
+
+inline void WDT_off()
+{
+ //cli(); // Disable interrupts
+ wdt_reset(); // Reset the WDT
+ MCUSR &= ~(1<<WDRF); // Clear Watchdog reset flag
+ WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
+ WDTCR = 0x00; // Disable WDT
+ //sei(); // Enable interrupts
+}
+
+// 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();
+}
+
+// last-called 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;
+}
+
+// boot-time tasks
+// Define this in your RoundTable recipe
+void setup();
+
+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 << 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();
+ }
+ }
+}
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.