/*
Forum: https://forum.arduino.cc/t/millis-statt-delay-funktioniert-so-nicht/1381778
Wokwi: https://wokwi.com/projects/466268764049890305
A very simple state machine that demonstrates the use of
enum class for states
millis() instead of delays to control sequence flows
A sequence is started by Serial input 's' or 'S'.
The sequence ends after "sequenceDuration" [ms]
Version V moved button and Serial from IDLE inside loop()
Now the sketch can react to button and Serial independently from the actual state.
E.g. you can switch the green led on or off sending an l or L via Serial at any time.
ec2021
2025/06/09
*/
constexpr byte redLedPin {12};
constexpr byte greenLedPin {10};
constexpr byte buttonPin {11};
constexpr unsigned long sequenceDuration = 3500; // [ms]
constexpr unsigned long blinkInterval = 166; // [ms]
constexpr unsigned long printInterval = 1000; // [ms]
enum class STATE {IDLE, RUNNING};
STATE state = STATE::IDLE;
unsigned long seqStart = 0;
unsigned long lastTimeHere = 0;
unsigned long lastTimeLedChanged = 0;
byte redLedState = LOW;
boolean changeState = false;
void setup() {
pinMode(redLedPin, OUTPUT);
pinMode(greenLedPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("Input (s or S) or (l or L)");
}
void loop() {
handleSerialAndButton();
verySimpleStateMachine();
}
void verySimpleStateMachine() {
switch (state) {
case STATE::IDLE:
doIdle();
break;
case STATE::RUNNING:
doSerialPrinting();
blinkLed();
checkAndHandleSequenceDone();
break;
}
}
void handleSerialAndButton() {
changeState = false;
if (Serial.available()) {
char c = Serial.read();
c = toupper(c);
switch (c) {
case 'S':
changeState = true;
break;
case 'L':
byte greenLedState = !digitalRead(greenLedPin);
digitalWrite(greenLedPin, greenLedState);
break;
default:
// ignore other characters
break;
}
}
if (digitalRead(buttonPin) == LOW) {
changeState = true;
delay(30); // Simple Debouncing! Happens only when the button is pressed and not really required in
// this specific case as the following state RUNNING does not react to changes of changeState
// but to be on the safe side ...
}
}
void doIdle() {
if (changeState) {
// This is performed when a sequence shall start
// We store the start time
seqStart = millis();
// Do something that shall be done once everytime
// a sequence starts (here we only print something)
Serial.print("Start - Running - ");
// and now change the state to RUNNING
state = STATE::RUNNING;
}
}
void doSerialPrinting() {
if (millis() - lastTimeHere >= printInterval) {
// This is performed every printInterval while state == RUNNING
// We store the time when this if clause was entered for the next loop()
lastTimeHere = millis();
// We could do e.g. read sensor values or control a stepper or ...just print something
Serial.print('.');
}
}
void blinkLed() {
if (millis() - lastTimeLedChanged >= blinkInterval) {
// This is performed every blinkInterval while state == RUNNING
// We store the time when this if clause was entered for the next loop()
lastTimeLedChanged = millis();
// Let's switch the state of the led:
redLedState = !redLedState;
digitalWrite(redLedPin, redLedState);
}
}
void checkAndHandleSequenceDone() {
// If we are in state RUNNING the sequence duration expired ...
if (millis() - seqStart >= sequenceDuration) {
// This is performed when sequenceDuration is expired
// We could stop a motor, switch an led on or off, or just print something
Serial.println(" - Stop");
Serial.println("Input (s or S) or (l or L)");
// Let's make sure the led is off when entering the IDLE state
redLedState = LOW;
digitalWrite(redLedPin, redLedState);
// As the sequenceDuration has expired and we have done our "final work" we go back to IDLE
state = STATE::IDLE;
}
}