/*
Forum: https://forum.arduino.cc/t/variable-types-used-in-state-machine/1405843
Wokwi:https://wokwi.com/projects/441880084428513281
*/
constexpr uint8_t analogPins[] = {A3, A4, A5}; // analog pin names for the LEDs
constexpr uint8_t ledNumber = sizeof(analogPins) / sizeof(analogPins[0]);
constexpr uint8_t buttonPin = {A0};
constexpr unsigned long base_time_increment = 150; // increment of time between LEDs
constexpr unsigned long base_time_offset = 200; // time added to all of the timing data
constexpr unsigned long overall_time = (base_time_offset + base_time_increment*ledNumber) * 2; // Complete cycle time
byte direction; // Holds the state of the switch to control the "led off" timing
unsigned long previousMillis = 0; // Holds the time when one cycle starts
unsigned long timing[ledNumber]; // Time interval after that a certain led has to be switched off
enum States {START, LEFT, RIGHT, END}; // Provides an enumeration of the different states required to achieve the function
States state = START; // The machine starts with the state "START"
int actualLed; // Global variable that holds the actual led number that has to be treated
void setup() {
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP);
direction = digitalRead(buttonPin); // Get the actual button state at start/restart
Serial.print("Overall time:\t");
Serial.println(overall_time);
// Calculate the timings for direction HIGH,
// print the timings for both directions (HIGH and LOW)
// and set pinMode for all pins declared in analogPins[]
for (int i = 0; i < ledNumber; i++) {
timing[i] = base_time_offset + base_time_increment * (i + 1);
pinMode(analogPins[i], OUTPUT);
Serial.print("Led "); Serial.print(i + 1); Serial.print("\t");
Serial.print(timing[i]); Serial.print("\t"); Serial.println(overall_time - timing[i]);
}
}
void loop() {
handleDirection(); // Checks the switch and changes "direction" if required
stateMachine();
printLedStatesEveryMS(10); // Uncomment this line and start the Serial Plotter View to see a graphical "state" display
}
// Routine to set a pin to a given state and store the actual state in ledState[]
void setLed(uint8_t no, byte pinState) {
digitalWrite(analogPins[no], pinState);
}
// Function to switch all leds on and set previousMillis as the common sync basis
void allLedsOn() {
for (int i = 0; i < ledNumber; i++) {
setLed(i, HIGH);
}
previousMillis = millis();
}
// Function that checks the state of the switch
// If the state changes we wait for 50 ms (a very simple way to debounce the mechanical switch)
// then "direction" is set to the actual switch state and all leds are turned on
// As allLedsOn() sets previousMillis to the recent time a new cycle begins
void handleDirection() {
uint8_t actState = digitalRead(buttonPin);
if (actState != direction) {
delay(50); // Very Simple Debouncing ...
direction = actState;
state = START;
}
}
/*
This is a finite state machine that provides the following states
START: Switches all Leds on and sets the correct state and start value of actualLed
LEFT: Switches the Leds off starting with the lowest entry in analogPins[]
RIGHT: Switches the Leds off starting with the highest entry in analogPins[]
END : Waits until overall_time has expired and starts the cycle again from START
*/
void stateMachine() {
unsigned long currentMillis = millis(); // Catch the actual time
switch (state) {
case START: // On START
allLedsOn(); // Switch all leds on and store the time in previousMillis
if (direction) { // Depending on the switch state open or closed
state = LEFT; // the next state will be LEFT
actualLed = 0; // starting with the first entry of analogPins[]
} else { // or
state = RIGHT; // RIGHT starting with
actualLed = ledNumber - 1; // the last entry of analogPins[]
}
break;
case LEFT:
if (currentMillis - previousMillis > timing[actualLed]) { // In this direction we start with the lowest entry
setLed(actualLed++, LOW); // set the pin to LOW and after this increase actualLed by 1
}
if (actualLed >= ledNumber) { // When the last entry in analogPins[] has been handled the next state is END
state = END;
} // else we stay and repeat the state LEFT
break;
case RIGHT:
if (currentMillis - previousMillis > overall_time - timing[actualLed]) { // In RIGHT we use the different timing
setLed(actualLed--, LOW); // set the pin to LOW and decrease actualLed by 1
}
if (actualLed < 0) { // When the very first entry in analogPins[] has been handled the next state is END
state = END;
} // else we stay and repeat the state RIGHT
break;
case END: // Now all leds should be handled and we just wait until overall_time has expired
if (currentMillis - previousMillis > overall_time) {
state = START; // If that's the case we start the next cycle
}
break;
}
}
// This function prints the led states so that they can be tracked with the Serial Plotter
// Each led's state is multiplied by the led no so that they can be easily distinguished
// on the screen by their height
void printLedStatesEveryMS(unsigned long interval) {
static unsigned long lastPrint = 0;
if (millis() - lastPrint < interval) {
return;
};
lastPrint = millis();
for (int i = 0; i < ledNumber; i++) {
Serial.print(digitalRead(analogPins[i]) * (i + 1));
Serial.print("\t");
}
Serial.println();
}