Combine multiple state machines

Hello,
I'm a new user and and new to code programing.

I need a program to blink two LEDs completely independent of one another with two outputs. I found this code on Digi-Key. It works as advertised, but I need the two on/off times to run consecutively. The program I have here runs the two blink times concurrently.

Here's the code:

// These variables store the flash pattern
// and the current state of the LED

int ledPin1 = 12; // the number of the LED pin
int ledState1 = LOW; // ledState used to set the LED
unsigned long previousMillis1 = 0; // will store last time LED was updated
long OnTime1 = 250; // milliseconds of on-time
long OffTime1 = 750; // milliseconds of off-time

int ledPin2 = 13; // the number of the LED pin
int ledState2 = LOW; // ledState used to set the LED
unsigned long previousMillis2 = 0; // will store last time LED was updated
long OnTime2 = 330; // milliseconds of on-time
long OffTime2 = 400; // milliseconds of off-time

void setup()
{
// set the digital pin as output:
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
}

void loop()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();

if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
{
ledState1 = LOW; // Turn it off
previousMillis1 = currentMillis; // Remember the time
digitalWrite(ledPin1, ledState1); // Update the actual LED
}
else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
{
ledState1 = HIGH; // turn it on
previousMillis1 = currentMillis; // Remember the time
digitalWrite(ledPin1, ledState1); // Update the actual LED
}

if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
{
ledState2 = LOW; // Turn it off
previousMillis2 = currentMillis; // Remember the time
digitalWrite(ledPin2, ledState2); // Update the actual LED
}
else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
{
ledState2 = HIGH; // turn it on
previousMillis2 = currentMillis; // Remember the time
digitalWrite(ledPin2, ledState2); // Update the actual LED
}
}

Hello, do yourself a favour and please read How to get the best out of this forum and post accordingly (including code with code tags and necessary documentation for your ask like your exact circuit and power supply, links to components etc).


To your question For information and examples look at

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

If that is all your code has to do, just use delay() instead of a millis() based approach (and maybe a for-loop; your requirement is not quite clear).

If it needs to be more future proof, you need to keep track if the blink sequence of the first LED is finished (e.g. set a boolean variable). If it is, don't do the first LED but the second one. When the second one is finished, clear the variable and it will again do the first one.

Based on your requirement I can not say what the sequence should be
1 on
1 off
2 on
2 off
back to the beginning

or

blink 1 10 times
blink 2 22 times
back to the beginning

or something else

Please edit your post, select all code and next click the <CODE/> button to apply code tags. This makes the code easier to read and copy and the forum software will not mangle it.

the following code may be a little too much for someone "new" because it uses features of C that you may not have read about yet, but i'm going to post it and let you decide

you can read in The C Programming Language about

  • struct in chap 6
  • pointer in chap 5
  • enum pg 39
// generic timer

struct LedTimer {
    byte          Pin;
    unsigned long MsecOn;
    unsigned long MsecOff;

    int           state;
    unsigned long msecPeriod;
    unsigned long msec;
};

LedTimer tmr [] = {
    { 12,  250, 750 },
    { 13,  330, 400 },
};
const int Ntmr = sizeof(tmr) / sizeof(LedTimer);

enum { Off = HIGH, On = LOW };

char s [90];

