Changing LED state with IR sensors

Hi

I am trying to make a simple signal system for a model railway using IR sensors to set the signals from green to Red. my problem is I need the led state to switch when the sensor at the start of the block is tripped (LOW) and to change the signal back to green after the train has completly cleared the sensor at the other end of the block (switching from LOW back to HIGH). I have seen tutorials on how to switch led states when the switch/sensor switchers from LOW to HIGH, but this won't work in this case.
Does anybody know of a simple sketch which would point me in the direction I need to go.

1 Like

Let me check I understand:

  • You have two sensors
  • When sensor 1 (start of block) goes low, turn off green and turn on red
  • When sensor 2 (other end of block) changes from low to high turn off red and turn on green

[Edit, I swapped green / red as I think I had it the wrong way round when I first posted]

Have I understood correctly?

1000 words

a7

@loco4515 - no matter we have it slightly right or wrong, you are going to need to use edge detection or state change detection.

There is an example in the IDE and a competent article covering this basic signal handling concept, start here:

https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection/

State change detection doesn't know or care if you talking about pushbuttons or train sesnors…

HTH

a7

1 Like

If any train is longer than the block, yes. If not, it’s not needed. Doing the point 1 switching several time will not be seen. The same goes for the point 2 action.

1 Like

Wait, does it have to work for a train going in the opposite direction?

I assume the point is to not have two trains occupying the block.

a7

2 Likes

Hi @loco4515 !

This Wokwi project is following the diagram @alto777 provided in post 3 (thanks for the "1000 words diagram"! :slightly_smiling_face: ):

https://wokwi.com/projects/457939146850369537

The red button emulates an IR sensor at the start of the block and the green one at the end. The start sensor reacts on the first falling edge (HIGH to LOW), the end button on the last rising edge (LOW to HIGH) assuming that the end sensor is continousely interrupted while the train passes it (if not we have to add a little bit of logic regarding the timing).

Sketch
/*
  Forum: https://forum.arduino.cc/t/changing-led-state-with-ir-sensors/1434325/2
  Wokwi: https://wokwi.com/projects/457939146850369537


  Simple Signal Light
    starts with green signal
    press red button (train enters block)
    signal switches to red
    press green button (train starts leaving block)
    release green button (train hast completely left block)
    signal switches to green again

  ec2021

*/

constexpr byte redPin   {12};
constexpr byte greenPin {11};
constexpr byte blockInPin  {7};
constexpr byte blockOutPin {6};
enum class BLOCK {FREE, OCCUPIED};

BLOCK blockState = BLOCK::FREE;

class blockSensor {
  private:
    byte pin;
    byte state = HIGH;
    byte lastState = HIGH;
    unsigned long lastChange = 0;
    boolean changed() {
      byte actState = digitalRead(pin);
      if (actState != lastState) {
        lastChange = millis();
        lastState = actState;
      }
      if (actState != state && millis() - lastChange > 30) {
        state = actState;
        return true;
      }
      return false;
    };
  public:
    void init(byte Pin) {
      pin = Pin;
      pinMode(pin, INPUT_PULLUP);
    };
    boolean pressed() {
      if (changed()) {
        return !state;
      } else {
        return false;
      }
    };
    boolean released() {
      if (changed()) {
        return state;
      } else {
        return false;
      }
    };
};

blockSensor blockIn, blockOut;

void setup() {
  Serial.begin(115200);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  blockIn.init(blockInPin);
  blockOut.init(blockOutPin);
  signalToGreen();
  Serial.println("Start");
}

void loop() {
  stateMachine();
}

void signalToRed() {
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
}

void signalToGreen() {
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
}

void stateMachine() {
  switch (blockState) {
    case BLOCK::FREE:
      if (blockIn.pressed()) {
        Serial.println("Train entered block ...");
        signalToRed();
        blockState = BLOCK::OCCUPIED;
      }
      break;
    case BLOCK::OCCUPIED:
      if (blockOut.released()) {
        Serial.println("Train left block ...");
        signalToGreen();
        blockState = BLOCK::FREE;
      }
      break;
  }
}

For the moment It works in one direction only and demonstrates the principle. We can adopt the principle to work in both directions if required.

Just let us know ...

[Edit:] As it was easy to implement here the version for both directions (left to right or vice versa):

https://wokwi.com/projects/457940661339301889

Sketch Both Directions
/*
  Forum: https://forum.arduino.cc/t/changing-led-state-with-ir-sensors/1434325/2
  Wokwi: https://wokwi.com/projects/457940661339301889


  Simple Signal Light, both directions
    starts with green signal
    press red or green button (train enters block from LEFT or RIGHT)
    signal switches to red
    press green or red button (train starts leaving block to the RIGHT or to the LEFT)
    release green or red button (train hast completely left the block)
    signal switches to green again

  ec2021

*/

