Finite state machine on arduino with Serial Interrupts

Hi,

I am having a bit of an issue, I am not sure how to code this.
I have an arduinoMega controlling a mechanism using a finite state machine and I would like to connect it to a computer with could communicate with it via serial using python.
The only command that the computer needs to send to the arduino is a status request and the arduino needs to reply with the current status of the finite state machine.

I simplified my state machine(it turns the LED on and OFF) for the sake of the discussion:

here is my arduino code:


  int x = 0;
  int y = 0;
  int stat = 0;
  int S_info;


void setup(void) 
{
  pinMode(12, OUTPUT); //led
  Serial.begin(115200);
  Serial.setTimeout(1);
}
 
void FSM()
{

  static enum { one, two } state = one;

 
 
  switch (state)
  {
    case one:
      digitalWrite(12, LOW);
      x=x+1;
      stat =1;
      if (x ==100)
      {
        state = two;
        x = 0;
        }
      break;
 
    case two:   
      digitalWrite(12, HIGH);
      y = y +1;
      stat = 2;
      if (y == 1000)
      {
        state = one;
        y = 0;
        }
      break;
 
    default:
      state = one;
      break;
  }
} 



void loop(void) 
{

 //run FSM
  FSM();

//serial connection

      S_info = Serial.readString().toInt();
      if (S_info ==1)
      {
        Serial.print(stat);
      }
  
}

My python code so fare is the following:

# Importing Libraries
import serial
import time
import sys

arduino = serial.Serial(port='/dev/ttyUSB0', baudrate=115200, timeout=.1)

def write_read(cmd):
    arduino.write(bytes(cmd, 'utf-8'))
    time.sleep(0.05)
    data = arduino.readline()
    return data

for timer in range(1000):
    status = write_read(1)
    print(status)

When I run it, I don't get anything. I already ran the codes when the arduino is a slave like this example and it works: https://create.arduino.cc/projecthub/ansh2919/serial-communication-between-python-and-arduino-e7cce0

I just don't know how to deal with my issue,

Thank you very much for any help!

I suspect that this function is blocking. Implement another machine that reads and interprets Serial input without blocking the controller.

While you are contemplating that, some minutae:

You are repeatedly setting the LED and also repeatedly assigning a value to 'stat'. It's harmless in this case, but futile. The range checks for x and y should not use equality alone as a bounds check because it might be skipped. It works here but it's safer to use "greater than or equal to" to protect against future program changes. Also, you can format your code using ctrl-T in the IDE. I fixed these issues, you only have to ensure the correct starting state for the LED before the state machine runs.

switch (state)
{
case one:
  x = x + 1;
  if (x >= 100)
  {
    digitalWrite(12, HIGH);
    stat = 2;
    state = two;
    x = 0;
  }
  break;

case two:
  y = y + 1;
  if (y >= 1000)
  {
    digitalWrite(12, LOW);
    stat = 1;
    state = one;
    y = 0;
  }
  break;

default:
  state = one;
  break;
}

One more thing, "one" and "two" surely don't describe what the states do. It is better to give them meaningful names, otherwise you might as well just use integers.

Hi thanks,

I'll try to put the serial read in an FSM of it`s own.

How would you recommend?

I am thinking something of this (putting pseudocode):

if ( serial.available())
{
S_info = Serial.readString().toInt();
}

It should only go in the if statement if something was sent to the serial bus correct?

@anon57585045
I agree that the state machine is a bit stupid right now, it's because I took my massive state machine and just dummed it down, the x and y will be replaced by limit switches and since I am running steppers, the x and y will store cartesian positions, they are just place holders to simulate running the operation multiple times until I get the to the point I need.

Yes. And only be in there for the time it takes for the rest of the integer to come in.

But you might try skipping the String step, look at

https://www.arduino.cc/reference/en/language/functions/communication/serial/parseint/

for gathering an integer at a slightly lower level.

a7

Again readString() is blocking!

Your FSM should collect characters (if available()) and check for digits. Should non-digits be ignored before the first digit? How many digits should be expected? Is there a definite end-of-number marker? Many questions to be answered and handled in your FSM.

I am not 100% sure of what is happening when a serial message is sent to the arduino while the arduino is doing another operation, will the arduino store it in the buffer until I get to the line to check the serial bus or will it be lost?

the codes that will be received over serial will only be integers, from 1 to 5.

basically, we want to be able to code the following instructions:
1 -> send back status update
2 -> reset system
3 -> debug mode
4 -> ...
5 -> ...

Yes, Serial input is interrupt driven. Nothing will get lost unless the buffer overflows. Serial.available() will tell you how many characters already have been received.

perfect, so if I only send one integer over the bus, then it will be stored. Would the

S_info = Serial.readString().toInt();

line work in that case or would it still block the code?

would replacing the line above with this one solve the issue:

S_info=Serial.parseInt();

