Working with states and 2 IR beams, states not working

So I am very happy with arduino so far. My intention is to automate parts of the Lego layout that we create for events. I managed a lot o small projects with the arduino, and even manged to use extra libraries and control my layout with IR leds.

Issue I have now is the following.
I have a small loop where a train is running. It has 2 ir beams, where in between a rail crossing should. In another area also a light sensor is there.
Light sensor is supposed to stop the train to wait at a station (not implemented yet)
IR beams are supposed to blink LEDs and close/open a bar that blocks the road.

I build this setup using state machines. I build a part of my own code, and borrowed parts I found on the internet

What should happen is:

  • train runs
  • ir beam west or east is blocked -> blink LEDs (etcetera)
  • if both ir beams are blocked also blink LEDs
  • if both are unblocked -> blinking should stop (after a small time)

Currently I can have the LEDs blink, but I cannot get it to stop blinking again.
Sketch (copied below) will not stop blinking
State that trigegrs the blinking is " case crossingOccupied:"

Can somebody look at my code and tell me what I am doing wrong?

/*
Sketch to let 9V train run with L298N motor and :
- stop at light sensor
- let leds blink between ir sensor 1 and 2
*/

//Pins
const int IN1=24;             //Connects to IN1 on L298N
const int IN2=26;             //Connects to IN2 on L298N
const int ENA=7;              //PWN motor switch
const int irsensorPIN_01 = 3; //IR sensor pin that detects (un)broken east
const int irsensorPIN_02 = 4; //IR sensor pin that detects (un)broken west
const int LDR = A0;           //Pin connecting to Light sensor
const int LED_RIGHT = 9;      //LED red right
const int LED_LEFT = 10;      //LED red left

//Variables
int LDRValue=0;                         //LDR value used to write to serial monitor
int LDRValNow;                          //Variable to save light sensor value
unsigned long previousMillis=0;         //Save time to prevent object blocking the sensor
int sensorState_01 = 0;                 // variable for reading the ir sensor 01 status
int sensorState_02 = 0;                 // variable for reading the ir sensor 02 status

//Settings
const int lightSensitivity=500;         //Set switching point for light sensor (LDR)
int interval=5000;                       //Pause period
const int numberBlinks = 5;             //Minimum number of blinks

//Possible states of the state machine
const byte CHECK_IRBEAM = 0;            //Check if Beams are broken
const byte crossingOccupied = 1;        //Blink LEDs if train is within 2 IR beams
const byte CHECK_LDR = 2;               //Check if light sensor is blocked
const byte TRAIN_GO = 3;                //Let train move
const byte eastBoundApproach = 4;       //Train comes in from east
const byte westBoundApproach = 5;       //Train comes in from west
const byte approachCommon = 6;          //Generic stuff to set
byte state = TRAIN_GO;                  //Default state

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);                   //Write to log
  pinMode(IN1,OUTPUT);                  //Motor pin 1
  pinMode(IN2,OUTPUT);                  //Motor pin 2
  pinMode(LED_RIGHT, OUTPUT);           //Write to red led on the right
  pinMode(LED_LEFT, OUTPUT);            //Write to red led on the left

  // initialize the sensor 01 pin as an input:
  pinMode(irsensorPIN_01, INPUT);
  digitalWrite(irsensorPIN_01, HIGH); // turn on the pullup
  // initialize the sensor 02 pin as an input:
  pinMode(irsensorPIN_02, INPUT);
  digitalWrite(irsensorPIN_02, HIGH); // turn on the pullup

  //Set fixed train direction which is stopped
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,LOW);
}

void loop() {
  // put your main code here, to run repeatedly:
  switch(state) {
    case TRAIN_GO:                //Let train move
      if(millis() - previousMillis > interval){  //Time to start train
//        digitalWrite(IN2, LOW);  //This sketch goes only one direction
//        digitalWrite(IN2, HIGH);  //This sketch goes only one direction
//        analogWrite(ENA,80);      //Motor speed
        previousMillis=millis();  //Save the time
        state = CHECK_IRBEAM;
      }
      break;
    case CHECK_IRBEAM:         //Check if Beams are broken
      if(millis() - previousMillis > interval){
        sensorState_01 = digitalRead(irsensorPIN_01); //reads ir sensor 01 value      
        sensorState_02 = digitalRead(irsensorPIN_02); //reads ir sensor 02 value
        // check if the sensor beam is broken, sensorstate is LOW
        if (sensorState_01 == LOW && sensorState_02 == HIGH) {
          state = eastBoundApproach;      } //Sensor 01 is broken
        else if (sensorState_01 == HIGH && sensorState_02 == LOW) {
          state = westBoundApproach;      } //Sensor 02 is broken
      }
      break;
    case eastBoundApproach:       //Train comes from east
      state = approachCommon;
      break;
    case westBoundApproach:       //Train comes from west
      state = approachCommon;
      break;
    case approachCommon:
      interval = 5000;
      state = crossingOccupied;
      break;
    case crossingOccupied:         //Close bars and blink leds
      animateLEDs();
      //do other stuff
      // Hang out in occupied state until both detectors are showing clear
      if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) {
        interval--;
        if (!interval) {
          turnOffLEDs();
          state = TRAIN_GO;
        }
      } else {
        interval = 5000;
      }
            break;
    default:
      break;
  }
}

