Last active
May 3, 2021 21:01
-
-
Save lorsi96/6e8cd04a4524f96c9d43c0325dfa3468 to your computer and use it in GitHub Desktop.
Debouncing FSM using a FSMTable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*============================================================================= | |
* Copyright (c) 2021, Lucas Orsi <lorsi@itba.edu.ar> | |
* All rights reserved. | |
* License: mit (see LICENSE.txt) | |
* Date: 2021/04/01 | |
* Version: 1.0 | |
*===========================================================================*/ | |
/*=====[Debounce .h]=================================*/ | |
#ifndef __DEBOUNCE_H__ | |
#define __DEBOUNCE_H__ | |
#include <stdint.h> | |
#include <stddef.h> | |
#include "sapi.h" | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
/** Tidy way of saying "reference to a function that "receives nothing and returns nothing" | |
* For example, if you have a function named "void doSomething()", you can create a variable | |
* like this one: | |
* Runnable_t myFunctionReference = doSomething; | |
* And then you can pass myFunctionReference to #initDebounceFSM for it to be run when | |
* a TEC key is pressed. | |
*/ | |
typedef void(*Runnable_t)(); | |
/** | |
* @brief initializes the FSM. Should be called only once in the "setup" portion of the code. | |
* @param button that will trigger the event. i.e. TEC1 | |
* @param function that will run when the button is pressed. Must receive and return void. | |
*/ | |
void initDebounceFSM(const gpioMap_t button, const Runnable_t cb); | |
/** | |
* @brief nonblocking function that keeps the FSM going. Meant to be called in the "superloop" as many times as possible. | |
*/ | |
void spinDebounceFSM(); | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif /* __DEBOUNCE_H__ */ | |
/*=====[Debounce .c]=================================*/ | |
#include "debounce.h" | |
#define DEBOUNCE_DELAY_MS 40 | |
/** States */ | |
typedef enum { | |
DEBOUNCE_PRESSING, | |
DEBOUNCE_UNPRESSING, | |
DEBOUNCE_NOT_PRESSED, | |
DEBOUNCE_PRESSED, | |
} DebounceState_t; | |
/** Events that can occur */ | |
typedef enum { | |
EV_NOT_PRESSED, // The button is currently not being pressed. | |
EV_PRESSED, // The button is currently being pressed. | |
EV_NONE // Default state -> not used, since the button is always either pressed or not pressed. | |
} Event_t; | |
static void doNothing() {} | |
static DebounceState_t currentState_; // | |
static delay_t debDelay_; // debounce delay | |
static Event_t ev_ = EV_NONE; // stores the latest event | |
static Runnable_t callback_ ; // stores a function to be called when the button is pressed | |
static Runnable_t doNothingPtr_ = doNothing; | |
static gpioMap_t debButton_ = TEC1; // button to be "debounced". TEC1 by default. | |
/** Represents an entry in the FSM state table. */ | |
typedef struct { | |
// This reads as follows : | |
DebounceState_t current; // If I'm in this state... | |
Event_t event; // ... and this event occurs ... | |
DebounceState_t next; //... now I should go to this state ... | |
Runnable_t* action; // ... and execute this action ... | |
} FSMEntry_t; | |
/** This table contains all the entries that describe our system | |
* | |
* For example: | |
* {DEBOUNCE_NOT_PRESSED, EV_PRESSED, DEBOUNCE_PRESSING, &doNothingPtr_} | |
* Means that if the button was not pressed (DEBOUNCE_NOT_PRESSED) and | |
* after reading its state again is pressed (EV_PRESSED) we should | |
* go to the "DEBOUNCE_PRESSING" state and "doNothing". | |
* | |
* | |
* */ | |
FSMEntry_t fsmTable[] = { | |
{DEBOUNCE_NOT_PRESSED, EV_PRESSED, DEBOUNCE_PRESSING, &doNothingPtr_}, | |
{DEBOUNCE_PRESSED, EV_NOT_PRESSED, DEBOUNCE_UNPRESSING, &doNothingPtr_}, | |
{DEBOUNCE_PRESSING, EV_PRESSED, DEBOUNCE_PRESSED ,&doNothingPtr_}, | |
{DEBOUNCE_PRESSING, EV_NOT_PRESSED, DEBOUNCE_NOT_PRESSED, &doNothingPtr_}, | |
{DEBOUNCE_UNPRESSING, EV_PRESSED, DEBOUNCE_NOT_PRESSED, &doNothingPtr_}, | |
{DEBOUNCE_UNPRESSING, EV_NOT_PRESSED, DEBOUNCE_NOT_PRESSED, &callback_}, // We trigger the callback when the user releases the button. | |
}; | |
void initDebounceFSM(const gpioMap_t button, const Runnable_t cb) { | |
debButton_ = button; | |
callback_ = cb; | |
currentState_ = DEBOUNCE_NOT_PRESSED; | |
delayConfig(&debDelay_, DEBOUNCE_DELAY_MS); | |
} | |
/** This shall be run as fast as possible in the application's "superloop". */ | |
void spinDebounceFSM() { | |
// We check for events here. | |
// In the debounce case, the only event is whether a press or release | |
// occurred in #DEBOUNCE_DELAY_MS. | |
if(delayRead(&debDelay_)) { | |
ev_ = gpioRead(TEC1) ? EV_NOT_PRESSED : EV_PRESSED; | |
} | |
// If there are no events, we return. | |
if(ev_ == EV_NONE) { | |
return; | |
} | |
//... otherwise, we process the event. | |
for(uint8_t i=0; i<sizeof(fsmTable)/sizeof(FSMEntry_t); i++) { | |
// if the current combination of state and event are in the table.... | |
if(fsmTable[i].current == currentState_ && fsmTable[i].event == ev_) { | |
(*(fsmTable[i].action))(); // ... we execute the action ... | |
currentState_ = fsmTable[i].next; // ... and we move to the next state. | |
} | |
} | |
// We clear the event, so as not to read the same event over and over. | |
ev_ = EV_NONE; | |
} | |
/*=====[Main .c]=================================*/ | |
void onButtonPress() { | |
gpioToggle(LEDB); | |
} | |
int main( void ) | |
{ | |
boardInit(); | |
// onButtonPress wil run when TEC1 is pressed | |
initDebounceFSM(TEC1, onButtonPress); | |
for(;;) { | |
spinDebounceFSM(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment