Reading Two Sensors

Hello,

The code below works as I expect it too. I would like the code to only change the state of the LED once the IR has been broken. The sensor is placed between the rails and when a locomotive or a railcar passes over the sensor the state of the LED changes. However the issue is the LED changes state each time there is a break between the cars etc. I need the sensor state to stay LOW. Do I need to implement millis() or sometype of delay/debounce? The reason why i am using 2 sensors (TCRT5000) at the entrance and exit of the block is to determine direction of travel. Thanks for the help in advance and the complete code is below:

int b1leftsensor   = 2; 
int b1rightsensor  = 3; 
int b1Eleftsensor  = 4; 
int b1Erightsensor = 5;

int b1LeftTrafficLamp  = 12; // GREEN
int b1RightTrafficLamp = 11; // RED
int b2LeftTrafficLamp  = 10; // GREEN
int b2RightTrafficLamp =  9; // RED

int b1Right;
int b1Left;
int b1ERight;
int b1ELeft; 

enum { Waiting, TriggerLeft, GoingLeft, TriggerRight, GoingRight, Undefined };

void setup() { 
pinMode (b1leftsensor,    INPUT);
pinMode (b1rightsensor,   INPUT);
pinMode (b1Eleftsensor,   INPUT);
pinMode (b1Erightsensor, INPUT);
pinMode (b1LeftTrafficLamp,   OUTPUT);
pinMode (b1RightTrafficLamp,  OUTPUT);
pinMode (b2LeftTrafficLamp,   OUTPUT);
pinMode (b2RightTrafficLamp,  OUTPUT);
digitalWrite(b1LeftTrafficLamp, HIGH);
digitalWrite(b2LeftTrafficLamp, HIGH);
Serial.begin (9600);
} // END SETUP

int getDOT(int b1Left, int b1Right, int b1ELeft, int b1ERight) {
static int state = Waiting;

switch (state) {
case Waiting:
if ((b1Left && !b1Right) || (b1ELeft && !b1ERight)) { 
state = TriggerRight; 
}
if ((!b1Left && b1Right) || (!b1ELeft && b1ERight)) { 
state = TriggerLeft; 
}
break;

case TriggerRight:
if ((!b1Left) || (!b1ELeft)) { 
state = Waiting; 
}
if ((b1Right) || (b1ERight)) { 
state = GoingRight; 
}
break;

case TriggerLeft:
if ((!b1Right) || (!b1ERight)) { 
state = Waiting; 
}
if ((b1Left) || (b1ELeft)) { 
state = GoingLeft; 
}
break;

case GoingRight:
case GoingLeft:
if ((!b1Left && !b1Right) || (!b1ELeft && !b1ERight)) { 
state = Waiting;
}
break;

case Undefined:
default:
state = Waiting;
}
return state;
}

void loop() {

b1Left   = digitalRead(b1leftsensor);
b1Right  = digitalRead(b1rightsensor);
b1ELeft  = digitalRead(b1Eleftsensor);
b1ERight = digitalRead(b1Erightsensor);

int DOT = getDOT(b1Right, b1Left, b1ERight, b1ELeft); // DOT = Direction of travel

if (DOT == GoingLeft) {
digitalWrite(b1LeftTrafficLamp, LOW);
digitalWrite(b1RightTrafficLamp, HIGH);
Serial.println("OCCUPIED");
} else if (DOT == GoingRight) {
digitalWrite(b1LeftTrafficLamp, HIGH);
digitalWrite(b1RightTrafficLamp, LOW);
Serial.println("UNOCCUPIED");
}

} // END MASTER LOOP

Yes, it sounds like you need to ignore any signals that are less than a certain length of time. You could get the value from millis whenever the sensor reads no car and set up an if statement to compare that with the current value of millis and only turn on the led if a certain interval has passed.

Where would I implement the millis code? in the IF statements or in the switch case statements?

PilotinControl: Where would I implement the millis code? in the IF statements or in the switch case statements?

Neither. You'd set b1Left, b1Right, b1ELeft and/or b1ERight (weird names) to HIGH or LOW based on the pin reading and the time since the last pin change before the call to getDOT().

The sensor is placed between the rails

Which sensor? The one connected to b1Left, to b1Right, to b1ELeft, or the one connected to b1ERight?

@PaulS -All sensors are placed between the rails....the first pair is b1Left and b1Right...when those 2 are crossed it should keep the Red LED on...until being crossed in reverse direction b1Right and b1Left....however when each car rolls across it trips the sensors causing the lights to flicker from red and green. So a timing function needs to be added to either keep the lights from flickering OR shut off the IR sensor temporarily. I'm used to using delays in other programming so using millis would be different. Thanks for the help.

The first thing to do is to look at the state change detection example. What is interesting is when the pin changes state, not when the pin IS HIGH or IS LOW.

When you know that, you can see that the state change interval is longer when the car is over the sensor, and shorter when the gap between the cars is over the sensor. If the interval between the state changes is below some threshold (the time between the cars), ignore the state change.

Is this related to the problem discussed previously or a new issue?