////////////////////////////////////////////////////////
//  LED Animation Routines
////////////////////////////////////////////////////////
enum  {
    kLEDsOff  = 0,
    kLEDLeftOn,
    kLEDRightOn
};
int ledState  = kLEDsOff;
int ledTimer  = 0;

void turnOffLEDs(void)  {
    digitalWrite(LED_RIGHT,HIGH);
    digitalWrite(LED_LEFT,HIGH);
    ledState  = kLEDsOff;
}

void animateLEDs(void)  {
    if (ledTimer) ledTimer--;
    switch (ledState)  {
      case kLEDsOff:
        ledTimer  = 0;
        //  fall  through
      case kLEDRightOn:
        if (!ledTimer) {
          ledState  = kLEDLeftOn; ledTimer  = 10000;
          digitalWrite(LED_LEFT,LOW);
          digitalWrite(LED_RIGHT,HIGH);
        } 
        break;
      case kLEDLeftOn:
        if (!ledTimer) {
          ledState = kLEDRightOn; ledTimer  = 10000;
          digitalWrite(LED_LEFT,HIGH);
          digitalWrite(LED_RIGHT,LOW);
        }
        break;
    }
}
const byte crossingOccupied = 1;        //Blink LEDs if train is within 2 IR beams

So, the crossing is always occupied. That makes no sense.

crossingOccupied should be a boolean, should NOT be const, and should be true (the crossing IS occupied) or false (the crossing is not occupied).

If that variable is to represent a state, it should be in all caps, like TRAIN_GO and CHECK_IRBEAM.

      // Hang out in occupied state until both detectors are showing clear
      if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) {
        interval--;
        if (!interval) {
          turnOffLEDs();
          state = TRAIN_GO;
        }
      } else {
        interval = 5000;
      }

That comment is wrong. The block of code does not "hang out". The if statement is evaluated, once.

Perhaps you meant to use while?

PaulS:
If that variable is to represent a state, it should be in all caps, like TRAIN_GO and CHECK_IRBEAM.

It is representing a state, but caps or not makes hopefully not a difference here.

PaulS:

      // Hang out in occupied state until both detectors are showing clear

if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) {
       interval--;
       if (!interval) {
         turnOffLEDs();
         state = TRAIN_GO;
       }
     } else {
       interval = 5000;
     }



That comment is wrong. The block of code does not "hang out". The if statement is evaluated, once.

Perhaps you meant to use while?

Why using while here? The idea is that only after 5 seconds and both ir-beams clear this statement is triggered and the leds are turned off.
In the meantime it is supposed to loop around.

I have the feeling I am missing the advice you are giving me. Apologies there, I am just not seeing it.

Well, I gave it another try.
So I managed to write in a FSM the blinking of the rail crossing. So that's working.

However I also want to use a light sensor where the train should stop, and drive further after an interval of 5 seconds.
This works, but not always. The IR beams work fine and are triggered everytime the train passes.
The light sensor however is not, sometimes it is triggered and the train stops, sometimes it does not.

I have the feeling that when the train passes the light sensor is is not always triggered in the loop, maybe because of a timing issue.
Hereby the full code. Any advice?

/*
Sketch to let 9V train run with L298N motor and :
- stop at light sensor
- let leds blink between ir sensor 1 and 2
*/

//Pins
const int IN1=24;             //Connects to IN1 on L298N
const int IN2=26;             //Connects to IN2 on L298N
const int ENA=7;              //PWN motor switch
const int irsensorPIN_01 = 3; //IR sensor pin that detects (un)broken east
const int irsensorPIN_02 = 4; //IR sensor pin that detects (un)broken west
const int LDR = A0;           //Pin connecting to Light sensor
const int LED_RIGHT = 9;      //LED red right
const int LED_LEFT = 10;      //LED red left

