/*
Forum: https://forum.arduino.cc/t/variable-types-used-in-state-machine/1405843
Wokwi: https://wokwi.com/projects/441884708594108417
*/
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 cycleStartTime = 0; // Holds the time when one cycle starts
enum States {START, CYCLE, END};
States state = START;
class ledClass {
private:
uint8_t pin;
uint8_t state;
unsigned long timing;
unsigned long lastOn = 0;
public:
void init(uint8_t myPin, unsigned long myTiming) {
pin = myPin;
pinMode(pin, OUTPUT);
timing = myTiming;
};
void on() {
digitalWrite(pin, HIGH);
state = HIGH;
lastOn = millis();
}
void off() {
digitalWrite(pin, LOW);
state = LOW;
}
boolean isOn() {
return (state == HIGH);
}
void handle(boolean direct) {
unsigned long interval = direct ? timing : overall_time - timing;
if (millis() - lastOn > interval) {
off();
}
}
};
ledClass leds[ledNumber];
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++) {
unsigned long timing = base_time_offset + base_time_increment * (i + 1);
leds[i].init(analogPins[i], timing );
Serial.print("Led "); Serial.print(i + 1); Serial.print("\t");
Serial.print(timing); Serial.print("\t"); Serial.println(overall_time - timing);
}
state = START;
}
void loop() {
handleDirection(); // Checks the switch and changes "direction" if required
stateMachine();
}
// 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
CYCLE: Checks whether the Led timing has expired
END : Waits until overall_time has expired and starts the cycle again from START
*/
void stateMachine() {
switch (state) {
case START: // On START
for (auto &l : leds) { // This automatically iterates through the array of objects leds[]
l.on(); // Assigns the address of each object to l (that's what the adress operator &)
} // is required for and calls its .on() function
state = CYCLE; // the next state will be LEFT
cycleStartTime = millis();
break;
case CYCLE: {
boolean allLedsOff = true;
for (auto &l : leds) { // The same as above
l.handle(direction == HIGH); // but now calls each object's handle() function
if (l.isOn()) { // checks the object's state and
allLedsOff = false; // if at least one object's state is HIGH allLedsOff is set to false
}
}
if (allLedsOff) { // if all leds are off goto state END
state = END;
}
}
break;
case END: // Now all leds should be handled and we just wait until overall_time has expired
if (millis() - cycleStartTime > overall_time) {
state = START; // If that's the case we start the next cycle
}
break;
}
}