Multiple state machines

I don’t know if this is the best place for this, but on my project I’ve got a couple of things going on. First, my little robot has a couple of different modes, like IR control, Bluetooth control, line tracking, along with a boot mode and loiter. Then I have how it drives, forward, backward, reverse, etc… then there are two independent actions, ears moving and tail moving.

I created all of these separately, basically one sketch for each of those 3 things. Mode, movement, and ears because these are really all independent actions.

So the question is, when I put all this together, can I leave them all as separate state machines? I was starting to look at putting the code together and combining all those machines into one was getting confusing.

Your modular code is exactly the right path for scaling programs (combining code). Functions can be improved (or destroyed) without causing problems with the main function until the function is repaired.

When you combine the functions into one program, you will want some variables to live and die inside a function, some variables to live regardless of location in the code, and some variables to be passed from function to function.

An advanced programming technique is to use structures. By passing structures, you can pass everything about a device (mode, movement, position, et c.) rather than passing and returning only one piece of data (mode OR movement OR position OR et c.).

1 Like

Yes, you can provided you write non-blocking code. See e.g. Task Macros to make each machine a flyweight non-blocking task.

1 Like

did you include delays and long running while / for loops into your existing code or are they coded really as non blocking state machines ?

Post your code!

Multiple FSMs is def the way to go, and only depends on everyone playing nice, and the total computational load be within the resources of the processor.

It's fun when you need to figure out how they might talk to each other as well as doing whatever they are up to.

Post your code!

a7

1 Like

Here is the code... Keep in mind, I'm making all this up as I go so some of this might not do what I think it does. Also, I comment the shit out of my code, asking myself questions, saying what I think things do, there might be some spelling issues.

Some things have not been implemented yet. I stopped combining when I had the question about the multiple states. There are a few empty functions and functions that are never called

one question about my code. At some point in one function is use delayMicroseconds(), I have no clue why I'm using this and if it blocks the code just like delay() does

#include <IRremote.hpp> // to make the ir remote work
#include <Servo.h> // to make the servo work
#include <elapsedMillis.h> // to make the timers work

#define IR_RECEIVE_PIN 3 // pin IR receiver it attached to
#define trigPin A1 // ultrasonic sensor trig pin
#define echoPin A0 // ultrasonic sensor echo pin
#define motorPin1 2 // digital motor pin
#define motorPin2 4 // digital motor pin
#define motorPin3 7 // digital motor pin
#define motorPin4 8 // digital motor pin
#define motorPin5 5 // analog motor pin
#define motorPin6 6 // analog motor pin
#define laserPin A5 // pin for laser emmitter
#define servoPin A2 // pin for the servo motor

Servo myServo;  // creates the variable to control the servo

// The list of possible states and modes of our system
enum {EARS, FORWARD, BACKWARD, LEFT, RIGHT, STOP, LASER} currentState; // the drive states - should I move LASER to a mode?
enum {BOOT, WAIT, LOITER, DRIVE} currentMode; // the modes states
enum {START_EARS, STOP_AT_MIDDLE, STEP_TO_MIN, STEP_TO_MIDDLE} earMode; // special stuff just for the ears - Should I rename this?

// globals
int speedK9 = 200; // sets the speed of the motors
const unsigned StepPeriod = 0; // steps needed for turning the ears - does this need to be here?
const int StepIncrement = 2; // needed for turning the ears - does this need to be here?
const uint8_t MAX_SWEEP=180; // the maximum turn of the ear servo
const uint8_t MIN_SWEEP=0; // the minimum turn of the ear servo
const uint8_t MID_SWEEP=90; // middle of the ear servo
const uint32_t  STEP_RATE= 10; // speed of the ears - is this still used?
const int turnDelay = 1000; // the timer for turning - works better here so it can be used in both the left turn and the right turn
elapsedMillis taskTimer = 3000; // for firing the laser - does this need to be here?
elapsedMillis turnTimer = 3000; // timer for the turns - works better here so it can be used in both the left turn and the right turn

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200); // initializes the serial port to send data to the monitor
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // starts the IR receiver
  pinMode(trigPin, OUTPUT); // Starts the Ultrasonic Sensor
  pinMode(echoPin, INPUT); // Starts the Ultrasonic Sensor
  myServo.attach(servoPin); // Starts the servo
  //myServo.write(90); // puts the servo in the middle (90 degree) position - due to earMode, is this even needed?
  currentState = STOP; // sets the start currentState at STOP - keeps him from going anywhere
  currentMode = BOOT; // sets the start currentMode as BOOT - not that it does anything there yet
  earMode = STOP_AT_MIDDLE; // sets the start earMode as STOP_AT_MIDDLE - this should put the ears at 90?
}