//Variables
int LDRValue=0;                         //LDR value used to write to serial monitor
int LDRValNow;                          //Variable to save light sensor value
String last_LDR;                        //Save previous LDR state
unsigned long previousMillis=0;         //Save time to prevent object blocking the sensor
int sensorState_01 = 0;                 // variable for reading the ir sensor 01 status
int sensorState_02 = 0;                 // variable for reading the ir sensor 02 status

//Settings
const int lightSensitivity=500;         //Set switching point for light sensor (LDR)
const int interval=5000;                //Pause period
int minimumLedTime=200;                 //Minimum time LEDs should be on
const int numberBlinks = 5;            //Minimum number of blinks

//Possible states of the state machine
enum States {
 CHECK_IRBEAM,                         //Check if Beams are broken
 CROSSING_OCCUPIED,                    //Blink LEDs if train is within 2 IR beams
 CHECK_LDR,                            //Check if light sensor is blocked
 TRAIN_GO,                             //Let train move
 EASTBOUND_APPROACH,                   //Train comes in from east
 WESTBOUND_APPROACH,                   //Train comes in from west
 APPROACH_COMMON,                      //Generic stuff to set  
};
States state = TRAIN_GO;                //Default state

void setup() {
 // put your setup code here, to run once:
 Serial.begin(9600);                   //Write to log
 pinMode(IN1,OUTPUT);                  //Motor pin 1
 pinMode(IN2,OUTPUT);                  //Motor pin 2
 pinMode(LED_RIGHT, OUTPUT);           //Write to red led on the right
 pinMode(LED_LEFT, OUTPUT);            //Write to red led on the left

 // initialize the sensor 01 pin as an input:
 pinMode(irsensorPIN_01, INPUT);
 digitalWrite(irsensorPIN_01, HIGH); // turn on the pullup
 // initialize the sensor 02 pin as an input:
 pinMode(irsensorPIN_02, INPUT);
 digitalWrite(irsensorPIN_02, HIGH); // turn on the pullup

 //Set fixed train direction which is stopped
 digitalWrite(IN1,LOW);
 digitalWrite(IN2,LOW);
}

void loop() {
 // put your main code here, to run repeatedly:
Serial.println("0 ik loop");
 //When the train blocks the sensor, the lightsensitivity increases to above set sensitivity
 // check if the sensor beam is broken, sensorstate is LOW
 if (analogRead(LDR) > lightSensitivity) 
 { //Train is on sensor
   if(millis() - previousMillis > interval)
   {  //Time to start train again
     last_LDR = "";
     analogWrite(ENA,80);           //Motor speed
   }
   else if(last_LDR != "Blocked")
   {
     analogWrite(ENA,0);                     //poweroff train
     previousMillis=millis();                //Save the time
     last_LDR="Blocked";                     //Remember previous state
     Serial.println(previousMillis);
     Serial.println(interval);delay(200);
   }
 }
 else {analogWrite(ENA,80);}       //Also drive as not in front
/////////////////////////////////////////////////
 static int state = TRAIN_GO;    //Start with Running
 switch(state) {
   case TRAIN_GO:                //Let train move
     if(millis() - previousMillis > interval){  //Time to start train
Serial.println("2-In train_go");
       digitalWrite(IN2, LOW);  //This sketch goes only one direction
       digitalWrite(IN2, HIGH);  //This sketch goes only one direction
       analogWrite(ENA,80);      //Motor speed
       previousMillis=millis();  //Save the time
       state = CHECK_IRBEAM;
     }
     break;
   case CHECK_IRBEAM:         //Check if Beams are broken
Serial.println("4-check irbeam");
       sensorState_01 = digitalRead(irsensorPIN_01); //reads ir sensor 01 value      
       sensorState_02 = digitalRead(irsensorPIN_02); //reads ir sensor 02 value
       // check if the sensor beam is broken, sensorstate is LOW
       if (sensorState_01 == LOW && sensorState_02 == HIGH) {
         state = EASTBOUND_APPROACH;      } //Sensor 01 is broken
       else if (sensorState_01 == HIGH && sensorState_02 == LOW) {
         state = WESTBOUND_APPROACH;      } //Sensor 02 is broken
     break;
   case EASTBOUND_APPROACH:       //Train comes from east
Serial.println("5-east");
     state = APPROACH_COMMON;
     break;
   case WESTBOUND_APPROACH:       //Train comes from west
Serial.println("5-west");
     state = APPROACH_COMMON;
     break;
   case APPROACH_COMMON:
//      interval = 5000;
     state = CROSSING_OCCUPIED;
     break;
   case CROSSING_OCCUPIED:         //Close bars and blink leds
Serial.println("6-occupied");
     blinkLEDs();
     //do other stuff
     // Hang out in occupied state until both detectors are showing clear
     if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) {
       turnOffLEDs();
       state = TRAIN_GO;
     }
     break;
   default:
     state = TRAIN_GO;
Serial.println("Go back");delay(200);
     break;
 }
}

