Using Finite State Machine

Hello!

I think I have the concept of using FSM from LINK incorrectly implemented.

Could someone look over my work and tell me what I am doing wrong? I am trying to use a single button to move from one state machine to the other. So, I start at no LED's turning on, then move into turning the green LED on after pressing the button HIGH and checking my current state. Then to turn on the red LED I check if the button is HIGH and am I currently in the green LED state. And so on.

#include <Arduino.h>
#include <FiniteStateMachine.h>
#include <Streaming.h>

#define buttonPin 32
#define ledWhitePin 31
#define ledRedPin 30
#define ledGreenPin 29

int buttonState = 0;
unsigned long previousGreenMillis = 0;
unsigned long previousRedMillis = 0;

#define ledTimer 1000

State nothing = State(noLED);
State go = State(greenLED);
State hault = State(redLED);

FSM Display = FSM(nothing);

void setup() {
  Serial.begin(115200);
  pinMode(ledGreenPin, OUTPUT);
  pinMode(ledRedPin, OUTPUT);
  pinMode(ledWhitePin, OUTPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  buttonState = digitalRead(buttonPin);
  if(buttonState == HIGH && Display.isInState(nothing)){
    Display.immediateTransitionTo(go);
  }
  else if(buttonState == HIGH && Display.isInState(go)){
    Display.immediateTransitionTo(hault);
  }
  else if(buttonState == HIGH && Display.isInState(hault)){
    Display.immediateTransitionTo(nothing);
  }
  else{
    Serial << "The End" << endl;
  }
}

void noLED(){
  digitalWrite(ledGreenPin, LOW);
  digitalWrite(ledRedPin, LOW);
}

void greenLED(){
  unsigned long currentMillis = 0;
  if (currentMillis - previousGreenMillis >= ledTimer){
    previousGreenMillis = currentMillis;
    digitalWrite(ledGreenPin, HIGH);
  }
}

void redLED(){
  unsigned long currentMillis = 0;
  if (currentMillis - previousRedMillis >= ledTimer){
    previousRedMillis = currentMillis;
    digitalWrite(ledRedPin, HIGH);
  }
}

1 Like

What does the code not do correctly?

I take it a MEGA is being used?

The board I am using is the Teensy4.1, but this simple implementation should work on any Arduino compatible board.

It goes into the else function "The End". Which makes sense when I am not pressing the button, but even after pressing the button it does nothing.

buttons are typically connected between the pin and ground, the pin configured as INPUT_PULLUP to use the internal pullup resistor which pulls the pin HIGH and when pressed, the button pulls the pin LOW.

consider

// simple button processing

const byte PinBut = A1;
const byte PinLeds [] = { 10, 11, 12 };
const int  Nled = sizeof(PinLeds);

byte butState;

enum { Off = HIGH, On = LOW };

enum { ST_OFF, ST_1, ST_2, ST_3, ST_4 };
int state = 0;

// -----------------------------------------------------------------------------
bool
getButton (void)
{
    byte but = digitalRead (PinBut);
    if (butState != but)  {     // check that button state changed
        butState = but;
        delay (10);         // debounce

        if (LOW == but)     // button pressed
            return true;
    }
    return false;
}

// -----------------------------------------------------------------------------
void loop (void)
{
    switch (state) {
    case ST_OFF:
        if (getButton ())  {
            digitalWrite (PinLeds [0], On);
            state = ST_1;
        }
        break;

    case ST_1:
        if (getButton ())  {
            digitalWrite (PinLeds [0], Off);
            digitalWrite (PinLeds [1], On);
            state = ST_2;
        }
        break;

    case ST_2:
        if (getButton ())  {
            digitalWrite (PinLeds [1], Off);
            digitalWrite (PinLeds [2], On);
            state = ST_3;
        }
        break;

    case ST_3:
        if (getButton ())  {
            digitalWrite (PinLeds [2], Off);
            state = ST_OFF;
        }
        break;
    }
}

// -----------------------------------------------------------------------------
void setup (void)
{
    Serial.begin (9600);

    for (unsigned n = 0; n < Nled; n++)  {
        digitalWrite (PinLeds [n], Off);
        pinMode      (PinLeds [n], OUTPUT);
    }

    pinMode     (PinBut, INPUT_PULLUP);
    butState  = digitalRead (PinBut);
}
1 Like

@gndardiuno

I would suggest to you that learning how to code a basic FSM without a library "helping" you would be a Good Idea.

Having said that, I didn't see something in your code, and looked at the example on the GitHub page.

  //THIS LINE IS CRITICAL
  //do not remove the stateMachine.update() call, it is what makes this program 'tick'
  stateMachine.update();

Did you do something that means your code should work without updating the state machine as is deemed critical in the example?

Study the example more, or Better Yet, google and wander around a bit and find FSM code that appeals to you implemented with no library.

a7

I have the same problem as gcjr: why do you use that library ? Does it make something easier ? Now you not only have to understand the Finite State Machine, but also the library.

Tutorial for Finite State Machine: https://majenko.co.uk/blog/our-blog-1/the-finite-state-machine-26

I tried the make the most simple sketch possible. It turned out to be the same as the sketch by gcjr, but in a different style:

// Very simple Finite State Machine
// Forum: https://forum.arduino.cc/t/using-finite-state-machine/1114323/
// Tutorial: https://majenko.co.uk/blog/our-blog-1/the-finite-state-machine-26
// This Wokwi project: https://wokwi.com/projects/361809673037018113

const int buttonPin = 2;
const int redLedPin = 12;
const int greenLedPin = 13;

int lastButtonValue = HIGH;       // default HIGH is not pressed.

enum
{
  IDLE,
  RED,
  GREEN,
} state;

void setup() 
{
  pinMode( buttonPin, INPUT_PULLUP);
  pinMode( redLedPin, OUTPUT);
  pinMode( greenLedPin, OUTPUT);
}

void loop() 
{
  // ------------------------------------------------------
  // Gather all the information from buttons and sensors.
  // ------------------------------------------------------
  // The variable 'buttonPressed' is passed on to the Finite State Machine.
  bool buttonPressed = false;        // default: button not pressed
  
  // Checking the event that the button has just been pressed
  // can be done with the finite state machine.
  // I decided to that with a variable.
  int buttonValue = digitalRead( buttonPin);
  if( buttonValue != lastButtonValue) // something changed ?
  {
    if( buttonValue == LOW)          // pressed just now ?
      buttonPressed = true;
    lastButtonValue = buttonValue;   // remember for the next time
  }

  // ------------------------------------------------------
  // Process the information with a Finite State Machine
  // ------------------------------------------------------
  // Changing the leds can be done with a intermediate state,
  // depending on how much needs to be done before going
  // to a new state.
  switch( state)
  {
    case IDLE:
      if( buttonPressed)
      {
        digitalWrite( redLedPin, HIGH);
        state = RED;
      }
      break;
    case RED:
      if( buttonPressed)
      {
        digitalWrite( redLedPin, LOW);
        digitalWrite( greenLedPin, HIGH);
        state = GREEN;
      }
      break;
    case GREEN:
      if( buttonPressed)
      {
        digitalWrite( redLedPin, HIGH);
        digitalWrite( greenLedPin, LOW);
        state = RED;
      }
      break;
  }

  delay(20);   // slow down the sketch, also to avoid button bouncing
}

Try the sketch in Wokwi:

This is no fun :frowning_face: Should the leds blinks ? or a buzzer for one second somewhere ? :smiley:

1 Like

i don't see a need for a library to implement a state machine, each seems so unique, most, like the OP's, are just sequencers. there are 2 different types: where the action is determined by the state, the other where the action depends on the transition. so having a fuller understanding allows one specific to the application (perhaps a combination of both

here's a more elaborate, table based approach with multiple states and multiple stimuli. I've seen reviews that spend most of their time on the tables, not the action functions

//
// example state machine
//

#include <stdio.h>
#include "stateMach.h"

// ------------------------------------------------
Status
a (void* a)
{
    printf ("%s:\n", __func__);
    return OK;
}

// ------------------------------------------------
Status
b (void* a)
{
    printf ("%s:\n", __func__);
    return OK;
}

// ------------------------------------------------
Status
c (void* a)
{
    printf ("%s:\n", __func__);
    return OK;
}

// ------------------------------------------------
Status
__ (void* a)
{
    printf ("%s:\n", __func__);
    return OK;
}

// --------------------------------------------------------------------

#define N_STATE     3
#define N_STIM      2

typedef enum { S0, S1, S2 } State_t;

typedef Status(*Action_t)(void*) ;

State_t smTransitionTbl [N_STATE] [N_STIM] = {
    {   S1, S2, },
    {   S0, S2, },
    {   S2, S1, },
};


Action_t smActionTbl [N_STATE] [N_STIM] = {
     {  a,  b },
     {  c, __ },
     { __,  a },
};

// ------------------------------------------------
char *msg1 [] = {
    "State machine has 3 states and 2 stimuli.",
    "It has the following state transition and action routine tables.",
    "A __() represents do nothing and is easily identified in table.",
    "Each row is indexed by a state and each column a stimulus.",
    "Of course, meaningful action routine names are helpful.",
};

char *msg2 [] = {
    "Enter valid stimuli [0-1]:"
};

void
smHelp (void)
{
    for (int i = 0; i < sizeof(msg1)/sizeof(char*); i++)
        printf ("  %s\n", msg1 [i]);

    for (int state = 0; state < N_STATE; state++)  {
        printf ("%8s", "");
        for (int stim = 0; stim < N_STIM; stim++)
            printf (" %d", smTransitionTbl [state][stim]);

        printf ("%8s", "");
        for (int stim = 0; stim < N_STIM; stim++)  {
            if (a == smActionTbl [state][stim])
                printf ("  a()");
            else if (b == smActionTbl [state][stim])
                printf ("  b()");
            else if (c == smActionTbl [state][stim])
                printf ("  c()");
            else
                printf (" __()");
        }

        printf ("\n");
    }

    for (int i = 0; i < sizeof(msg2)/sizeof(char*); i++)
        printf ("  %s\n", msg2 [i]);

}

// ------------------------------------------------
static State_t _smState  = S0;

Status
smEngine (Stim_t stim)
{
    Action_t   func;
    State_t    last = _smState;

    if (N_STIM <= stim)
        return ERROR;

    func        = smActionTbl [_smState] [stim];
    _smState    = smTransitionTbl [_smState] [stim];

    printf ("    stim %d, transition from state %d to %d, exec ",
        stim, last, _smState);

    return (*func) (NULL);
}

stateMach.h

#ifndef SM_ENGINE
# define SM_ENGINE

typedef enum { ERROR = -1, OK } Status;
typedef unsigned int            Stim_t;

void   smHelp   (void);
Status smEngine (Stim_t stim);

#endif
1 Like

This is a not precise enough description to make sure to understand what functionality you want.

I write in my own words what I am guessing/assuming what you want:
start with mode 1
mode 1: green/red led off - if button is pressed switch to mode 2
mode 2: green led on - if button is pressed switch to mode 3
mode 3: switch off green led and switch on red led - if button is pressed again switch to mode 1

if another button-press is done switch to mode 2 etc.

is this a correct description of the wanted functionality?

Could you add stateMach.h to your post?

1 Like

Both offered simple FSMs use no timing with millis() as you have done.

What is greenLED() supposed to do beyond turning on the green LED that somehow involves a ledTimer period?

a7

1 Like

I think I should reconfigure the button as my first step. I am using LED's right now for simplicity to test if I can even move from one state to the other. And I was using millis() bc I didn't want to use delay(). Also since I don't have much experience with FSM I figured a library could help me out.

Essentially I was planning to use the state machines to develop a main page and settings page on a 2.42" OLED display.

I want to have two pages on the display. The default page is the main page. The main page shows a voltage reading captured from a SPI device as default. The settings page shows the different types of readings I can show on the main page. For example current and power. So I was thinking I could use a FSM and a button to switch between the main and settings page. But in the settings page I also want to use the same button to be able to select which other reading I want to show up on the main page.

This is what led me down testing LED's and a FSM library.

OK so now you have given an overview about your project. This is good.

The thing behind the scene of a state-machine is rather simple.
This is the reason why most users here say it's not nescessary to use a library

I have written a small tutorial about state-machines.
What I claim as the advantage of my tutorial is that at first it gives an normal worded overview about what the state-machine is doing. And then there comes the code which prints to the serial monitor what is going on in the state-machine.

The intention of this is to make it easier to understand.
I'm very interested in questions of newcomers because the questions of newcomers point to that parts that shold be improved for even more easier understanding

best regards Stefan

this doesn't need a state machine.

i built a relatively elaborate menu system a while back using tables and an array that kept track of the sub-menu at each level allowing you to go back to the previous level/sub-menu.

Thanks @StefanL38 I can look into this.

I wasn't sure what else to do and I knew FSMs was one option. Could you link me to a simple example of how this works?

i've found that most people find even relatively simple menus daunting. most implement them as one or more switch statements.

i've also found data driven approaches easier non-programmers to understand and maintain

consider

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

// -----------------------------------------------------------------------------
#define NO_BUT  -1
byte butState [N_BUT];

int
butChk ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return NO_BUT;
}

// ---------------------------------------------------------
void
butInit (void)
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }
}