void checkDistance() {
  // Sets the minimun forward distance needed to not hit something
  const int d_min = 20; // 20mm? - should I move it to the top to make it easier to change?
  float duration, distance;
  
  digitalWrite(trigPin, LOW);  
	delayMicroseconds(2);  // when did I start using delayMicroseconds? - is this blocking the code?
	digitalWrite(trigPin, HIGH);  
	delayMicroseconds(10); // when did I start using delayMicroseconds? - is this blocking the code?
	digitalWrite(trigPin, LOW);  
  duration = pulseIn(echoPin, HIGH); 

  distance = (duration*.0343)/2;
 // sanity check
 // Serial.print("Distance: ");
 // Serial.println(distance);

 // Checks to make sure the data from the sensor is valid - greater than 0 and less than 500
 // and stops the motors if the distance is less than d_min
  if (distance <= d_min && distance != 0 && distance <= 500){ 
   if (currentState == FORWARD) { // only changes state to stop when it is moving forward
    currentState = STOP;
   }
  }
} 

void FireLaser () {
// elapsedMillis taskTimer; // moved to the top
unsigned int taskADelay; // should this be moved to the top?
unsigned int taskBDelay; // should this be moved to the top?
taskADelay = 3000; // should this be moved to the top?
taskBDelay = 7000; // should this be moved to the top?
taskTimer = 0; // resets timer to 0

if (taskTimer > taskADelay) {
 // taskTimer = 0; // should the the timer be reset to 0 here?
 digitalWrite(laserPin, LOW);
  }
if (taskTimer > taskBDelay) {
  // taskTimer = 0; // should the the timer be reset to 0 here?
 digitalWrite(laserPin, HIGH);
  }
}

void turnLeft() { // partial turn to the left
  turnTimer = 0; // resets the timer to 0
  if (turnTimer > turnDelay) { // if the timere is longer than the delay it changes currentMode to 
    currentState = STOP; // do i need both of these here?
    Stop_Robot(); // do i need both of these here?
  }
  else  {
    currentState = LEFT; // do i need both of these here?
    Rotate_Left(); // do i need both of these here?
  }
}

void Move_Backward() { // Function to get the wheels going backward
  digitalWrite(motorPin1, HIGH); // writes data to pin
  digitalWrite(motorPin2, LOW); // writes data to pin
  digitalWrite(motorPin3, HIGH); // writes data to pin
  digitalWrite(motorPin4, LOW); // writes data to pin
  analogWrite(motorPin5, speedK9); // writes data to anaglog pin
  analogWrite(motorPin6, speedK9); // writes data to ana log pin
}

// This is what caused the ears to move. when the earMode is set
// to START_EARS it starts a single radar sweep of the ears.
// As it runs it automaticaly changes the state takining it from
// max to min then back to center where it stops
void moveEars() { 
static uint32_t stepTimer=0; // state machine timer, removes the need for delay()
static uint8_t position = MID_SWEEP; // if moveEars is called without a change earMode it keeps the servo in the middle position

   if(millis() - stepTimer > STEP_RATE) {// starts the timer making a step every few milliseconds
    stepTimer=millis(); //resets the timer
    switch (earMode) { // defining the state machine states
    case  START_EARS: // initial position
     if(position++ >= MAX_SWEEP) // takes the current position and adds 1 to it until it gets to the MAX_SWEEP
    earMode=STEP_TO_MIN; // once it hits max it changes the machine state to STEP_TO_MIN
    break;
    case  STEP_TO_MIN: // the current position should be the max position
     if(position-- <= MIN_SWEEP) // takes the current position and subtracts 1 from it until it gets to the MIN_SWEEP
    earMode=STEP_TO_MIDDLE; // once it hits min it changes the machine state to STEP_TO_MIDDLE
    break;
    case  STEP_TO_MIDDLE: // the current postion needs to be less than the mid to work
     if(position++ >= MID_SWEEP) // takes the current position and adds 1 to it until it gets to the MID_SWEEP
    earMode=STOP_AT_MIDDLE; // once it hits the middle it changes the machine state to STOP_AT_MIDDLE
    break;
    case  STOP_AT_MIDDLE: // the current postion should be MID_SWEEP with no further movement
    position=MID_SWEEP; // this makes sure that the servo is in the MID_SWEEP postition
    // digitalWrite(LEDPin, LOW); // this turns of the LED - currently not used
    break;
    } //switch end of the machine states

    myServo.write(position);   // moves servo 
   }  // timer
}

