Skip to content

Instantly share code, notes, and snippets.

@lorsi96
Last active May 3, 2021 21:01
Show Gist options
  • Save lorsi96/6e8cd04a4524f96c9d43c0325dfa3468 to your computer and use it in GitHub Desktop.
Save lorsi96/6e8cd04a4524f96c9d43c0325dfa3468 to your computer and use it in GitHub Desktop.
Debouncing FSM using a FSMTable
/*=============================================================================
* 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