// -----------------------------------------------------------------------------
enum { B_Sel,  B_Adv,  B_Back };
enum { T_Menu, T_Func, T_Back, T_Last };

struct MenuItem {
    int         type;
    MenuItem   *p;
    const char *text;
};

typedef MenuItem*       M;
typedef void (*F) (void);

struct MenuStk {
    MenuItem    menu;
    int         idx;
};
int   stkIdx;

// ---------------------------------------------------------
void back   (void) { Serial.println (__func__); }

void func1  (void) {
    Serial.println (__func__);

    for (unsigned n = 0; n < sizeof(pinsLed); n++)
        digitalWrite (pinsLed [n], ! digitalRead (pinsLed [n]));
}    

void func11 (void) { Serial.println (__func__); digitalWrite (pinsLed [0], On); }
void func12 (void) { Serial.println (__func__); digitalWrite (pinsLed [1], On); }
void func13 (void) { Serial.println (__func__); digitalWrite (pinsLed [2], On); }

void func21 (void) { Serial.println (__func__); digitalWrite (pinsLed [0], Off); }
void func22 (void) { Serial.println (__func__); digitalWrite (pinsLed [1], Off); }
void func23 (void) { Serial.println (__func__); digitalWrite (pinsLed [2], Off); }

// ---------------------------------------------------------
MenuItem sub1 [] = {
    { T_Func, (M)func11,  "Func-11" },
    { T_Func, (M)func12,  "Func-12" },
    { T_Func, (M)func13,  "Func-13" },
    { T_Back, 0,          "back"    },
    { T_Last, 0,       0        },
};