void Move_Forward() { // Function to get the wheels going forward
//  checkDistance(); // checks for front distance to make sure the head does not bump anything - is this the right spot for this?
  digitalWrite(motorPin1, LOW); // writes data to pin
  digitalWrite(motorPin2, HIGH); // writes data to pin
  digitalWrite(motorPin3, LOW); // writes data to pin
  digitalWrite(motorPin4, HIGH); // writes data to pin
  analogWrite(motorPin5, speedK9); // writes data to analog pin
  analogWrite(motorPin6, speedK9); // writes data to analog pin 
}

void Rotate_Left() { // Function to get the wheels moving in opposite directons to turn left
  digitalWrite(motorPin1, LOW); // writes data to pin
  digitalWrite(motorPin2, HIGH); // writes data to pin
  digitalWrite(motorPin3, HIGH); // writes data to pin
  digitalWrite(motorPin4, LOW); // writes data to pin
  analogWrite(motorPin5, speedK9); // writes data to analog pin
  analogWrite(motorPin6, speedK9); // writes data to analog pin
}

void Rotate_Right() { // Function to get the wheels moving in opposite directons to turn right
  digitalWrite(motorPin1, HIGH); // writes data to pin
  digitalWrite(motorPin2, LOW); // writes data to pin
  digitalWrite(motorPin3, LOW); // writes data to pin
  digitalWrite(motorPin4, HIGH); // writes data to pin
  analogWrite(motorPin5, speedK9); // writes data to analog pin
  analogWrite(motorPin6, speedK9); // writes data to analog pin
}

void readRemote() { // deals with the signals from the ir remote
 if (IrReceiver.decode()) { // sanity check - what is this code saying?
      Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX); // Print "old" raw data
      IrReceiver.printIRResultShort(&Serial); // Print complete received data in one line
      IrReceiver.printIRSendUsage(&Serial);   // Print the statement required to send this data
      Serial.println(IrReceiver.decodedIRData.command); // prints the decoded command
      IrReceiver.resume(); // Enable receiving of the next value

  if (IrReceiver.decodedIRData.command == 24) { // 24 is the command code for the up button
  currentState = FORWARD;
  IrReceiver.resume();  // Enable receiving of the next value
 } else if (IrReceiver.decodedIRData.command == 28) { // 28 is the command code for the OK button
  currentState = STOP;
  IrReceiver.resume();  // Enable receiving of the next value
 } else if (IrReceiver.decodedIRData.command == 8) { // 8 is the command code for the left button
  currentState = LEFT;
  IrReceiver.resume();  // Enable receiving of the next value
 } else if (IrReceiver.decodedIRData.command == 90) { // 90 is the command code for the right button
  currentState = RIGHT;
  IrReceiver.resume();  // Enable receiving of the next value
 } else if (IrReceiver.decodedIRData.command == 82) { // 82 is the command code for the down button
  currentState = BACKWARD;
  IrReceiver.resume();  // Enable receiving of the next value
  }
  // sanity check for the moveEars function
  else if (IrReceiver.decodedIRData.command == 25) { // 25 is the command code for the "0" button
  currentState = EARS;
  IrReceiver.resume();  // Enable receiving of the next value
 }
  // checking to see if the turn code actually works
  else if (IrReceiver.decodedIRData.command == 13) { // 25 is the command code for the "0" button
  turnLeft();
  IrReceiver.resume();  // Enable receiving of the next value
 }
}
}

void Stop_Robot(){ // Function to stop the wheels from turning
  digitalWrite(motorPin1, HIGH); // writes data to pin
  digitalWrite(motorPin2, HIGH); // writes data to pin
  digitalWrite(motorPin3, HIGH); // writes data to pin
  digitalWrite(motorPin4, HIGH); // writes data to pin
  analogWrite(motorPin5, speedK9); // writes data to pin
  analogWrite(motorPin6, speedK9); // writes data to pin
}

void Other_Stop() { // Function to stop other things that need to be stopped
// not doing anything right now
}

