diff --git a/CMakeLists.txt b/CMakeLists.txt index aac0c63..9658a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,6 @@ project(firmware) set(CMAKE_CXX_STANDARD GNU99) set(SOURCE_FILES - main.c lib/calc.h lib/iopins.c lib/iopins.h @@ -23,9 +22,19 @@ set(SOURCE_FILES lib/color.h lib/wsrgb.c lib/wsrgb.h + lib/timebase.c + lib/timebase.h + main.c pinout.h + game.c + game.h display.c - display.h leds.c leds.h) + display.h + leds.c + leds.h + game.c + game.h + common.h) include_directories(lib /usr/avr/include/) diff --git a/Makefile b/Makefile index 89ac099..d66f101 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,12 @@ OBJS += lib/iopins.o OBJS += lib/spi.o OBJS += lib/adc.o OBJS += lib/debounce.o +OBJS += lib/timebase.o OBJS += lib/wsrgb.o OBJS += lib/color.o OBJS += display.o +OBJS += game.o +OBJS += leds.o # Dirs with header files INCL_DIRS = . lib/ @@ -52,7 +55,7 @@ CFLAGS += -ffunction-sections -fdata-sections -Os -Wno-unused-parameter LDFLAGS = -Wl,--gc-sections -Wl,--relax -lm -#LD_FLAGS += -Wl,-u,vfprintf -lprintf_flt -lm ## for floating-point printf +#LD_FLAGS += -Wl,-u,vfprintf -lprintf_flt ## for floating-point printf LD_FLAGS += -Wl,-u,vfprintf -lprintf_min ## for smaller printf ############################################# diff --git a/game.c b/game.c new file mode 100644 index 0000000..454a404 --- /dev/null +++ b/game.c @@ -0,0 +1,362 @@ +// +// Created by MightyPork on 2017/06/08. +// originally written 2016/9/2 for STM32F103 bluepill, adapted +// + +#include "game.h" + +#include +#include +#include +#include +#include +#include "lib/color.h" +#include "leds.h" +#include "lib/timebase.h" +#include "display.h" +#include "pinout.h" + +//region Colors + +#define C_DARK rgb24(0,0,0) +#define C_DIMWHITE rgb24(30,30,30) +#define C_OKGREEN rgb24(30,200,0) +#define C_CRIMSON rgb24(255,0,0) + +#define C_DIMRED rgb24(100,0,0) +#define C_DIMGREEN rgb24(0,100,0) +#define C_DIMBLUE rgb24(0,0,80) +#define C_DIMYELLOW rgb24(50,45,0) + +#define C_BRTRED rgb24(255,0,0) +#define C_BRTGREEN rgb24(0,255,0) +#define C_BRTBLUE rgb24(0,0,255) +#define C_BRTYELLOW rgb24(127,110,0) + +// assign to positions + +#define C_DIM1 C_DIMRED +#define C_DIM2 C_DIMGREEN +#define C_DIM3 C_DIMBLUE +#define C_DIM4 C_DIMYELLOW + +#define C_BRT1 C_BRTRED +#define C_BRT2 C_BRTGREEN +#define C_BRT3 C_BRTBLUE +#define C_BRT4 C_BRTYELLOW + +//endregion + +enum GameState_enum { + STATE_NEW_GAME, // new game, waiting for key + STATE_REPLAY, // showing sequence + STATE_USER_INPUT, // waiting for user input of repeated sequence + STATE_SUCCESS_EFFECT, // entered OK, show some fireworks + STATE_FAIL_EFFECT, // entered wrong, show FAIL animation, then reset. +}; + +/** Current game state */ +enum GameState_enum GameState = STATE_NEW_GAME; + +/** Screen colors */ +uint32_t screen[4] = {0, 0, 0, 0}; +const uint32_t brt[4] = {C_BRT1, C_BRT2, C_BRT3, C_BRT4}; +const uint32_t dim[4] = {C_DIM1, C_DIM2, C_DIM3, C_DIM4}; +const uint32_t dark[4] = {C_DARK, C_DARK, C_DARK, C_DARK}; +const uint32_t dimwhite[4] = {C_DIMWHITE, C_DIMWHITE, C_DIMWHITE, C_DIMWHITE}; + +#define REPLAY_INTERVAL 400 +#define REPLAY_INTERVAL_GAP 75 +#define SUC_EFF_TIME 500 +#define FAIL_EFF_TIME 1000 + +/** Sequence of colors to show. Seed is constant thorough a game. + * rng_state is used by rand_r() for building the sequence. */ +uint32_t game_seed; +unsigned long game_rng_state; +uint8_t last_item; +uint8_t repeat_count; + +/** Nr of revealed colors in sequence */ +uint8_t game_revealed_n; +/** Nr of next color to replay/input */ +uint8_t game_replay_n; +/** Nr of succ repeated colors */ +uint8_t game_repeat_n; + +void enter_state(enum GameState_enum state); + +/** Show current screen colors */ +void show_screen() +{ + leds_set(screen); +} + +/** Prepare rng sequence for replay / test */ +void reset_sequence() +{ + game_rng_state = game_seed; + last_item = 99; + repeat_count = 0; +} + +/** Get next item in the sequence */ +uint8_t get_next_item() +{ + uint8_t item; + while (1) { + item = (uint8_t) rand_r(&game_rng_state) & 0x03; + if (item == last_item) { + repeat_count++; + if (repeat_count < 2) { + goto suc; + } + } else { + last_item = item; + repeat_count = 0; + goto suc; + } + } + +suc: + return item; +} + +/** Enter state - callback for delayed state change */ +void deferred_enter_state(void *state) +{ + enter_state((enum GameState_enum) state); +} + +/** Future task CB in replay seq */ +void replay_callback(void *onOff) +{ + bool on = (bool) onOff; + + screen[0] = C_DARK; + screen[1] = C_DARK; + screen[2] = C_DARK; + screen[3] = C_DARK; + + if (on) { + uint8_t color = get_next_item(); + game_replay_n++; + screen[color] = brt[color]; + show_screen(); + schedule_task(replay_callback, (void *) 0, REPLAY_INTERVAL, false); + } else { + // turning off + show_screen(); + + // Schedule next turning ON + if (game_replay_n < game_revealed_n) { + schedule_task(replay_callback, (void *) 1, REPLAY_INTERVAL_GAP, false); + } else { + enter_state(STATE_USER_INPUT); + //schedule_task(deferred_enter_state, (void *) STATE_USER_INPUT, 50, false); + } + } +} + +/** SUCCESS effect */ +void suc_eff_callback(void *onOff) +{ + bool on = (bool) onOff; + + if (on) { + for (uint8_t i = 0; i < 4; i++) screen[i] = C_OKGREEN; + schedule_task(suc_eff_callback, 0, SUC_EFF_TIME, false); + } else { + for (uint8_t i = 0; i < 4; i++) screen[i] = C_DARK; + + schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 250, false); + } + + show_screen(); +} + +/** ERROR effect */ +void fail_eff_callback(void *onOff) +{ + bool on = (bool) onOff; + + if (on) { + for (int i = 0; i < 4; i++) screen[i] = C_CRIMSON; + schedule_task(fail_eff_callback, 0, FAIL_EFF_TIME, false); + } else { + for (int i = 0; i < 4; i++) screen[i] = C_DARK; + + schedule_task(deferred_enter_state, (void *) STATE_NEW_GAME, 250, false); + } + + show_screen(); +} + +/** + * @brief Enter a game state + * @param state + */ +void enter_state(enum GameState_enum state) +{ + GameState = state; + + switch (state) { + case STATE_NEW_GAME: + // new game - idle state before new game is started + + // all dimly lit + for (int i = 0; i < 4; i++) screen[i] = 0; //C_DIMWHITE + break; + + case STATE_REPLAY: + game_replay_n = 0; + reset_sequence(); + + // Start replay + replay_callback((void *) 1); + break; + + case STATE_USER_INPUT: + memcpy(screen, dim, sizeof(screen)); + + // Start entering & checking + game_repeat_n = 0; + reset_sequence(); + break; + + case STATE_SUCCESS_EFFECT: + memcpy(screen, dim, sizeof(screen)); + //suc_eff_callback((void *) 1); + schedule_task(suc_eff_callback, (void *) 1, 250, false); + break; + + case STATE_FAIL_EFFECT: + memcpy(screen, dim, sizeof(screen)); + //fail_eff_callback((void *) 1); + schedule_task(fail_eff_callback, (void *) 1, 250, false); + break; + } + + show_screen(); +} + +/** Prepare new sequence, using time for seed. */ +void prepare_sequence() +{ + game_seed = time_ms; + game_rng_state = game_seed; + last_item = 99; + repeat_count = 0; +} + +/** Main function, called from MX-generated main.c */ +void game_main(void) +{ + display_show(SEG_G, SEG_G); // two dashes... + enter_state(STATE_NEW_GAME); + // we'll init the sequence when user first presses a button - the time is used as a seed + + enum GameState_enum last_state = STATE_NEW_GAME; + uint16_t cnt = 0; + while (1) { + if (GameState == last_state) { + if (GameState == STATE_NEW_GAME) { + if (cnt == 50) { + // clear after 5 secs + display_show(SEG_G, SEG_G); + } + + if (cnt == 3000) { + // Shut down after 5 mins + screen[0] = C_CRIMSON; + screen[1] = C_CRIMSON; + screen[2] = C_CRIMSON; + screen[3] = C_CRIMSON; + show_screen(); + delay_s(2000); + pin_down(PIN_PWR_HOLD); + } + } else { + if (cnt > 150) {// 15 secs = stop game. + // reset state + enter_state(STATE_NEW_GAME); + show_screen(); + display_show(SEG_G, SEG_G); + cnt = 0; + } + } + } else { + last_state = GameState; + cnt = 0; + } + + cnt++; + delay_ms(100); + } +} + +/** + * @brief Handle a button press. Callback for debouncer. + * @param button: button identifier + * @param press: press state (1 = just pressed, 0 = just released) + */ +void game_button_handler(uint8_t button, bool press) +{ + // convert to 0-3 + button--; + + switch (GameState) { + case STATE_NEW_GAME: + if (press) { + // feedback + display_show_number(0); // show 0 + } + + if (!press) { // released + // user wants to start playing + prepare_sequence(); + game_revealed_n = 1; // start with 1 revealed + + // darken + //memcpy(screen, dark, sizeof(screen)); + //show_screen(); + + // start playback with a delay + // this makes it obvious the playback is not a feedback to the pressed button + schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 500, false); + //enter_state(STATE_REPLAY); + } + break; + case STATE_USER_INPUT: + // user is entering a color + memcpy(screen, dim, sizeof(screen)); + + if (press) { + // Button is down + screen[button] = brt[button]; + } else { + // Button is released + // Verify correctness + uint8_t expected = get_next_item(); + if (expected == button) { + // good! + game_repeat_n++; + if (game_repeat_n == game_revealed_n) { + // repeated all, good work! + game_revealed_n++; + display_show_number(game_revealed_n-1); + enter_state(STATE_SUCCESS_EFFECT); + } + } else { + enter_state(STATE_FAIL_EFFECT); + } + } + + show_screen(); + break; + + default: + // discard button press, not expecting input now + break; + } +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..3c349ba --- /dev/null +++ b/game.h @@ -0,0 +1,15 @@ +// +// Created by MightyPork on 2017/06/08. +// + +#ifndef FIRMWARE_GAME_H +#define FIRMWARE_GAME_H + +#include +#include + +void game_main(void); + +void game_button_handler(uint8_t button, bool press); + +#endif //FIRMWARE_GAME_H diff --git a/leds.c b/leds.c index 6d27f20..fe69365 100644 --- a/leds.c +++ b/leds.c @@ -2,9 +2,32 @@ // Created by MightyPork on 2017/06/08. // +#include #include "lib/color.h" +#include "lib/wsrgb.h" +#include "display.h" +#include +#include + #include "leds.h" -void leds_set(uint32_t L1, uint32_t L2, uint32_t L3, uint32_t L4); +static uint32_t leds[4]; + +void leds_set(const uint32_t *new_leds) +{ + memcpy(leds, new_leds, 4*sizeof(uint32_t)); +} + +void leds_show(void) +{ + xrgb_t arr[4]; + for (uint8_t i = 0; i < 4; i++) { + float db = (float)disp_brightness / 255.0f; + arr[i].r = (uint8_t) ((float)rgb24_r(leds[i]) * db); + arr[i].g = (uint8_t) ((float)rgb24_g(leds[i]) * db); + arr[i].b = (uint8_t) ((float)rgb24_b(leds[i]) * db); + } -void leds_show(void); + ws_send_xrgb_array(arr, 4); + //ws_send_rgb24_array(leds, 4); +} diff --git a/leds.h b/leds.h index f75c7ac..9234281 100644 --- a/leds.h +++ b/leds.h @@ -2,37 +2,13 @@ // Created by MightyPork on 2017/06/08. // -#include -#include "lib/color.h" -#include "lib/wsrgb.h" -#include "display.h" -#include - #ifndef FIRMWARE_LEDS_H #define FIRMWARE_LEDS_H -uint32_t leds[4]; - -void leds_set(uint32_t L1, uint32_t L2, uint32_t L3, uint32_t L4) -{ - leds[0] = L1; - leds[1] = L2; - leds[2] = L3; - leds[3] = L4; -} +#include -void leds_show(void) -{ - xrgb_t arr[4]; - for (uint8_t i = 0; i < 4; i++) { - float db = (float)disp_brightness / 255.0f; - arr[i].r = (uint8_t) ((float)rgb24_r(leds[i]) * db); - arr[i].g = (uint8_t) ((float)rgb24_g(leds[i]) * db); - arr[i].b = (uint8_t) ((float)rgb24_b(leds[i]) * db); - } +void leds_set(const uint32_t *new_leds); - ws_send_xrgb_array(arr, 4); - //ws_send_rgb24_array(leds, 4); -} +void leds_show(void); #endif //FIRMWARE_LEDS_H diff --git a/lib/color.h b/lib/color.h index 7fef13a..d62a7f2 100644 --- a/lib/color.h +++ b/lib/color.h @@ -34,7 +34,7 @@ typedef uint32_t rgb24_t; #define xrgb_rgb6(c) (((((rgb6_t)c.r) & 0xC0) >> 2) | ((((rgb6_t)c.g) & 0xC0) >> 4) | ((((rgb6_t)c.b) & 0xC0) >> 6)) #define rgb24c(r,g,b) (((((rgb24_t)r) & 0xFF) << 16) | ((((rgb24_t)g) & 0xFF) << 8) | (((rgb24_t)b) & 0xFF)) -#define rgb24(r,g,b) ((rgb24_t) rgb24(r,g,b)) +#define rgb24(r,g,b) ((rgb24_t) rgb24c(r,g,b)) #define rgb24_r(c) ((((rgb24_t) (c)) >> 16) & 0xFF) #define rgb24_g(c) ((((rgb24_t) (c)) >> 8) & 0xFF) diff --git a/lib/timebase.c b/lib/timebase.c new file mode 100644 index 0000000..1786e60 --- /dev/null +++ b/lib/timebase.c @@ -0,0 +1,296 @@ +// +// Created by MightyPork on 2017/06/08. +// + +#include "timebase.h" + +// Time base +volatile ms_time_t time_ms = 0; + + +typedef struct { + /** User callback with arg */ + void (*callback)(void *); + /** Arg for the arg callback */ + void *cb_arg; + /** Callback interval */ + ms_time_t interval_ms; + /** Counter, when reaches interval_ms, is cleared and callback is called. */ + ms_time_t countup; + /** Unique task ID (for cancelling / modification) */ + task_pid_t pid; + /** Enable flag - disabled tasks still count, but CB is not run */ + bool enabled; + /** Marks that the task is due to be run */ + bool enqueue; +} periodic_task_t; + + +typedef struct { + /** User callback with arg */ + void (*callback)(void *); + /** Arg for the arg callback */ + void *cb_arg; + /** Counter, when reaches 0ms, callback is called and the task is removed */ + ms_time_t countdown_ms; + /** Unique task ID (for cancelling / modification) */ + task_pid_t pid; + /** Whether this task is long and needs posting on the queue */ + bool enqueue; +} future_task_t; + +// Slots +static periodic_task_t periodic_tasks[TIMEBASE_PERIODIC_COUNT]; +static future_task_t future_tasks[TIMEBASE_FUTURE_COUNT]; + +static task_pid_t next_task_pid = 1; // 0 (PID_NONE) is reserved + +/** Get a valid free PID for a new task. */ +static task_pid_t make_pid(void) +{ + task_pid_t pid = next_task_pid++; + + // make sure no task is given PID 0 + if (next_task_pid == PID_NONE) { + next_task_pid++; + } + + return pid; +} + + +/** Take an empty periodic task slot and populate the basics. */ +static periodic_task_t* claim_periodic_task_slot(ms_time_t interval, bool enqueue) +{ + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != PID_NONE) continue; // task is used + + task->countup = 0; + task->interval_ms = interval - 1; + task->enqueue = enqueue; + task->pid = make_pid(); + task->enabled = true; + return task; + } + + return NULL; +} + + +/** Take an empty future task slot and populate the basics. */ +static future_task_t* claim_future_task_slot(ms_time_t delay, bool enqueue) +{ + for (size_t i = 0; i < TIMEBASE_FUTURE_COUNT; i++) { + future_task_t *task = &future_tasks[i]; + if (task->pid != PID_NONE) continue; // task is used + + task->countdown_ms = delay; + task->enqueue = enqueue; + task->pid = make_pid(); + return task; + } + + return NULL; +} + +/** Add a periodic task with an arg. */ +task_pid_t add_periodic_task(void (*callback)(void*), void* arg, ms_time_t interval, bool enqueue) +{ + periodic_task_t *task = claim_periodic_task_slot(interval, enqueue); + + if (task == NULL) return PID_NONE; + + task->callback = callback; + task->cb_arg = arg; + + return task->pid; +} + + +/** Schedule a future task, with uint32_t argument. */ +task_pid_t schedule_task(void (*callback)(void*), void *arg, ms_time_t delay, bool enqueue) +{ + future_task_t *task = claim_future_task_slot(delay, enqueue); + + if (task == NULL) return PID_NONE; + + task->callback = callback; + task->cb_arg = arg; + + return task->pid; +} + + +/** Enable or disable a periodic task. */ +bool enable_periodic_task(task_pid_t pid, bool enable) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != pid) continue; + + task->enabled = enable; + return true; + } + + return false; +} + + +/** Check if a periodic task is enabled */ +bool is_periodic_task_enabled(task_pid_t pid) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != pid) continue; + + return task->enabled; + } + + return false; +} + +bool reset_periodic_task(task_pid_t pid) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != pid) continue; + + task->countup = 0; + return true; + } + + return false; +} + + +bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != pid) continue; + task->interval_ms = interval; + return true; + } + + return false; +} + + +/** Remove a periodic task. */ +bool remove_periodic_task(task_pid_t pid) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid != pid) continue; + + task->pid = PID_NONE; // mark unused + return true; + } + + return false; +} + + +/** Abort a scheduled task. */ +bool abort_scheduled_task(task_pid_t pid) +{ + if (pid == PID_NONE) return false; + + for (size_t i = 0; i < TIMEBASE_FUTURE_COUNT; i++) { + future_task_t *task = &future_tasks[i]; + if (task->pid != pid) continue; + + task->pid = PID_NONE; // mark unused + return true; + } + + return false; +} + + +/** Run a periodic task */ +static void run_periodic_task(periodic_task_t *task) +{ + if (!task->enabled) return; + + if (task->enqueue) { + // queued task + // FIXME re-implement queue + //tq_post(task->callback, task->cb_arg); + } else { + // immediate task + task->callback(task->cb_arg); + } +} + + +/** Run a future task */ +static void run_future_task(future_task_t *task) +{ + if (task->enqueue) { + // queued task + // FIXME re-implement queue + //tq_post(task->callback, task->cb_arg); + } else { + // immediate task + task->callback(task->cb_arg); + } +} + + +/** + * @brief Millisecond callback, should be run in the SysTick handler. + */ +void timebase_ms_cb(void) +{ + // increment global time + time_ms++; + + // run periodic tasks + for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) { + periodic_task_t *task = &periodic_tasks[i]; + if (task->pid == PID_NONE) continue; // unused + + if (task->countup++ >= task->interval_ms) { + // run if enabled + run_periodic_task(task); + // restart counter + task->countup = 0; + } + } + + // run planned future tasks + for (size_t i = 0; i < TIMEBASE_FUTURE_COUNT; i++) { + future_task_t *task = &future_tasks[i]; + if (task->pid == PID_NONE) continue; // unused + + if (task->countdown_ms-- == 0) { + // run + run_future_task(task); + // release the slot + task->pid = PID_NONE; + } + } +} + +/** Helper for looping with periodic branches */ +bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration) +{ + if (time_ms - *start >= duration) { + *start = time_ms; + return true; + } + + return false; +} diff --git a/lib/timebase.h b/lib/timebase.h new file mode 100644 index 0000000..b930cf0 --- /dev/null +++ b/lib/timebase.h @@ -0,0 +1,151 @@ +// +// Created by MightyPork on 2017/06/08. +// + +#ifndef TIMEBASE_H +#define TIMEBASE_H + +/** + * To use the Timebase functionality, + * set up SysTick to 1 kHz and call + * timebase_ms_cb() in the IRQ. + * + * If you plan to use pendable future tasks, + * also make sure you call run_pending_tasks() + * in your main loop. + * + * This is not needed for non-pendable tasks. + */ + +#include +#include +#include + +/** Task PID. */ +typedef uint8_t task_pid_t; + +/** Time value in ms */ +typedef uint16_t ms_time_t; + +extern volatile ms_time_t time_ms; + +#define TIMEBASE_PERIODIC_COUNT 5 +#define TIMEBASE_FUTURE_COUNT 5 + + +// PID value that can be used to indicate no task +#define PID_NONE 0 + +/** Loop until timeout - use in place of while() or for(). break and continue work too! */ +#define until_timeout(to_ms) for(uint32_t _utmeo = ms_now(); ms_elapsed(_utmeo) < (to_ms);) + +/** Retry a call until a timeout. Variable 'suc' is set to the return value. Must be defined. */ +#define retry_TO(to_ms, call) \ + until_timeout(to_ms) { \ + suc = call; \ + if (suc) break; \ + } + +/** Must be called every 1 ms */ +void timebase_ms_cb(void); + + +// --- Periodic ----------------------------------------------- + +/** + * @brief Add a periodic task with an arg. + * @param callback : task callback + * @param arg : callback argument + * @param interval : task interval (ms) + * @param enqueue : put on the task queue when due + * @return task PID + */ +task_pid_t add_periodic_task(void (*callback)(void *), void *arg, ms_time_t interval, bool enqueue); + + +/** Destroy a periodic task. */ +bool remove_periodic_task(task_pid_t pid); + +/** Enable or disable a periodic task. Returns true on success. */ +bool enable_periodic_task(task_pid_t pid, bool cmd); + +/** Check if a periodic task exists and is enabled. */ +bool is_periodic_task_enabled(task_pid_t pid); + +/** Reset timer for a task */ +bool reset_periodic_task(task_pid_t pid); + +/** Set inteval */ +bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval); + + +// --- Future ------------------------------------------------- + + +/** + * @brief Schedule a future task, with uint32_t argument. + * @param callback : task callback + * @param arg : callback argument + * @param delay : task delay (ms) + * @param enqueue : put on the task queue when due + * @return task PID + */ +task_pid_t schedule_task(void (*callback_arg)(void *), void *arg, ms_time_t delay, bool enqueue); + + +/** Abort a scheduled task. */ +bool abort_scheduled_task(task_pid_t pid); + + +// --- Waiting functions -------------------------------------- + +/** Get milliseconds elapsed since start timestamp */ +static inline ms_time_t ms_elapsed(ms_time_t start) +{ + return time_ms - start; +} + +/** Delay N ms */ +static inline void delay_ms(ms_time_t ms) +{ + ms_time_t start = time_ms; + while ((time_ms - start) < ms); // overrun solved by unsigned arithmetic +} + +/** Get current timestamp. */ +static inline ms_time_t ms_now(void) +{ + return time_ms; +} + +/** Seconds delay */ +static inline void delay_s(uint16_t s) +{ + while (s-- != 0) { + delay_ms(1000); + } +} + +/** + * @brief Check if time since `start` elapsed. + * + * If so, sets the *start variable to the current time. + * + * Example: + * + * ms_time_t s = ms_now(); + * + * while(1) { + * if (ms_loop_elapsed(&s, 100)) { + * // this is called every 100 ms + * } + * // ... rest of the loop ... + * } + * + * @param start start time variable + * @param duration delay length + * @return delay elapsed; start was updated. + */ +bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration); + +#endif //TIMEBASE_H diff --git a/main.c b/main.c index 645e48e..62ede2f 100644 --- a/main.c +++ b/main.c @@ -13,10 +13,12 @@ #include "lib/spi.h" #include "lib/adc.h" #include "lib/debounce.h" +#include "lib/timebase.h" #include "pinout.h" #include "display.h" #include "leds.h" +#include "game.h" /** * Configure pins @@ -76,14 +78,6 @@ ISR(TIMER2_OVF_vect) // --- Debouncer slot allocation constants --- volatile bool booting = true; -volatile uint16_t time_ms = 0; - -// (normally those would be retvals from debo_add()) -#define DB_KEY_POWER 0 -#define DB_KEY_1 1 -#define DB_KEY_2 2 -#define DB_KEY_3 3 -#define DB_KEY_4 4 volatile uint16_t time_pwr_pressed = 0; @@ -102,26 +96,14 @@ void key_cb_power(uint8_t num, bool state) } } -/** Button state changed */ -void key_cb_button(uint8_t num, bool state) -{ - // TODO - // num - 1,2,3,4 - usart_puts("BTN "); - usart_tx('0'+num); - usart_tx(' '); - usart_tx('0'+state); - usart_puts("\r\n"); -} - void setup_debouncer(void) { // Debouncer config debo_add(PIN_PWR_KEY, key_cb_power); - debo_add(PIN_KEY_1, key_cb_button); - debo_add(PIN_KEY_2, key_cb_button); - debo_add(PIN_KEY_3, key_cb_button); - debo_add(PIN_KEY_4, key_cb_button); + debo_add(PIN_KEY_1, game_button_handler); + debo_add(PIN_KEY_2, game_button_handler); + debo_add(PIN_KEY_3, game_button_handler); + debo_add(PIN_KEY_4, game_button_handler); // Timer 1 - CTC, to 16000 (1 ms interrupt) OCR1A = 16000; @@ -129,25 +111,28 @@ void setup_debouncer(void) TCCR1B |= _BV(WGM12) | _BV(CS10); } +// SysTick ISR(TIMER1_COMPA_vect) { // Tick 1 ms debo_tick(); - time_ms++; + timebase_ms_cb(); + leds_show(); +} + +// Shut down by just holding the button - better feedback for user +void task_check_shutdown_btn(void *unused) { + (void)unused; - // Shut down by just holding the button - better feedback for user - if (debo_get_pin(DB_KEY_POWER) + if (debo_get_pin(0) // 0 - first && !booting && (time_ms - time_pwr_pressed > 1000)) { usart_puts("Power OFF\r\n"); // shut down pin_down(PIN_PWR_HOLD); } - - leds_show(); } - /** * Main function */ @@ -169,6 +154,10 @@ void main() // TODO verify the cpha and cpol. those seem to work, but it's a guess spi_init_master(SPI_LSB_FIRST, CPOL_1, CPHA_0, SPI_DIV_4); adc_init(ADC_PRESC_128); + + // clear + display_show(0,0); + setup_pwm(); setup_debouncer(); @@ -178,22 +167,12 @@ void main() pin_down(PIN_NEOPIXEL_PWRN); ws_init(); + add_periodic_task(task_check_shutdown_btn, NULL, 1, 0); + // globally enable interrupts sei(); - leds_set(0xFFFF00, 0x00FF00, 0x0000FF, 0xFF0000); + usart_puts("Starting game...\r\n"); - uint8_t cnt = 0; - - char buf[100]; - while (1) { - display_show_number(cnt); - cnt++; - cnt = cnt % 100; - - _delay_ms(150); - - sprintf(buf, "BRT = %d\r\n", disp_brightness); - usart_puts(buf); - } + game_main(); }