////////////////////////////////////////////////////////
//  LED Animation Routines
////////////////////////////////////////////////////////
void turnOffLEDs(void)  {
   digitalWrite(LED_RIGHT,LOW);
   digitalWrite(LED_LEFT,LOW);
}

void blinkLEDs(void) {
 for (int i = 0; i < numberBlinks; i++) {
   // Blink first light
   digitalWrite(LED_LEFT, HIGH);
   digitalWrite(LED_RIGHT, LOW);
   
   delay(minimumLedTime);
   
   // Blink second light
   digitalWrite(LED_LEFT, LOW);
   digitalWrite(LED_RIGHT, HIGH);     
 
   delay(minimumLedTime);
 }
}

My advice would be to do away with using delay() for timing and switch to millis() instead to avoid the program being blocked whilst the delay()s happen. The effect is exacerbated when you put the delay() in a for loop as you do.

Yeah that helped. I also found some delay statements that I used for printing to the serial monitor. THANKS

Code runs fine now, except for 1 other thing.
So sketch is:

  • Train drives
  • 1of2 ir beams is broken -> blink leds
  • Lightsensor blocked -> train stops

Thing that now does not work an more is to keep the light blinking for a minimum amount of time (or number of blinks) even if the ir beam is not broken any more.
Previous sketch that I had used this:

   for (int i = 0; i < numberBlinks; i++) {
        // Blink first light
        digitalWrite(LED_PIN1, HIGH);
        digitalWrite(LED_PIN2, LOW);
        
        delay(interval);
        
        // Blink second light
        digitalWrite(LED_PIN1, LOW);
        digitalWrite(LED_PIN2, HIGH);     
     
        delay(interval);         
    }

However in the new method I can't get this to work again.
I tried in several places to put an if statement in that checks for millis, but it is either reuslting in the opposit (takes the amount of millis to wait before blinking) or doing nothing.
if (millis() - previousMinimumLedTime > minimumLedTime)

Most logical place to do a check like this was when I call the function turnOffLEDs(). So only call this if there is a minimum amount of time between the last blink and this call.

Any ideas?

Full code

/*
Sketch to let 9V train run with L298N motor and :
- stop at light sensor
- let leds blink between ir sensor 1 and 2
*/

//Pins
const int IN1=24;             //Connects to IN1 on L298N
const int IN2=26;             //Connects to IN2 on L298N
const int ENA=7;              //PWN motor switch
const int irsensorPIN_01 = 3; //IR sensor pin that detects (un)broken east
const int irsensorPIN_02 = 4; //IR sensor pin that detects (un)broken west
const int LDR = A0;           //Pin connecting to Light sensor
const int LED_RIGHT = 9;      //LED red right
const int LED_LEFT = 10;      //LED red left

//Variables
int LDRValue=0;                         //LDR value used to write to serial monitor
int LDRValNow;                          //Variable to save light sensor value
String last_LDR="Unblocked";            //Save previous LDR state
unsigned long previousLDRMillis=0;      //Save time to prevent object blocking the LDR sensor
unsigned long previousMillis=0;         //Save time to prevent object blocking the IR sensor
int sensorState_01 = 0;                 // variable for reading the ir sensor 01 status
int sensorState_02 = 0;                 // variable for reading the ir sensor 02 status

//Settings
const int lightSensitivity=500;         //Set switching point for light sensor (LDR)
const int interval=5000;                //Pause period
int minimumLedTime=2000;                //Minimum time LEDs should be on
unsigned long previousMinimumLedTime=0; //Previoustime that LED stopped
const int numberBlinks = 5;             //Minimum number of blinks

//Possible states of the state machine
enum States {
  CHECK_IRBEAM,                         //Check if Beams are broken
  CROSSING_OCCUPIED,                    //Blink LEDs if train is within 2 IR beams
  CHECK_LDR,                            //Check if light sensor is blocked
  TRAIN_GO,                             //Let train move
  EASTBOUND_APPROACH,                   //Train comes in from east
  WESTBOUND_APPROACH,                   //Train comes in from west
  APPROACH_COMMON,                      //Generic stuff to set  
};
States state = TRAIN_GO;                //Default state

