aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster
diff options
context:
space:
mode:
authorSelene ToyKeeper2017-08-24 19:22:10 -0600
committerSelene ToyKeeper2017-08-24 19:22:10 -0600
commit5184aad41fdb501f05ff7b0d7131011657ed8275 (patch)
tree9de56f14972a79b86a0291ab0ab0fc81ae7469d2 /spaghetti-monster
parentAdded loop() to API, executes constantly. (diff)
downloadanduril-5184aad41fdb501f05ff7b0d7131011657ed8275.tar.gz
anduril-5184aad41fdb501f05ff7b0d7131011657ed8275.tar.bz2
anduril-5184aad41fdb501f05ff7b0d7131011657ed8275.zip
Started on some documentation, spaghetti-monster.txt.
Added #defines for State return values: EVENT_HANDLED, EVENT_NOT_HANDLED Improved handling of delay includes. Managed mischief.
Diffstat (limited to 'spaghetti-monster')
-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.
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.