void checkStates() { // Function that checks to see what state the robot is in and take an appropriate actions
 switch (currentState) {
   case STOP:
   Stop_Robot();
   currentState = STOP; // do i need this here?
   IrReceiver.resume();
 break;
   case FORWARD:
   currentState = FORWARD;  // do i need this here
   Move_Forward();
   IrReceiver.resume();
 break;
   case BACKWARD:
   Move_Backward();
   currentState = BACKWARD; // do i need this here
   IrReceiver.resume();
 break;
   case LEFT:
   Rotate_Left();
   currentState = LEFT; // do i need this here
   IrReceiver.resume();
 break;
   case RIGHT:
   Rotate_Right();
   currentState = RIGHT; // do i need this here
   IrReceiver.resume();
 break;
    case LASER: // makes the laser figher - do i even need this state?
    currentState = LASER; // do i need this here
    IrReceiver.resume();
 break;   
 }
}
 
void sanityCheck() { // prints all sorts of shit to the serial monitor for debugging
if (currentState == FORWARD) {
    Serial.println("State Forward");
  } else if (currentState == STOP) {
    Serial.println("State STOP");
  } else if (currentState == LEFT) {
    Serial.println("State LEFT");
  } else if (currentState == RIGHT) {
    Serial.println("State RIGHT");
  } else if (currentState == WAIT){
    Serial.println("State WAIT");
  } else if (currentState == BACKWARD){
   Serial.println("State BACKWARD");
  } else if (currentState == EARS){
    Serial.println("State EARS");
  } else if (currentState == BOOT){
    Serial.println("State BOOT");
  }
}

void runStateMachine(){ // calls all the functions needed to run the robot. Hopefully with no delay...
readRemote(); // looks for data coming into the IR sensor from the remote
checkStates(); // looks to see if the currentStates has changed
// checkMode(); // checks to see if the currentMode has changed - need to actually code this - chould this come before checkStates? Does it matter?
// checkEars(); // checks to see if the earMode has changed - need to actually code this - chould this come before checkStates? Does it matter?
if (currentState == FORWARD){
 checkDistance(); // Does this work better here or in the Move_Forward function?
 }
// sanity check - check to make sure what state the robot is in
// sanityCheck();
}

void loop() { // put your main code here, to run repeatedly:
runStateMachine(); // this runs the whole enchilada
}

LOL.

Thanks for posting the code. I won't be the first to get into it as I am in transit looking through the tiny window, but I look forward to a low level flight over it when I am next in the lab.

BTW delayMicroseconds() does block, but sometimes it's easier to just let it, as you can imagine it blocks for microseconds, which may not damage the "play nice" philosophy.

a7

You do this several times through your code

If turnTimer is 0, how will it every be greater than turnDelay?

You don't want to be resetting your variables to zero and then testing them for the amount of delay...

If turnTimer is 0, how will it every be greater than turnDelay?

You don't want to be resetting your variables to zero and then testing them for the amount of delay...

Well this probably explains why the turn wasn't working. I guess my thought was I need to set turnTimer to 0 at some point otherwise it would always be greater than the turnDelay. Of course when I create turnTimer I set it at 3000 for some unknown reason.

What I'm trying to do is to get it to do a partial turn, about 45 degrees. If I call Rotate_Left() it spins to the left. What Im trying is to turn for 1000 (or whatever time I need to get it to about 45) and then STOP.

would this work?

// global
elapsedMillis turnDelay = 1000;

... 

void turnLeft() { // partial turn to the left
elapsedMillis turnTimer; 
  if (turnTimer > turnDelay) { // if the timere is longer than the delay it changes currentMode to 
    currentState = STOP; // do i need both of these here?
    Stop_Robot(); // do i need both of these here?
  }
  else  {
    currentState = LEFT; // do i need both of these here?
    Rotate_Left(); // do i need both of these here?
  }
}

OK, still just doing the peephole thing for amusement.

The delayMicroseconds stuff looks OK.

If a variable is only used inside a function, no need to move it to the top as you put it. Better actually that you don't.

If you want such a variable to retain its value for the next time the function is called, add the qualifier static to the declaration.

If you believe the code is running freely, then declare a variable, I call it now but ppl like to ake it sound more prestigious… uo top (so it is global)

unsigned long currentMillis;

Then first thing in the loop() function