// -----------------------------------------------------------------------------
void loop()
{
    unsigned long currentMillis = millis();
    
    LedTimer *p = tmr;
    for (int n = 0; n < Ntmr; n++, p++)  {
        if (currentMillis >= p->msec)  {
            if (Off == p->state)  {
                p->msecPeriod = p->MsecOn;
                p->state      = On;
            }
            else  {
                p->msecPeriod = p->MsecOff;
                p->state      = Off;
            }

            p->msec += p->msecPeriod;
            digitalWrite (p->Pin, p->state);
        }
    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin (9600);
    
    for (int n = 0; n < Ntmr; n++)  {
        tmr [n].state      = Off;
        tmr [n].msecPeriod = tmr [n].MsecOff;
        tmr [n].msec       = tmr [n].msecPeriod;
        pinMode      (tmr [n].Pin, OUTPUT);
        digitalWrite (tmr [n].Pin, tmr [n].state);
    }
}

LOL! I knew that one day the advice would be tipped over.

This some like a contradiction. But as @J-M-L says, you could plan the entire co-dependent sequence of the LEDs as multiple states in one finite state machine or you could as @sterretje suggests have your two current mechanisms share at least one variable that would allow each to run or run, and then pass control to the other.

With the addition of a counter they could similarly share, one LED could blink a number of times at its rate before allowing the other to do the same for a different number of times.

But mostly, you need to be able to describe this independent and consecutive concept.

a7

Hello kted

Check:

//https://forum.arduino.cc/t/combine-multiple-state-machines/1220978
//https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
#define ProjectName "Combine multiple state machines"
#define NotesOnRelease "Arduino MEGA tested"
// make names
enum TimerControl {Halt, Run};
enum OnOff {Off, On};
enum Numbers  {One, Two};
// make variables
constexpr uint8_t LedPins[] {12, 13};
constexpr uint32_t OnOffInterval [] [2]
{
  {750, 250},
  {400, 330},
};
constexpr uint32_t NumberOfBlinks [] {3, 1};
// make structures
struct BLINKLED
{
  uint8_t pin;
  uint32_t interval[2];
  uint8_t control;
  uint8_t numerOfBlinks;
  uint32_t now;
  void make(uint8_t pin_, uint32_t offTime, uint32_t onTime, uint8_t numerOfBlinks_)
  {
    pin = pin_;
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
    delay(1000);
    digitalWrite(pin, LOW);
    delay(1000);
    interval[Off] = offTime;
    interval[On] = onTime;
    numerOfBlinks = 2 * numerOfBlinks_;
  }
  void start(uint32_t now_)
  {
    now = now_;
    control = numerOfBlinks;
    digitalWrite(pin, HIGH);
  }
} ledBlinks[sizeof(LedPins)];

// make support
void heartBeat(const uint8_t LedPin, uint32_t currentMillis)
{
  static bool setUp = false;
  if (setUp == false) pinMode (LedPin, OUTPUT), setUp = true;
  digitalWrite(LedPin, (currentMillis / 500) % 2);
}
// make application
void setup()
{
  Serial.begin(115200);
  Serial.print("Source: "), Serial.println(__FILE__);
  Serial.print(ProjectName), Serial.print(" - "), Serial.println(NotesOnRelease);
  uint8_t element = 0;
  for (auto &ledBlink : ledBlinks)
  {
    ledBlink.make(LedPins[element], OnOffInterval [element] [Off], OnOffInterval [element] [On], NumberOfBlinks[element]);
    element++;
  }
  ledBlinks[One].start(millis());
  Serial.println(" =-> and off we go\n");
}
void loop()
{
  uint32_t currentMillis = millis();
  heartBeat(LED_BUILTIN, currentMillis);
  static uint8_t blinkNumber = 0;
  for (auto &ledBlink : ledBlinks)
  {
    if (currentMillis - ledBlink.now >= ledBlink.interval[digitalRead(ledBlink.pin)] and ledBlink.control)
    {
      if ((--ledBlink.control) != 0)
      {
        digitalWrite(ledBlink.pin, digitalRead(ledBlink.pin) ? LOW : HIGH);
        ledBlink.now = currentMillis;
      }
      else
      {
        blinkNumber = (blinkNumber + 1) % (sizeof(ledBlinks) / sizeof(ledBlinks[0]));
        ledBlinks[blinkNumber].start(currentMillis);
      }
    }
  }
}

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

To achieve the effect of having two LEDs blink consecutively, you need to modify the existing code so that one LED starts blinking after the other has completed its cycle. You can accomplish this by adding a condition to check if both LEDs have completed their cycles before starting the next cycle for the first LED. Here's how you can adjust the loop() function to implement this behavior:

void loop()
{
  unsigned long currentMillis = millis();

  // Check if both LEDs have completed their cycles
  bool bothCompleted = false;

  // Update LED1 state
  if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
  {
    ledState1 = LOW;
    previousMillis1 = currentMillis;
    digitalWrite(ledPin1, ledState1);
  }
  else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
  {
    ledState1 = HIGH;
    previousMillis1 = currentMillis;
    digitalWrite(ledPin1, ledState1);
  }
  // Check if LED1 has completed its cycle
  if(ledState1 == HIGH && currentMillis - previousMillis1 >= OnTime1 + OffTime1)
  {
    bothCompleted = true;
  }

  // Update LED2 state
  if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
  {
    ledState2 = LOW;
    previousMillis2 = currentMillis;
    digitalWrite(ledPin2, ledState2);
  }
  else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
  {
    ledState2 = HIGH;
    previousMillis2 = currentMillis;
    digitalWrite(ledPin2, ledState2);
  }
  // Check if LED2 has completed its cycle
  if(ledState2 == HIGH && currentMillis - previousMillis2 >= OnTime2 + OffTime2)
  {
    bothCompleted = true;
  }

  // If both LEDs have completed their cycles, reset their timers
  if(bothCompleted)
  {
    previousMillis1 = currentMillis;
    previousMillis2 = currentMillis;
    bothCompleted = false; // Reset flag for the next cycle
  }
}

I added a boolean variable bothCompleted to track whether both LEDs have completed their blinking cycles. I then added checks after updating each LED's state to determine if the LED has finished its cycle (OnTime + OffTime). If both LEDs have completed their cycles, we reset the timers for both LEDs, ensuring that the next cycle starts immediately after the previous one completes.

Thanks for your reply.

I know it will work using delay, but I will be adding a LCD to display remaining "on" times for each output. If I use delay it's a blocking code, it won't do anything else.

So next thing you should do is describing the complete functionality in normal words that you want to have in the end.

If you only describe parts of the wanted functionality you are fooling your potential helpers to suggest solutions that may turn out to be not suitable.

best regards Stefan

Thank you for your help.

I'm totally lost. I'm new to Arduino programing. In other words I'm wet behind the ears!

The code does not work in the way I want it to. Perhaps I did not explain just what I'm trying to do. So I will try to explain.

I need 2 separate timers on 2 separate output pins.
I will call the timers as T1 & T2.
The timers can not be "on" at the same time.
If T1's on time is set for 10,000ms and the off time is set for 30,000ms then T1"s total time would be 4sec.

At the end of T1's total time ( 4sec ) then T2's timer would start. At the end of T2's total time, Then loop to the beginning.

When I get that portion of the code to work, I will attempt to add a LCD to show the remaining time of each timer. The approximate timer times are T1 "on" = 6hr, "off" = 0. I know that it can't be 0, so I will use 1ms. T2's "on" time = 240hrs.
Now you now why I need a LCD. I don't need to show milliseconds on the LCD, only HHH:MM:SS

Thank you for your help.

I'm totally lost. I'm new to Arduino programing. In other words I'm wet behind the ears!

The code does not work in the way I want it to. Perhaps I did not explain just what I'm trying to do. So I will try to explain.

I need 2 separate timers on 2 separate output pins.
I will call the timers as T1 & T2.
The timers can not be "on" at the same time.
If T1's on time is set for 10,000ms and the off time is set for 30,000ms then T1"s total time would be 4sec.

At the end of T1's total time ( 4sec ) then T2's timer would start. At the end of T2's total time, Then loop to the beginning.

When I get that portion of the code to work, I will attempt to add a LCD to show the remaining time of each timer. The approximate timer times are T1 "on" = 6hr, "off" = 0. I know that it can't be 0, so I will use 1ms. T2's "on" time = 240hrs.
Now you now why I need a LCD. I don't need to show milliseconds on the LCD, only HHH:MM:SS
[/quote]

Just to make sure I understand right I describe with my own words using example-numbers

You have two timers called "T1" and "T2" these timers shall run mutually exclusive. This means:

  • whenever T1 is active T2 must stay deactivated
  • whenever T2 is active T1 must stay deactivated

A timing-cycle starts with T1 beeing active let's say:
T1-on-time 120 hours / T1-off-time 180 hours

If T1 has finished its cycle T2 starts its own cycle
T2-on-time 80 hours / T2-off-time 220 hours

Whenever T1 or T2 is running show the remaining time on a LCD in Format HHH:MM:SS

Is this a correct description of the wanted functionality?
Are the numbers how many hours the timers shall run a typical example of the times you want the timers to be on/off.

So this means in the final state you need a timer that runs more than 100 hours. In your example 240 hours which are 10 days.

Well if you need so long time-intervals I recommend to use a Real Time Clock in short RTC.

Beause the function millis() will deviate for 10 or 20 seconds or even more over such a long period of time. If you can live with such a deviation you could still use function millis() as the clockbase for a software-RTC.

Here is a demo-Code for a millis() / software-based RTC

/* This software belongs to the public domain
 * Attention: a software-RTC like this one 
 * which uses the onboard-oscillator of the microcontroller-board
 * has a deviation from exact time of up to multiple seconds per day
 * This means of you want real time with a higher precision use a
 * hardware based RTC which will be much more precise
 */

unsigned long RTC_Timer  = 0;
unsigned long MyTestTimer;

int RTC_Hour   = 0;
int RTC_Minute = 59;
int RTC_Second = 50;
int RTC_10nth_Seconds = 0;
int minutesOfDay;
long secondsOfDay;


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}