// Function Prototypes
void turnOffLEDs(void);
void animateLEDs(void);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);                   //Write to log
  pinMode(IN1,OUTPUT);                  //Motor pin 1
  pinMode(IN2,OUTPUT);                  //Motor pin 2
  pinMode(LED_RIGHT, OUTPUT);           //Write to red led on the right
  pinMode(LED_LEFT, OUTPUT);            //Write to red led on the left

  // initialize the sensor 01 pin as an input:
  pinMode(irsensorPIN_01, INPUT);
  digitalWrite(irsensorPIN_01, HIGH); // turn on the pullup
  // initialize the sensor 02 pin as an input:
  pinMode(irsensorPIN_02, INPUT);
  digitalWrite(irsensorPIN_02, HIGH); // turn on the pullup

  //Set fixed train direction which is stopped
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,LOW);
}

void loop() {
  //When the train blocks the sensor, the lightsensitivity 
  //increases to above set sensitivity. Act upon
  if (analogRead(LDR) > lightSensitivity) 
  { //Train is on sensor
    if((millis() - previousLDRMillis > interval) && (last_LDR == "Blocked"))
    {  //Time to start train again
      analogWrite(ENA,80);                    //Motor speed
      last_LDR="UnBlocked";                   //Remember previous state
    }
    else if(last_LDR != "Blocked")
    {
      analogWrite(ENA,0);                     //poweroff train
      last_LDR="Blocked";                     //Remember previous state
    }
    previousLDRMillis=millis();               //Save the time
  }
  else {analogWrite(ENA,80);}                 //Also drive as not in front
/////////////////////////////////////////////////
  static int state = TRAIN_GO;    //Start with Running
  switch(state) {
    case TRAIN_GO:                //Let train move
      if(millis() - previousMillis > interval){  //Time to start train
        digitalWrite(IN2, LOW);  //This sketch goes only one direction
        digitalWrite(IN2, HIGH);  //This sketch goes only one direction
        analogWrite(ENA,80);      //Motor speed
        previousMillis=millis();  //Save the time
        state = CHECK_IRBEAM;
      }
      break;
    case CHECK_IRBEAM:         //Check if Beams are broken
        sensorState_01 = digitalRead(irsensorPIN_01); //reads ir sensor 01 value      
        sensorState_02 = digitalRead(irsensorPIN_02); //reads ir sensor 02 value
        // check if the sensor beam is broken, sensorstate is LOW
        if (sensorState_01 == LOW && sensorState_02 == HIGH) {
          state = EASTBOUND_APPROACH;      } //Sensor 01 is broken
        else if (sensorState_01 == HIGH && sensorState_02 == LOW) {
          state = WESTBOUND_APPROACH;      } //Sensor 02 is broken
      break;
    case EASTBOUND_APPROACH:       //Train comes from east
      state = APPROACH_COMMON;
      break;
    case WESTBOUND_APPROACH:       //Train comes from west
      state = APPROACH_COMMON;
      break;
    case APPROACH_COMMON:
      state = CROSSING_OCCUPIED;
      break;
    case CROSSING_OCCUPIED:         //Close bars and blink leds
      blinkLEDs();
      // Hang out in occupied state until both detectors are showing clear
      if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) 
      {
          turnOffLEDs();
          state = TRAIN_GO;
      }
      break;
    default:
      state = TRAIN_GO;
      break;
  }
}

////////////////////////////////////////////////////////
//  LED Animation Routines
////////////////////////////////////////////////////////
// each "event" (LED) gets their own tracking variable
unsigned long previousMillis_LED_RIGHT=0;
unsigned long previousMillis_LED_LEFT=0;
 
// different intervals for each LED
int interval_LED_RIGHT = 500;
int interval_LED_LEFT = 1000;

// each LED gets a state varaible
boolean LED_RIGHT_state = false;     // the LED will turn ON in the first iteration of loop()
boolean LED_LEFT_state = false;      // need to seed the light to be OFF

void turnOffLEDs(void)  
  {
      digitalWrite(LED_RIGHT,LOW);
      digitalWrite(LED_LEFT,LOW);
  }

void blinkLEDs(void) 
  {
      previousMinimumLedTime=millis();  //Save the time
           // get current time stamp
           // only need one for both if-statements
           unsigned long currentMillis = millis();
 
           // time to toggle LED on LED_RIGHT?
           if ((unsigned long)(currentMillis - previousMillis_LED_RIGHT) >= interval_LED_RIGHT) 
           {
              LED_RIGHT_state = !LED_RIGHT_state;
              digitalWrite(LED_RIGHT, LED_RIGHT_state);
              // save current time to LED_RIGHT's previousMillis
              previousMillis_LED_RIGHT = currentMillis;
           }
 
           // time to toggle LED on LED_LEFT?
           if ((unsigned long)(currentMillis - previousMillis_LED_LEFT) >= interval_LED_LEFT) 
           {
              LED_LEFT_state = !LED_LEFT_state;
              digitalWrite(LED_LEFT, LED_LEFT_state);
              // save current time to LED_LEFT's previousMillis
              previousMillis_LED_LEFT = currentMillis;
            }
  }

