aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spaghetti-monster/fsm-events.h5
-rw-r--r--spaghetti-monster/ramping-ui.c60
-rw-r--r--spaghetti-monster/spaghetti-monster.h6
-rw-r--r--spaghetti-monster/spaghetti-monster.txt246
4 files changed, 285 insertions, 32 deletions
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.