Here's some pseudocode for how I would handle it:

// at global scope
unsigned long lastCarDetected = 0


//  in loop or wherever

if (sensor detects a car) {
     lastCarDetected = millis();

   //  set the leds like you want when a car is there
}

else if (millis() - lastCarDetected >= someReasonableInterval){
     
   //   cars have been gone long enough to say they're gone
   //   set the leds like you want for no cars
}

@Delta_G - yes that psuedo code works just fine…however one caveat is when the time is up the signal goes to from red to green even when the train is still in that block of track. The signal needs to stay red once the block has been cleared either going forward into another block or reversing direction and exiting the same way it came in…that’s why I am using 2 sensors to determine direction. I just need to solve the intermittent between cars. Break beam once…hold LED state… then break beam again…in the reverse direction…change LED state.

If the car is still blocking the sensor then the time would never be up. It keeps resetting lastCarDetected to millis() as long as the car is there. If you park the car on top of the sensor then it just keeps setting lastCarDetected to millis and then seeing if that has been longer than so long since millis(). As long as your interval is longer than a millisecond or two you got nothing to worry about.

Actually, it's even better than that. Forgot that I used an else if. So if the car is left there it won't even bother check the interval.

I've uploaded a video of the current code in action....as you can see in the video the 2 sensors are lit up....and you can see the signal beside the tracks....you will notice when the train rolls over the sensor it changes as each car that rolls over the sensor also changes.....the flickering needs to be stopped....when the trains rolls over LED state changes to RED once the beam has been broken....and when the train goes in reverse it should change the RED back to GREEN and vice versa. The train does not sit over the sensor. I hope I am being clear on what I am trying to accomplish. Thanks

Video link here: http://www.pittsburghandohiorailroad.com/demolayout/DEMO-VIDEO.mpg

I've uploaded a video of the current code in action

But, not the current code.

@PaulS - the code in the video is posted below for your reference:

int leftsensor   = 4; // BLOCK ONE WEST LEFT 
int rightsensor  = 5; // BLOCK ONE WEST RIGHT

int LeftTrafficLamp  = 12; // GREEN
int RightTrafficLamp = 11; // RED

enum { Waiting, TriggerLeft, GoingLeft, TriggerRight, GoingRight, Undefined };

int getDOT(int Left, int Right) {
static int state = Waiting;

switch (state) {
case Waiting:
if (Left && !Right) { 
state = TriggerRight; 
}
if (!Left && Right) { 
state = TriggerLeft; 
}
break;

case TriggerRight:
if (!Left) { 
state = Waiting; 
}
if (Right) { 
state = GoingRight; 
}
break;

case TriggerLeft:
if (!Right) { 
state = Waiting; 
} // false alarm...
if (Left) {
state = GoingLeft; 
}
break;

case GoingRight:
case GoingLeft:

if (!Left && !Right) { 
state = Waiting; 
}
break;

case Undefined:
default:
state = Waiting;
}
return state;
}

void setup() {
pinMode (leftsensor,         INPUT);
pinMode (rightsensor,        INPUT);
pinMode (LeftTrafficLamp,   OUTPUT);
pinMode (RightTrafficLamp,  OUTPUT);
digitalWrite(LeftTrafficLamp, HIGH);
Serial.begin (9600);
} // END MASTER SETUP

void loop() {

int Left  = digitalRead(leftsensor);
int Right = digitalRead(rightsensor);

int DOT = getDOT(Right, Left);

// do something with "state" variable..., which will have one of the values in the enum above...
if (DOT == GoingLeft) {
digitalWrite(LeftTrafficLamp, HIGH);
digitalWrite(RightTrafficLamp, LOW);
} else if (DOT == GoingRight) {
digitalWrite(LeftTrafficLamp,   LOW);
digitalWrite(RightTrafficLamp, HIGH);
//} else {
//digitalWrite(LeftTrafficLamp,  LOW);
//digitalWrite(RightTrafficLamp, LOW);
}
//...
} // END MASTER LOOP

I don't see anything in that code that resembles storing when a pin changed state, or that resembles a comparison of this time - last time vs. the required interval between changes to signify that the change was not caused by a gap between cars.

Did I miss something?

PilotinControl: The train does not sit over the sensor.

I was responding to:

PilotinControl: @Delta_G - yes that psuedo code works just fine....however one caveat is when the time is up the signal goes to from red to green even when the train is still in that block of track.

I thought you were saying that it would go green while the train was still blocking the sensor.

So let me make sure I've got it straight. This is what I think you want. When the train is blocking the sensor, the light should be red. When the train is not blocking the sensor the light should be green except that you don't want the brief periods where the space between cars doesn't block the sensor to turn it green. If that's what you want then the pseudo-code I posted above would do it.

Or is it that the train passes the sensor once and that turns the light red and when the train passes the sensor a second time it turns it back to green?

One thing that might do some good would be for you to look at the examples about debouncing switches. That is happening on a much much smaller time scale but it is basically the same idea, ignoring signals that last for too short of a time. The same techniques will help you to solve the problem you describe with the gap between the cars triggering your sensor for short periods of time.