My advice, based on what I think you want to do, but I could be wrong, would be to have a separate switch/case in loop() with 2 cases. One, LEDs blinking and a second, LEDs off. When you want the LEDs to blink set the switch variable to LEDs blinking then each time through loop() determine whether the blink period has ended and if so change the state of the LEDs, or if the overall blink period has ended set the state to LEDs off to stop them blinking.

Not sure I am getting that ...

I'll explain you the setup. I have a small loop of rail track where a train drives on. This loop has an irbeam(it actually has 2, but that does not matter for this case). The moment the train blocks the irbeam the leds start blinking. This is triggered in the code in case "CHECK_IRBEAM:" and executed in case "CROSSING_OCCUPIED:".
If the sensor is unblocked again, it triggers in case "CROSSING_OCCUPIED:" the turnOffLEDs() function.

The only thing I want is that the blinking does not stop immediately but keeps going on for a certain time interval.

Unclear how I should do this based on your feedback. Your suggestion is to change the trigger when a led should start blinking and that is not the issue I am encountering.

Your suggestion is to change the trigger when a led should start blinking

Actually, it's not. Let me try and explain better. In loop() create a new switch/case block with 2 states controlled by a new state variable.

State 1 - turn off both LEDs

State 2 - blink the LEDs using millis() timing.
There will be 2 period variables, long and short
When the short period expires change the state of the LEDs and save the start time
When the long period expires change to state 1

The short period controls the flash rate and the long period controls how long the LEDs flash. At any time you want the LEDs to start flashing set the LED flash state variable to 2 and the LEDs will flash for the period set by the long period variable. If for any reason you want to terminate the LED flashing set the LED flash state variable to 1.

In practice I would also have a state 0 that saves the current time and switches to state 2 to begin the flashing, but that would just be to avoid having to set the start time in the main program.

I get your point, however I am still not sure this is solving my issue.
So my sketch has 2 triggers, which is casused by a broken or unbroken ir beam.

If I implement your suggestion, then it will lead to something like this:

TRIGGER - IR beam broken
State 2 - blink the LEDs using millis() timing.
There will be 2 period variables, long and short
When the short period expires change the state of the LEDs and save the start time
When the long period expires change to state 1

The other trigger is
TRIGGER - IR beam unbroken
State 1 - turn off both LEDs

However, I would to keep the leds blinking for this long period and then turn off the leds. So the state 1 should not turn off until a long period expires.

So the state 1 should not turn off until a long period expires.

I think that you meant state 2

Look carefully at state 2 in my suggestion. The exit condition for it is that the long period expires which matches what you say you want. Only then would the switch/case go into state 1 and turn off the LEDs

Whenever you want to start the long period just go into state 0 to restart it. You could even make the long period into a variable so that it could be different in different circumstances. It will be an unsigned long so the maximum period will be over 49 days !

IR beam broken - set state to 0 to start timing and LED flashing
IR beam broken - either set state to 0 to restart timing and LED flashing or set it to 1 to turn the LEDs off

UKHeliBob:
I think that you meant state 2

No I meant what I said as the leds need to keep blinking for a while when the trigger to turn them off has been activated. So I thought this was state 1.

However I am going to try your suggestion first and see if I get the programming done before I'll revert here :slight_smile:

:smiley:
So I managed. I don't think I did it entirely as you suggested, but it works. Let me show how.
Trigger

      animateLEDs();  //Start blinking LEDs
      if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) 
      {
        if (millis() - previousMinimumLedTime > minimumLedTime)  //Only turn of after minimum blink time 
        {
          turnOffLEDs();
          state = TRAIN_GO;
        }
      } else { previousMinimumLedTime = millis(); }