constexpr byte redPin   {12};
constexpr byte greenPin {11};
constexpr byte blockLeftPin  {7};
constexpr byte blockRightPin {6};
enum class BLOCK {FREE, OCCUPIED};

BLOCK blockState = BLOCK::FREE;

class blockSensor {
  private:
    byte pin;
    byte state = HIGH;
    byte lastState = HIGH;
    unsigned long lastChange = 0;
    boolean changed() {
      byte actState = digitalRead(pin);
      if (actState != lastState) {
        lastChange = millis();
        lastState = actState;
      }
      if (actState != state && millis() - lastChange > 30) {
        state = actState;
        return true;
      }
      return false;
    };
  public:
    void init(byte Pin) {
      pin = Pin;
      pinMode(pin, INPUT_PULLUP);
    };
    boolean pressed() {
      if (changed()) {
        return !state;
      } else {
        return false;
      }
    };
    boolean released() {
      if (changed()) {
        return state;
      } else {
        return false;
      }
    };
};

blockSensor blockLeft, blockRight;
blockSensor *blockPtr;

void setup() {
  Serial.begin(115200);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  blockLeft.init(blockLeftPin);
  blockRight.init(blockRightPin);
  signalToGreen();
  Serial.println("Start");
}

void loop() {
  stateMachine();
}

void signalToRed() {
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
}

void signalToGreen() {
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
}

void stateMachine() {
  switch (blockState) {
    case BLOCK::FREE:
      if (blockLeft.pressed()) {
        blockOccupied("LEFT");
        blockPtr = &blockRight;
      }
      if (blockRight.pressed()) {
        blockOccupied("RIGHT");
        blockPtr = &blockLeft;
      }
      break;
    case BLOCK::OCCUPIED:
      if (blockPtr->released()) {
        Serial.println("Train left block ...");
        signalToGreen();
        blockState = BLOCK::FREE;
      }
      break;
  }
}

void blockOccupied(char *msg) {
  Serial.print("Train entered block from ");
  Serial.println(msg);
  signalToRed();
  blockState = BLOCK::OCCUPIED;
}

[Edit no. 2] And here a version that considers that the IR light barrier may be released for short intervals while the train is leaving the block

https://wokwi.com/projects/457942657328082945

Sketch with debounced 'Leaving the Block'
/*
  Forum: https://forum.arduino.cc/t/changing-led-state-with-ir-sensors/1434325/2
  Wokwi: https://wokwi.com/projects/457942657328082945

  Simple Signal Light, both directions, considering that the release my be triggered several times while the train
  passes the end of block

    starts with green signal
    press red or green button (train enters block from LEFT or RIGHT)
    signal switches to red
    press green or red button (train starts leaving block to the RIGHT or to the LEFT)
    release green or red button for at least minReleaseTime (train hast completely left the block)
    signal switches to green again

    If the button that signalizes the leaving of a block is pressed again within minReleaseTime the block will
    be considered to be still occupied.

  ec2021

*/

constexpr byte redPin   {12};
constexpr byte greenPin {11};
constexpr byte blockLeftPin  {7};
constexpr byte blockRightPin {6};
constexpr unsigned long minReleaseTime {1000}; // The time that is required after a release to change state back to FREE
enum class BLOCK {FREE, OCCUPIED, INTERRUPTED};

BLOCK blockState = BLOCK::FREE;
unsigned long releaseTime;

class blockSensor {
  private:
    byte pin;
    byte state = HIGH;
    byte lastState = HIGH;
    unsigned long lastChange = 0;
    boolean changed() {
      byte actState = digitalRead(pin);
      if (actState != lastState) {
        lastChange = millis();
        lastState = actState;
      }
      if (actState != state && millis() - lastChange > 30) {
        state = actState;
        return true;
      }
      return false;
    };
  public:
    void init(byte Pin) {
      pin = Pin;
      pinMode(pin, INPUT_PULLUP);
    };
    boolean pressed() {
      if (changed()) {
        return !state;
      } else {
        return false;
      }
    };
    boolean released() {
      if (changed()) {
        return state;
      } else {
        return false;
      }
    };
};

blockSensor blockLeft, blockRight;
blockSensor *blockPtr;

void setup() {
  Serial.begin(115200);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  blockLeft.init(blockLeftPin);
  blockRight.init(blockRightPin);
  signalToGreen();
  Serial.println("Start");
}

void loop() {
  stateMachine();
}

void signalToRed() {
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
}

void signalToGreen() {
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
}