Delta_G: I was responding to:

Or is it that the train passes the sensor once and that turns the light red and when the train passes the sensor a second time it turns it back to green?

The quote above is exactly what I am looking for....and yes the debounce triggering is what I was leaning towards and I've been searching for examples using a sensor and all I've found is push button references.

PilotinControl: The quote above is exactly what I am looking for....and yes the debounce triggering is what I was leaning towards and I've been searching for examples using a sensor and all I've found is push button references.

OK, now I understand. Yes, a debouncing routine with a really long bounce period is what you need. Sure they're all for pushbuttons, but all their doing is looking for a HIGH and LOW. Your sensor is no different. Pretend the output of your sensor was a little elf hiding under the track and pushing a button as the cars go over.

So really, the same pseudocode as what I wrote above, but instead of haveing the led stuff in those if statements, you set whatever variables you need to set to mark that the train had passed once or twice or whatever. Basically all it is doing is giving you an if else telling you whether a car is present or not. You can react to that however you want.

// at global scope
unsigned long lastCarDetected = 0


//  in loop or wherever

if (sensor detects a car) {
     lastCarDetected = millis();
   
     // A car is present, so the train is passing the sensor
     //  Do whatever needs to be done when a car passes the sensor
     // maybe there is a boolean variable somewhere that we can use later

     carOverSensor = true;
}

else if (millis() - lastCarDetected >= someReasonableInterval){
     
   carOverSensor = false;
}


// maybe there's another boolean that tells whether the train is in the red zone
// and a boolean telling whether there was a car last time we checked (State Change Example)

if(!carOverSensor && carOverSensorLastTime){
    // We just cleared the sensor so toggle whether or not we're in the red zone
    inTheRedZone = !inTheRedZone;
}

// save the current state for next loop
carOverSensorLastTime = carOverSensor;


if(inTheRedZone){
   // make the lights red
}
else {
   // make the lights green
}

You just have to think about the logic of what you want to have happen. Write it out on paper in regular english one timebefore you ever even start to code. Play with it until you can get a good flow through it that ends up with what you want and then the code will just about write itself from that point.

@Delta_G

Ok I really stripped this code down with using just one sensor and I have played around with the timing intervals and I am still getting intermittent red/green back and forth. Its the else statement…when nothing is over the sensor it changes back to green. I’ve attached a drawing of the setup maybe you can make heads or tails of it. If the train is going from Left to Right it will pass over the left sensor first which will case the signal to go from red to green…while in between the left and right sensor the signals needs to stay Red…once it passes over and clears the right signal the light should turn green…and the same actions should happen from right to left…clockwise/counter clockwise or eastbound/westbound. I hope that clears up any confusion on what I am trying to accomplish.

int rightsensor  = 5;

int LeftTrafficLamp  = 12; // GREEN
int RightTrafficLamp = 11; // RED

unsigned long lastCarDetected = 0;
int someReasonableInterval = 100;

bool carOverSensor, carOverSensorLastTime, inTheRedZone, inTheGreenZone;

void setup() {
pinMode (rightsensor,        INPUT);
pinMode (LeftTrafficLamp,   OUTPUT);
pinMode (RightTrafficLamp,  OUTPUT);
digitalWrite(LeftTrafficLamp, HIGH);
Serial.begin (9600);
} // END MASTER SETUP

void loop() {

int Right = digitalRead(rightsensor);

if (Right == LOW) {
lastCarDetected = millis();
     // A car is present, so the train is passing the sensor
     //  Do whatever needs to be done when a car passes the sensor
     // maybe there is a boolean variable somewhere that we can use later
carOverSensor = true;
} else if (millis() - lastCarDetected >= someReasonableInterval){
carOverSensor = false;
}


// maybe there's another boolean that tells whether the train is in the red zone
// and a boolean telling whether there was a car last time we checked (State Change Example)

if(!carOverSensor && carOverSensorLastTime){
// We just cleared the sensor so toggle whether or not we're in the red zone
inTheRedZone = !inTheRedZone;
}

// save the current state for next loop
carOverSensorLastTime = carOverSensor;

if(inTheRedZone){
   // make the lights red
digitalWrite(LeftTrafficLamp, LOW);
digitalWrite(RightTrafficLamp, HIGH);
} else {
  // make the lights green
digitalWrite(LeftTrafficLamp,   HIGH);
digitalWrite(RightTrafficLamp, LOW);
}

} // END MASTER LOOP

Would it be feasible to maintain the occupancy and direction based only on the sequence of sensor activations? For example, initially the block is unoccupied and the direction is some default value. If a sensor is triggered, you know that a train has entered the block, and that it is occupied. You also know the direction, for example if the west sensor was triggered, the direction is eastbound (unless the train reverses). When the east sensor triggers, you know that the train has reached the end of the block, so it is now unoccupied (or soon will be...). Then you reset the direction to the default. This system would be immune to gaps because once one sensor is triggered, it can be ignored as the other sensor is the one that is being monitored.