This should only read the integer that is in the buffer and then continue with the code correct?

Read serial without blocking.

Also your python program is doing “read line”, but your arduino sketch isn’t transmitting an end-of-line character.
It’s not immediately clear how that interacts with the short timeout, but it could be a problem.

The problem is, it's only non blocking if you happen to perform your read after all the bytes in your received message have been received and are waiting in the buffer. That is not realistic. There is no sensible way to ensure those conditions. Normally, serial is so slow and asynchronous that you will find either one, or zero characters waiting there.

1 Like

there is a library called SafeString which can be installed from the Arduino-IDE library manager.
The name SafeString is program.

You can assign a SafeString as many characters as ever possible. This can never cause an overflow. If your program trys to add more characters to a SafeString than the SafeString can hold these characters are just dropped. You as the user will see the characters are dropped but the code will not crash.
SafeString offers almost the same comfort as the variabletype String but without crash-danger

SafeString offers string-debug-features and non-blocking serial receiving.
here is a tutorial about non-blocking serial receiving
https://www.forward.com.au/pfod/ArduinoProgramming/Serial_IO/index.html#userCmds

best regards Stefan

Not tailored to your needs but below should give you the idea

  char cmd = 0;
  if (Serial.available() > 0) {
    cmd = Serial.read();
  }

  if (cmd == 'S') {
    Serial.println(state);
  }

A complex library for a simple problem.

1 Like

Hello gastonlegaffes
Take a view to get some ideas:

Have a nice day and enjoy programming in C++ and learning.
Дайте миру шанс!

provide a "simple" copy & paste solution
or at least a few links to tutorials that explain the knowledge needed.

best regards Stefan

The name of SerialEvent is misleading. It's not an asynchronous event handler.

I'd expect an event handler attached to the RX interrupt.

Hello Doc
You are right.
The SerialEvent sketch is handling the rx of data without blocking.
A timeout handler shall be added for a better performance in real life.
An asynchronous event handler could be designed by using structured arrays with function pointers.

Have a nice day and enjoy programming in C++ and learning.
Дайте миру шанс!

That was done; not quite tailored to OP's needs but sufficient (in my opinion) to get started.

And the obvious tutorial is Serial Input Basics - updated.

rhetoric question: which code will be used by newcomers?

the code that comes close to their needs combined with easyness to understand how to apply it.

I have written a small tutorial with a demo-code how to use SafeString non-blocking serial input

and then derived from there a code that demonstrates LED switching on/off using serial commands that set states of a fsm
That is what I call a starting point

#include "SafeStringReader.h"

createSafeStringReader(myInputReader, 5, " ,\r\n");

const byte LED_pin = 13;

const byte fsm_idling         = 1;
const byte fsm_switchLEDOn    = 2;
const byte fsm_waitWithLEDOn  = 3;
const byte fsm_SwitchLEDOFF   = 4;

byte myStateVar;

unsigned long myWaitTimer;

const unsigned long LedOnTime = 10000;

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

void ManageSerialCommands() {

  if (myInputReader.read()) { // check if all characters of a command including delimiter are received
    if (myInputReader == "ON") {
      myStateVar = fsm_switchLEDOn;
    }

    if (myInputReader == "OFF") {
      myStateVar = fsm_SwitchLEDOFF;
    }
  }
}


void setup() {
  pinMode(LED_pin, OUTPUT); // no comment needed because of SELF-explaining name  led
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  Serial.println( F("enter command ON or OFF to switch LED") );

  SafeString::setOutput(Serial); // enable error messages and SafeString.debug() output to be sent to Serial
  myInputReader.connect(Serial); // where SafeStringReader will read from in this demo the standard Serial
  myInputReader.echoOn(); // echo back all input, by default echo is off
  myStateVar = fsm_idling;
}


void FSM() {

  switch (myStateVar) {

    case fsm_idling:
      // just do sit and wait for serial commands
      break;

    case fsm_switchLEDOn:
      digitalWrite(LED_pin, HIGH);
      myWaitTimer = millis(); // initialise Timer-variable with actual value of millis()
      myStateVar = fsm_waitWithLEDOn;
      break;

    case fsm_waitWithLEDOn:
      if ( TimePeriodIsOver(myWaitTimer, LedOnTime) ) { // check if waitingtime is over
        // if number of milliseconds stored in LedOnTime have passed by
        Serial.println( F("waiting time over chnage to switching LED off") );
        myStateVar = fsm_SwitchLEDOFF;
      }
      break;

    case fsm_SwitchLEDOFF:
      digitalWrite(LED_pin, LOW);
      Serial.println( F("LED switched off") );
      Serial.println( F("enter command ON or OFF to switch LED") );
      myStateVar = fsm_idling;

    default:
      myStateVar = fsm_idling;
      break;
  }
}


void loop() {
  ManageSerialCommands();
  FSM();
}

best regards Stefan