void stateMachine() {
  switch (blockState) {
    case BLOCK::FREE:
      if (blockLeft.pressed()) {
        blockOccupied("LEFT");
        blockPtr = &blockRight;
      }
      if (blockRight.pressed()) {
        blockOccupied("RIGHT");
        blockPtr = &blockLeft;
      }
      break;
    case BLOCK::OCCUPIED:
      if (blockPtr->released()) {
        Serial.println("Sensor released ...");
        releaseTime = millis();
        blockState = BLOCK::INTERRUPTED;
      }
      break;
    case BLOCK::INTERRUPTED:
      if (millis() - releaseTime >= minReleaseTime) {
        Serial.println("Train left block ...");
        signalToGreen();
        blockState = BLOCK::FREE;
      }
      if (blockPtr->pressed()) {
        //if the sensor triggers again let's go back to occupied
        Serial.println("Oops, train still in block ...");
        blockState = BLOCK::OCCUPIED;
      }
      break;

  }
}

void blockOccupied(char *msg) {
  Serial.print("Train entered block from ");
  Serial.println(msg);
  signalToRed();
  blockState = BLOCK::OCCUPIED;
}

Have fun!

You can accomplish this with a RS flip flop. You could use a CD4013 and the set and clear inputs. Q would be Green and Q\ would Green. You can also do it with discrete logic,

In all the solutions, so far going one way, there is some need to produce a signal on the leading edge of the train entering the block (any part of the train in the block) and another on the trailing edge of the train as it leaves the block (no part of the train in the block).

So I think state change detection.

@Railroader is correct in that it does not matter if the entry signal is done multiple times, or the exit signal same same. If the train fits in the block.

This is true of @gilshultz's SR flip flop idea and we also see it in @ec2021's simulation as

      if (blockIn.pressed()) {
// block becomes occupee


// and
      if (blockOut.released()) {
// block becomes inoccupee

a7

That's it!

I was wondering if the IR signal of a "leaving train" could be interrupted several times (e.g. between the railway carriages) so I added an additional external "debouncing" of about one second in the latest version.

There is still a chance that a train may (due to bad luck) stop in such a way that the IR sensor is not blocked ... That could be taken care of e.g. by using two IR sensors with a distance greater than the gap between two carriages.

There may be further proven solutions to it ...

@ec2021's edits to #7 are intersting.

I didn't play with the pure version (edit 1) very long before getting a red light from what the code considered to be a new train coming the opposite direction; I wonder if this was the motivation to add a guard against that (edit 2).

The algorithm is switch debouncing and state change, but asymmetrical - any train entering can turn the light red the instant the sensor sees the train.

When the train is leaving, however, the bouncing must be seen to stop, which can only be done by a longer lock-out period.

With pushbuttons, we can say contacts that appear closed are on their way to being fully closed and stable, and we can react instantly. Similarly we can say contacts that appear to be open are on their way to being fully open and stable.

There is nothing analogous to a train to consider.

Now… @ec2021 should take the challenge which is creating a Block object that would own some sensors and lamps, so the user would be able to easily code any number of such protected track sections.

Oh, I may be mixing uo red and green, just like I do IRL. :expressionless:

a7

That slipped my mind!

In that case the direction of the train needs to be known. It takes some thinking if using double “point 1” and “point 2” sensing.

I think it would be helpful to know the type of IR sensors, how they will be set up and in best case some data what we get from them in a real scenario...

In principle, yes. But… hopefully no train runs in the opposite direction and messis up the signals.

1 Like

Which sensors?

The avoidance of trains running in opposite directions is a different task...

I think that the first code from my post 7 satisfies the requirements of post 1.

The second allows to drive into the block from either side.

The third version solves issues when the IR light barrier is unblocked for a certain period while the train has not completely left the block.

Maybe. I don’t go into wokwi matters.

Being a railroading guy for 55 years, in the scale 1:1, I’ve looked into those questions quite a few times especially for gates in a crossing road. What happens if a train’s not but stopping in one of the three electrical sections used.

A triggered gates down without the release, train’s left the crossing, certain questions arise etc etc.. An emergency release kicks in after a certain time. Then the gates will not go down for the next train. A reset is needed….

Lots of constructive, possible, posts here.

I believe "real" crossing sensors respond to the passage of individual wheels, I've seen trains approach the crossing, trigger the gate, then stop before entering the crossing. The gates will go back up after a timeout if the train doesn't move. I think it works on pulses from the wheels.
I imagine sensing HO size train wheels would be somewhat difficult. :grin:

Not exactly. The axle with its 2 wheels short circuit the rails. One rail is powered by a low voltage and the other rail detects an axle. Running a museum railroad, in spring, with light railcars the rusted rail don’t always conduct. Timeout or no action is normal. The heavy steam locos doesn’t face this problem.

What You describe happens if the first “entry section” detects a train but the exit section does not. Then a timeout will raise the gates and the logic blocks itself, waiting for a reset.

Single wheel detection is not used as far as I ever heard!

for 2 rail, DC, trains resistors are mounted between the wheels on some axles. For 3 rail, like Maerklin, it’s not needed.

Okey, TNX.