This is the animate part

  switch (ledState) 
  {
    case kLEDsOff:
      //ledTimer = 0;
      // fall through

    case kLEDRightOn:
      if( millis() - previousMillis_blinkTimer > 1000)
      {
        ledState = kLEDLeftOn;
        previousMillis_blinkTimer = millis();
        digitalWrite(LED_LEFT,LOW);
        digitalWrite(LED_RIGHT,HIGH);
      } 
      break;

    case kLEDLeftOn:
      if( millis() - previousMillis_blinkTimer > 1000)
      {
        ledState = kLEDRightOn;
        previousMillis_blinkTimer = millis();
        digitalWrite(LED_LEFT,HIGH);
        digitalWrite(LED_RIGHT,LOW);
      }
      break;

So I used indeed a blinktimer (short timer in your suggestion) to enable the blinking in this Switch state. I also used the long timer (previousMinimumLedTime) in the other switch statement where the ir-beams are triggered. minimumLedTime is set with an int and is 4seconds at the moment. I have to figure out what is good to use when I use this in the official layout.

Thanks for helping out with this one :slight_smile:

Good for getting it working

Looking at the second snippet of code that you posted you don't need 2 cases because they are fundamentally the same. If you need to change the state of an LED pin you can use

digitalWrite(ledPin, !digitalRead(ledPin));

Wow, that saved 10 lines of code and I learned something new. Major thanks here!!

This is left then.

    case kLEDOn:
      if( millis() - previousMillis_blinkTimer > 1000)
      {
        previousMillis_blinkTimer = millis();
        if(digitalRead(LED_LEFT) == LOW && digitalRead(LED_RIGHT) == LOW)
        { digitalWrite(LED_LEFT, HIGH); }     //If both are LOW then Switch Left LED to HIGH
        digitalWrite(LED_LEFT, !digitalRead(LED_LEFT));
        digitalWrite(LED_RIGHT, !digitalRead(LED_RIGHT));
      } 
      break;

  if (digitalRead(LED_LEFT) == LOW && digitalRead(LED_RIGHT) == LOW)Will this ever be true if the LEDs are flashing alternately ?

This is only valid the first time this loop is triggered because they are both off at that moment. Otherwise they will not start to flash alternatively.

Understood. Presumably you turn them both off somewhere in the program.

Yep, as soon as both ir sensors are free this is done.
This is the full code of the sketch which is now working fine.
So ir beams sense a train, blink the leds (simulating a trian crossing) that stop blinking after the ir beams are free and 5 seconds have passed. Other sensor is a light sensor that stops the train (simulating a station or something similar) and let it drive again after 5 seconds.

/*
Sketch to let 9V train run with L298N motor and :
- stop at light sensor
- let leds blink between ir sensor 1 and 2
*/

//Pins
const int IN1=24;             //Connects to IN1 on L298N
const int IN2=26;             //Connects to IN2 on L298N
const int ENA=7;              //PWN motor switch
const int irsensorPIN_01 = 3; //IR sensor pin that detects (un)broken east
const int irsensorPIN_02 = 4; //IR sensor pin that detects (un)broken west
const int LDR = A0;           //Pin connecting to Light sensor
const int LED_RIGHT = 9;      //LED red right
const int LED_LEFT = 10;      //LED red left

//Variables
int LDRValue=0;                         //LDR value used to write to serial monitor
int LDRValNow;                          //Variable to save light sensor value
unsigned long previousLDRMillis=0;      //Save time to prevent object blocking the LDR sensor
unsigned long previousLDRMillis_BL=0;   //Save time to enable train driving away
unsigned long previousMillis=0;         //Save time to prevent object blocking the IR sensor
unsigned long previousMinimumLedTime=0; //Previoustime that LED stopped
int sensorState_01 = 0;                 // variable for reading the ir sensor 01 status
int sensorState_02 = 0;                 // variable for reading the ir sensor 02 status

//Settings
const int lightSensitivity=500;         //Set switching point for light sensor (LDR)
const int interval=5000;                //Pause period
const int minimumLedTime=4000;          //Minimum time LEDs should be on

//Possible states of the state machine
enum States {
  CHECK_IRBEAM,                         //Check if Beams are broken
  CROSSING_OCCUPIED,                    //Blink LEDs if train is within 2 IR beams
  CHECK_LDR,                            //Check if light sensor is blocked
  TRAIN_GO,                             //Let train move
  EASTBOUND_APPROACH,                   //Train comes in from east
  WESTBOUND_APPROACH,                   //Train comes in from west
  APPROACH_COMMON,                      //Generic stuff to set  
};
States state = TRAIN_GO;                //Default state

// Function Prototypes
void turnOffLEDs(void);
void animateLEDs(void);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);                   //Write to log
  pinMode(IN1,OUTPUT);                  //Motor pin 1
  pinMode(IN2,OUTPUT);                  //Motor pin 2
  pinMode(LED_RIGHT, OUTPUT);           //Write to red led on the right
  pinMode(LED_LEFT, OUTPUT);            //Write to red led on the left

  // initialize the sensor 01 pin as an input:
  pinMode(irsensorPIN_01, INPUT);
  digitalWrite(irsensorPIN_01, HIGH); // turn on the pullup
  // initialize the sensor 02 pin as an input:
  pinMode(irsensorPIN_02, INPUT);
  digitalWrite(irsensorPIN_02, HIGH); // turn on the pullup

  //Set fixed train direction which is stopped
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,LOW);
}

