diff options
| author | Selene ToyKeeper | 2017-08-24 19:22:10 -0600 |
|---|---|---|
| committer | Selene ToyKeeper | 2017-08-24 19:22:10 -0600 |
| commit | 5184aad41fdb501f05ff7b0d7131011657ed8275 (patch) | |
| tree | 9de56f14972a79b86a0291ab0ab0fc81ae7469d2 | |
| parent | Added loop() to API, executes constantly. (diff) | |
| download | anduril-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 '')
| -rw-r--r-- | spaghetti-monster/fsm-events.h | 5 | ||||
| -rw-r--r-- | spaghetti-monster/ramping-ui.c | 60 | ||||
| -rw-r--r-- | spaghetti-monster/spaghetti-monster.h | 6 | ||||
| -rw-r--r-- | spaghetti-monster/spaghetti-monster.txt | 246 |
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. |