void PrintRTC_Data() {
  Serial.print("Software RTC time: ");
  Serial.print(RTC_Hour);
  Serial.print(":");
  Serial.print(RTC_Minute);
  Serial.print(":");
  Serial.print(RTC_Second);

  minutesOfDay = RTC_Hour * 60 + RTC_Minute;
  secondsOfDay = RTC_Hour * 3600 + RTC_Minute * 60 + RTC_Second;

  Serial.print(" minutesOfDay:");
  Serial.print(minutesOfDay);

  Serial.print("  secondsOfDay:");
  Serial.print(secondsOfDay);  
  Serial.println();
  
}


void SoftRTC() {
  if ( TimePeriodIsOver(RTC_Timer, 100) ) { // once every 100 milliseconds 
    RTC_10nth_Seconds ++;                   // increase 1/10th-seconds counter
    
    if (RTC_10nth_Seconds == 10) {          // if 1/10th-seconds reaches 10
      RTC_10nth_Seconds = 0;                // reset 1/10th-seconds counterto zero
      RTC_Second++;                         // increase seconds counter by 1

      if (RTC_Second == 60) {               // if seconds counter reaches 60
        RTC_Minute++;                       // increase minutes counter by 1
        RTC_Second = 0;                     // reset seconds counter to zero
      }
    }

    if (RTC_Minute == 60) {                 // if minutes counter reaches 60
      RTC_Hour++;                           // increase hour counter by 1
      RTC_Minute = 0;                       // reset minutes counter to zero
    }

    if (RTC_Hour == 24) {                   // if hours counter reaches 24 
      RTC_Hour = 0;                         // reset hours counter to zero
    }
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
}


void loop() {
  SoftRTC(); // must be called very regular and repeatedly to update the 0.1 second time-ticks
             // this means you CAN'T use delay(). 
             // Each and every single delay() must be replaced NON-blocking timing
  if ( TimePeriodIsOver(MyTestTimer, 1000) ) { // if 1000 milliseconds have passed by
    PrintRTC_Data();                           // print time
  }
}

If you combine this software-RTC with a state-machine you can realise the wanted functionality

A state-machine is based on a switch-case-break-statement where the "break" makes code-execution mutually exclusive.

The case-part is a very simple equivalent to an if-condition
as simple as

if (myVariable == 1) {
// execute some code
}

if (myVariable == 2) {
// execute some code
}

if (myVariable == 3) {
// execute some code
}
switch (myVariable) {
  case 1:
    // execute some code

  case 2:
    // execute some code

  case 3:
    // execute some code

}

best regards Stefan

You have things that need to happen in sequence. The below uses a sequencer implemented in a finite state machine.

Your LEDs in can in one of four states. The four states are

  1. LED1 ON
  2. LED1 OFF
  3. LED2 ON
  4. LED2 OFF

I have added a fifth one (START) to make sure that the timing at first run is correct; this state is only used once.

Each time that a state has done what it has to do, the code switches to the next state. For the START state you do not have conditions, it sets the pins correctly, stores the time and changes to LED1_ON. In LED1_ON, the basics is the millis() based timing; if the time has lapsed, it switches the first LED off, remembers the time and switches to LED1_OFF. And so on. In the last state (LED2_OFF), the code switches back to LED1_ON.

int ledPin1 = 12;                   // the number of the LED pin
int ledState1 = LOW;                // ledState used to set the LED
unsigned long OnTime1 = 1250;        // milliseconds of on-time
unsigned long OffTime1 = 1750;       // milliseconds of off-time

int ledPin2 = 13;                   // the number of the LED pin
int ledState2 = LOW;                // ledState used to set the LED
unsigned long OnTime2 = 1330;        // milliseconds of on-time
unsigned long OffTime2 = 1400;       // milliseconds of off-time

void setup()
{
  // set the digital pin as output:
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  
  Serial.begin(115200);
}

void loop()
{
  fsmLEDs();
}

/*
Finite state machine for LEDs
*/
void fsmLEDs()
{
  // the various states
  enum FSMSTATES
  {
    START,
    LED1_ON,
    LED1_OFF,
    LED2_ON,
    LED2_OFF,
  };

  // current state of state machine
  static FSMSTATES fsmState = START;
  // last time that a change of state happened
  static uint32_t lastUpdateTime;  // note: uint32_t is the same as unsigned long

  switch (fsmState)
  {
    case START:
      Serial.println("fsm START");

      // switch led 1 on
      digitalWrite(ledPin1, HIGH);
      // switch led 2 on
      digitalWrite(ledPin2, LOW);
      // go to next state
      fsmState = LED1_ON;
      // remember the time that we switched to the next state
      lastUpdateTime = millis();
      Serial.println("fsm switching to LED1_ON");
      break;
    case LED1_ON:
      if (millis() - lastUpdateTime >= OnTime1)
      {
        digitalWrite(ledPin1, LOW);
        lastUpdateTime = millis();
        fsmState = LED1_OFF;
        Serial.println("fsm switching to LED1_OFF");
      }
      break;
    case LED1_OFF:
      if (millis() - lastUpdateTime >= OffTime1)
      {
        digitalWrite(ledPin2, HIGH);
        lastUpdateTime = millis();
        fsmState = LED2_ON;
        Serial.println("fsm switching to LED2_ON");
      }
      break;
    case LED2_ON:
      if (millis() - lastUpdateTime >= OnTime2)
      {
        digitalWrite(ledPin2, LOW);
        lastUpdateTime = millis();
        fsmState = LED2_OFF;
        Serial.println("fsm switching to LED2_OFF");
      }
      break;
    case LED2_OFF:
      if (millis() - lastUpdateTime >= OffTime2)
      {
        digitalWrite(ledPin1, HIGH);
        lastUpdateTime = millis();
        fsmState = LED1_ON;
        Serial.println("fsm switching to LED1_ON");
      }
      break;
  }
}

Study it and ask what you don't understand.

Notes

  1. There is no longer a need for the previousMillis variables.
  2. Your onTime and offTime should be unsigned longs.
  3. The function fsmLeds() can return values; e.g. the remaining time. You need to adjust for that.

Code can be improved by using structs or classes to combine the related info for a LED (pin, on time and off time).

Here's my sawed off attempt, prob'ly don't adhere to the Arduino "API" but it works. :grin:

const byte
  ledPin1 = 12, //the number of the LED pin
  ledPin2 = 13;

const uint16_t // unsigned int
  onTime1 = 250, // milliseconds of on-time
  offTime1 = 750, // milliseconds of off-time
  cycleTime1 = onTime1 + offTime1,
  onTime2 = 330, // milliseconds of on-time
  offTime2 = 400, //milliseconds of off-time
  cycleTime2 = onTime2 + offTime2;
unsigned long
  previousMillis, // will store last time LED was updated
  elapsed, // millis() - previousMillis
  totalTime = cycleTime1 + cycleTime2;
  
void setup()
{
  // set the digital pin as output:
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
}

void loop()
{
  elapsed = millis() - previousMillis;
  if(elapsed >= totalTime)
  {
    previousMillis += totalTime; 
  }
  digitalWrite(ledPin1, elapsed < onTime1);
  digitalWrite(ledPin2, elapsed > cycleTime1 &&
   elapsed < cycleTime1 + onTime2);
}
// generic timer

struct LedTimer {
    byte          Pin;
    byte          OnOff;
    unsigned long Period;
};

enum { Off = HIGH, On = LOW };

LedTimer tmr [] = {
    { 12, On,  250 },
    { 12, Off, 750 },
    { 13, On,  330 },
    { 13, Off, 330 },
};
const int Ntmr = sizeof(tmr) / sizeof(LedTimer);
      int tmrIdx;

unsigned long msecPeriod;
unsigned long msec0;

char s [90];

// -----------------------------------------------------------------------------
void loop()
{
    unsigned long msec = millis();
    
    if (msec - msec0 >= msecPeriod)  {
        msec0 = msec;

        if (Ntmr <= ++tmrIdx)
            tmrIdx = 0;

        msecPeriod  = tmr [tmrIdx].Period;

        digitalWrite (tmr [tmrIdx].Pin, tmr [tmrIdx].OnOff);

        Serial.println (tmrIdx);
    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin (9600);
    
    for (int n = 0; n < Ntmr; n++)  {
        pinMode      (tmr [n].Pin, OUTPUT);
        digitalWrite (tmr [n].Pin, Off);
    }
}

Shouldn't this

        if (Ntmr <= ++tmrIdx)
            tmrIdx = 0;

come after you've used tmrIdx to time one of the steps?

TBH I would not have noticed, but I had to run your code to see if and how it even works.

a7

sure

You're welcome.

a7

Thank you,

The code does to appear work.

I set the timer "times" as:

    { 12, On,  10000 },
    { 12, Off, 1 },
    { 13, On,  20000 },
    { 13, Off, 1 },

I used the stopwatch on my phone to check the LED "ON" times. I measured 10sec on the pin 12 LED and 21sec
on the pin 13 LED. Both within my reaction time.

In the next few days I will attempt to write code to add an LCD to display the remaining time for each
timer.

The final project "ON" time will be 6hrs and 240hrs respectively. That's why I need an LCD. I have a
I²C LCD that I will use.

The final project will be put in a project case. I'm using the Arduino Pro Mini.

Thanks again,
Ted

P.S., maybe someday I can return the info favor. I have some experience in Analog, discrete digital,
RF, and power supplies.

I don't understand

I believe your description is mostly correct. I don't need ( or want ) a RTC. Any inaccurate display times are moot. At 240 hrs ( 10 days ), a few minutes or hours either way won't do anything to my project.
If the deviation is more than I want, I can adjust the time in milliseconds for the ON time.
The timer will run continuously and I don't care what time of day or night the outputs go high or low.

I also don't need a serial monitor, the completed board and LCD will go into a project case along with the electronics.

Ted