MenuItem sub2 [] = {
    { T_Func, (M)func21,  "Func-21" },
    { T_Func, (M)func22,  "Func-22" },
    { T_Func, (M)func23,  "Func-23" },
    { T_Back, 0,          "back"    },
    { T_Last, 0,       0        },
};

MenuItem top [] = {
    { T_Menu, sub1,    "Sub-1" },
    { T_Menu, sub2,    "Sub-2" },
    { T_Func, (M)func1,  "Func-1" },
    { T_Back, 0,         "back"   },
    { T_Last, 0,       0        },
};

MenuItem *menu   = top;
int       idx    = 0;

// -----------------------------------------------------------------------------
void
loop ()
{
    void (*f) (void);

    switch (butChk ())  {
    case NO_BUT:
        return;

    case B_Adv:
        if (T_Last == menu [++idx].type)
            idx = 0;
        break;

    case B_Back:
        if (0 < idx)
            idx--;
        break;

    case B_Sel:
        switch (menu [idx].type)  {
        case T_Func:
            f = (F) menu [idx].p;
            f ();
            // drop thru

        case T_Back:
            menu = top;
            idx  = 0;
            break;

        case T_Menu:
            menu = menu [idx].p;
            idx  = 0;
            break;
        }
    }

    Serial.println (menu [idx].text);
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    butInit ();

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }

    Serial.println (menu [idx].text);
}