void loop() {
  currentMillis = millis();

and use currentMillis instead of calling millis() ever again anywhere. This just makes everything operate on the same standard time.

Can you say what all the states are? I have to jet, so I wonder how many separate state machines you might have all tnagled up in there.

I suggest you look into giving them nice names, and separate state variables, which yes do need to be updated to effect a change in state.

The C/C++ enum feature is very useful for that, as is the C/C++ switch/case statement, conceptually very simple and trust me will make thing easier to write (and read) and should take but a little concerted learning time while not worrying exactly about your projext. Just get a handle on them, shouldn't take too long.

a7

No. You are actually creating two, completely different variables here that just happen to have the same name, which is confusing to humans, but the compiler keeps them straight.

As @alto777 points out, you want a local variable (inside function) that retains its value, so you use static

void turnLeft() { // partial turn to the left
  static elapsedMillis turnTimer;  // retains value across calls to this function 

I'm getting the impression that it may be worth your while to step back from this project and do some of the simple examples and study C/C++ programming and learn a bit more before tackling this level of project.

you could look at the following examples

and you might benefit from studying state machines and common constructs (enum, switch/case, ...). Here is a small introduction to the topic: Yet another Finite State Machine introduction

@bagpiperdave - I've only noticed for sure now that you are using a timer library.

I have nothing against libraries, but in this case it would be good if you set aside some time to learn how you might do without it, even if you continue to use it.

This is the essence of the library:

// a global object (or static local) is the timer
elapsedMillis timeElapsed; 


// then, wherever in your code
// check the timer object

  if (timeElapsed > interval)
  {

// here be stuff that executes every interval

    timeElapsed = 0;  // reset for next time
  }

Three things:

  • something that is the timer.
  • some way to see if it has expired
  • some way to start or restart it

Here's how you do it all by yourself, a very simple idiomatic pattern:

// a global variable (or static local) is the timer
unsigned long startTime; 



// then, wherever in your code
// check the time since you started or restarted the timer

  if (currentMillis - startTime > interval)
  {

// here be stuff that executes every interval

    startTime = 0;  // reset for next time
  }

This assumes the existence of currentMillis, an unsigned long global variable, and that it is being kept up to date by being set to millis() at the top of you loop() function. And of course the constant interval.

I've basically converted it to C, which means you see exactly how it works, using only language features you are, or should be, familiar with, hiding no details and raising no questions about what the operations are actually doing. I didn't look, but the library is prolly very simple, but does use some C++ features that for me make things a bit of a mystery and results in code I have to pander and look into, or blindly trust.

Who said timing is so hard? :expressionless:

HTH

a7

1 Like

Hello bagpiperdave

Sit back, have a drink and study some system development basics to gain the knowledge.

The IPO model provides the basic knowledge to get you started.

A small example of reading a input button looks like this:

INPUT: Read and debounce button.
PROCESSING: Recognising state changes from LOW to HIGH or HIGH to LOW.
OUTPUT: Execute an action on the recognised status change as required.

This small recipe can be used for any software module.

Have a nice day and enjoy coding in C++.

p.s.: structs, arrays and enums are your friend.

1 Like

I've been going over my code and it looks like I've been using probably 3 different ways to keep track of time. I'm trying to clean it up and use currentMillis for all my timing.

I think part of my issue is that I create and test each thing in isolation, using the ultrasonic sensor, firing the laser, moving the wheels, and made sure each thing was working then cramming them together. The fact there there probably has been 15 days between each thing allows me to forget how I solved the same problem previously.

I haven't done any real programming in over 20 years. I originally went to school to get a CS degree, to be a programmer like my dad. 4 years later I graduated with a history degree, to my dads great disappointment, which is a pretty good indicator of what my programming skills were like back then. I'm not sure they have gotten any better since

never too late to make your dad proud :slight_smile:
there are lots of on line resources to learn programming

@bagpiperdave OK, now I took a bit of time with a bigger window and I see you already are using enum and switch/case.

It's time for me to consume mass quantities, but I did wonder about multiple anonymous enums, so file that under I learned something today...

I wrote this, which is what I usually do with a state variable:

enum {EARS = 0, FORWARD, BACKWARD, LEFT, RIGHT, STOP, LASER} currentState; // the drive states - should I move LASER to a mode?

const char *csTags[] = {
  "EARS",
  "Forward",
  "BACKWARD",
  "LEFT",
  "RIGHT",
  "STOP",
  "LASER?",
  "WAIT?",
  "BOOT?",
};

const byte nStates = sizeof csTags / sizeof *csTags;

void sanityCheck() {
  if (currentState < nStates) {
    Serial.print(" State ");
    Serial.println(csTags[currentState]);
  }
  else {
    Serial.print(" Sanity? State = ");
    Serial.println(currentState);
  }
}

There are some states not handled, and some not used, so I added a '?'.

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.