void loop() 
{
////////////////////////////////////////////////////////////
// When the train blocks the sensor, the lightsensitivity //
//increases to above set sensitivity. Act upon            //
////////////////////////////////////////////////////////////
  LDRValNow = analogRead(LDR);
  if ( LDRValNow > lightSensitivity) 
  { //Train is on sensor
    if(( LDRValue <= lightSensitivity) && (millis() - previousLDRMillis_BL > interval))
    {
      analogWrite(ENA,0);                     //poweroff train
      previousLDRMillis=millis();             //Save the time
    }
    else if(millis() - previousLDRMillis > interval)  //Drive away even if sensor is blocked
    { 
      previousLDRMillis_BL = millis();        //Save time to make sure train has time to drive away
      analogWrite(ENA,80);                    //Motor speed
    }
  }
  else {analogWrite(ENA,80);}                 //Also drive as not in front
  LDRValue = LDRValNow;                       //save for the next time

////////////////////////////////////////////////////////////
// Here the state machine comes in                        //
////////////////////////////////////////////////////////////
  static int state = TRAIN_GO;    //Start with Running
  switch(state) {
    case TRAIN_GO:                //Let train move
      if(millis() - previousMillis > interval){  //Time to start train
        digitalWrite(IN2, LOW);  //This sketch goes only one direction
        digitalWrite(IN2, HIGH);  //This sketch goes only one direction
        analogWrite(ENA,80);      //Motor speed
        previousMillis=millis();  //Save the time
        state = CHECK_IRBEAM;
      }
      break;
//    case CHECK_LDR:            //Check if light sensor is blocked

//      state = TRAIN_GO;
//      break;
    case CHECK_IRBEAM:         //Check if Beams are broken
        sensorState_01 = digitalRead(irsensorPIN_01); //reads ir sensor 01 value      
        sensorState_02 = digitalRead(irsensorPIN_02); //reads ir sensor 02 value
        // check if the sensor beam is broken, sensorstate is LOW
        if (sensorState_01 == LOW && sensorState_02 == HIGH) {
          state = EASTBOUND_APPROACH;      } //Sensor 01 is broken
        else if (sensorState_01 == HIGH && sensorState_02 == LOW) {
          state = WESTBOUND_APPROACH;      } //Sensor 02 is broken
      break;
    case EASTBOUND_APPROACH:       //Train comes from east
      state = APPROACH_COMMON;
      break;
    case WESTBOUND_APPROACH:       //Train comes from west
      state = APPROACH_COMMON;
      break;
    case APPROACH_COMMON:
      previousMinimumLedTime = millis();
      state = CROSSING_OCCUPIED;
      break;
    case CROSSING_OCCUPIED:         //Close bars and blink leds
      animateLEDs();  //Start blinking LEDs
      //do other stuff
      // Hang out in occupied state until both detectors are showing clear
      if ((digitalRead(irsensorPIN_01) == HIGH) && (digitalRead(irsensorPIN_02) == HIGH)) 
      {
        if (millis() - previousMinimumLedTime > minimumLedTime)  //Only turn of after minimum blink time 
        {
          turnOffLEDs();
          state = TRAIN_GO;
        }
      } else { previousMinimumLedTime = millis(); }
      break;
    default:
      state = TRAIN_GO;
      break;
  }
}

////////////////////////////////////////////////////////////
//  LED Animation Routines                                //
////////////////////////////////////////////////////////////
enum
{
  kLEDsOff = 0,
  kLEDOn
};

int ledState = kLEDsOff;                                 //Default state
unsigned long previousMillis_blinkTimer = millis();      //Timer to switch blinking

void turnOffLEDs(void) 
{
  digitalWrite(LED_RIGHT,LOW);                           //By default all leds off
  digitalWrite(LED_LEFT,LOW);                            //By default all leds off
  ledState = kLEDsOff;
}

void animateLEDs(void) 
{
  switch (ledState) 
  {
    case kLEDsOff:
      // fall through

    case kLEDOn:
      if( millis() - previousMillis_blinkTimer > 1000)
      {
        previousMillis_blinkTimer = millis();
        if(digitalRead(LED_LEFT) == LOW && digitalRead(LED_RIGHT) == LOW)
        { digitalWrite(LED_LEFT, HIGH); }     //If both are LOW then Switch Left LED to HIGH
        digitalWrite(LED_LEFT, !digitalRead(LED_LEFT));
        digitalWrite(LED_RIGHT, !digitalRead(LED_RIGHT));
      } 
      break;
  }
}