@gcjr
the TO was asking for

not for the most sophisticated one.

@gndardiuno

  • do you already know what arrays are?
  • do you know what pointers in general are?
  • do you know what function-pointers are?
  • do you know what the ++var/--var-operator is?
  • do you know what the var++/var-- operator is?

if not these were the things to learn to understand gcjrs example

what i posted is hardly the "most sophisticated one"

it's table driven and supports either a sub-menu or action function

The other basic concept for programming a menu is to have a special kind of variables.
These variables are used to store the path from main-menu down into the sub-sub-sub-menues and are used as some kind of a "map" to guide to the different locations of each sub-sub-sub-menu.

As these variables hold similar informations it would be nice to declare these variables in a compact way. On a plane each seat has a number to guide the passengers to their reserved seat.
They are all seats and for distinguishing them from each other a number is used. Seat[1], Seat[2], Seat[3], etc..

The same basic principle is used for these variables that are called "array".
You can read about how to use arrays in part 17 of this tutorial

From the fact that it is part 17 you can conclude there are quite some more basic things you should know before you start learning using arrays. My Opinion is if you have understood the basic concept of variables and the basic concept of for-loops you can jump ahead reading part 17
explaining arrays.

best regards Stefan

Please restrict the replies to helping @gndardiuno everyone.