Controlling two motors with two sensors

I’m trying to build what’s really just a traffic light, but for some reason the logic has me defeated.

I have a railway crossing with two light sensors. One sensor detects east-to-west traffic, the other detects north-to-south traffic.

The Arduino also powers the railway trains via PWM. If the sensor detects a train on the the east/west track, then it should power down the north/south track… but only when the north/south train is at its sensor. Likewise, the east/west train should stop at its sensor, but only when the north/south sensor is already triggered.

I’m struggling to express this problem in a way that I can break down into arduino code. I don’t want to give any pre-set priority to one track/sensor - simply, whichever train gets to the crossing first has priority.

Anyone got any ideas, or examples of similar setups?

Anyone got any ideas

Sure. Start with just reading sensors.

When you can consistently determine that there is a north-south train and you can consistently determine that there is an east-west train, then think about recording WHEN the sensor changed state (the state change detection example bears reading).

When you can tell which train got there first, the rest is trivial.

A state machine is the approach for this kind of control - find a tutorial on designing using state machines, this sort of thing is often used as an example.

Yes, I was planning on using states (a simple HIGH/LOW for each sensor), but I wasn't sure I could use two "if" statements with equal priority. Essentially:

If Sensor0 = High; If Sensor1 = High then Stop Train1 Else Move Train1

If Sensor1 = High; If Sensor0 = High then Stop Train0 Else Move Train0

Do you mean

if (Sensor0 == HIGH && Sensor1 == HIGH) {
   // stop train1
}
else {
   // move train1
}

OR

do you mean

if (Sensor0 == HIGH) {
  if( Sensor1 == HIGH) {
   // stop train1
  }
}
else {
   // move train1
}

If you study it carefully you will see that the else clauses will behave differently (I think).

And note the ==

...R

I mean the second one Robin; thanks for formatting it properly. My issue is that Train1 should be free to move past sensor1 at any time, but only stop at sensor1 if sensor0 was previously triggered. Then I need the opposite to happen for Train0.

To complicate further, I'll be adding a time to the sensors so that once their states change, they'll be held for 5 secs and then reset automatically. But that's another story :D

Grrrmachine: I mean the second one Robin; thanks for formatting it properly. My issue is that Train1 should be free to move past sensor1 at any time, but only stop at sensor1 if sensor0 was previously triggered. Then I need the opposite to happen for Train0.

Did you try my example code? What happened?

Maybe a better way to think of the problem is to start with two variables relating to the trains

trainAMoving = true; trainBMoving = true;

and then use your detectors to change these.

Also, it would be useful to have a diagram of the junction with the locations of the sensors shown.

...R

I tried this variation of your code Robin:

 if (Sensor0Value > light_sensitivity)
    {
       if (Sensor1Value > light_sensitivity) {
        digitalWrite(RED_LIGHT, LOW);
       }
    }
if (Sensor1Value > light_sensitivity)
    {
      if (Sensor0Value > light_sensitivity) {
       digitalWrite(GREEN_LIGHT, LOW);
      }
    }
 else {
   digitalWrite(RED_LIGHT, HIGH);
   digitalWrite(GREEN_LIGHT, HIGH);
 }
}

The lights here represent train power.

RESULT: If one sensor is blocked ( > light_sensitivity), then there is no change to the lights. If a second sensor is blocked, then both lights turn off. If either sensor is then unblocked, both lights turn back on.

So that you can appreciate the layout:

RED_LIGHT represents the red train, GREEN_LIGHT represents the green train. Arrows indicate direction of movement. The light sensors are placed just ahead of the junction so that the trains would have time/space to stop.

I did try to solve this using a state machine, but couldn't get it to work. So I'm trying to write it in "baby c" (lots of "if" statements) to see if I can make it work that way first.

Update: If I change the above code to include “if… ELSE if… and else…” (instead of “if… if… else”) then I get some strange behaviour:

Cover either sensor: nothing happens.

Cover Sensor1, then Sensor0: Red LED turns off as expected. It stays off until Sensor1 is uncovered, even if Sensor0 is uncovered first.

Cover Sensor0, then Sensor1: Red LED turns off, which is not what I wanted. It stays off until Sensor0 is uncovered, even if Sensor1 is uncovered first.

So nothing here will make GREEN_LED = LOW.

This pseudo code is not complete but I hope it will help to clarify things

// Arduino Code

trainAmove = HIGH;
trainBmove = HIGH;

void loop() {
    readDetectors();
    updateStatus();
    trainMove();
}

void readDetectors(){
    trainAapproaching = read detectorA; // assume active HIGH
    trainBapproaching = read detectorB;

}

void updateStatus() {
    if (trainAapproaching == HIGH) {
        trainBmove = LOW;
    }
    if (trainBapproaching == HIGH) {
        trainBmove = LOW;
    }
}

void trainMove() {
    digitalWrite(trainApin, trainAmove);
    digitalWrite(trainBpin, trainBmove);
}

I think 2 things are missing.

There needs to be some way to know which train gets priority - which triggers the detector first? And there needs to be some way to allow train B to proceed after train A has passed the junction.

...R

Robin2: There needs to be some way to know which train gets priority - which triggers the detector first?

That's really the thing that I'm struggling with. It would be really easy to solve this problem if one train always had priority (i.e the green train never stops), but that's not really what I want for this application. And I don't know how to give priority to the first train that activates a sensor.

You need another variable to record whether either sensor has been triggered. Something like

trainComing = false;
trainFirst = '';

if (trainA detected && trainComing == false) {
   trainComing = true;
   trainFirst = 'A'
}
etc etc

and that needs to be integrated into the earlier stuff

...R

Hi,

I've payed some attention to the problem:

I'm not completely sure, but I think that this (apparently simple) problem deserves a finite state machine approach. This means to have "state", "next state", "transition", and "event" variables along with the correspondent table.

Regards

vffgaston: I think that this (apparently simple) problem deserves a finite state machine approach.

Agreed. That is what we are working towards.

...R

I definitely need a state machine. I’m trying to follow Robin’s logic, and it seems to turn off the Green train as soon as a Red train is detected, and vice versa, rather than waiting for the second sensor to be blocked. I’m trying to include a “trainPresent” boolean, but I’m not sure how it would work.

This is the current Finite State Machine. I know it’s cleaner to break it up into separate voids, but I want to get it working rudimentarily first.

/* this program examines two light sensors.
When either light sensor is triggered, it deactivates
the LEDs based on the state table.
*/

//the analog pins used for each sensor
const byte RED_TRAIN_SENSOR = 0; //Red train
const byte GREEN_TRAIN_SENSOR = 1; //Green train

//possible states of the state machine

const byte TRACK_SECTION_CLEAR = 0;  //both light sensors off, power to both trains
const byte RED_TRAIN_DETECTED = 1;  //Red train detected, power to both trains
const byte GREEN_TRAIN_DETECTED = 2; //Green train detected, power to both trains
const byte RED_TRAIN_OFF = 3;  //Both trains detected (Green first), turn off Red train
const byte GREEN_TRAIN_OFF = 4;  //Both trains detected (Red first), turn off Green train

byte state = TRACK_SECTION_CLEAR;

//variables

boolean TrainPresent = false;
unsigned long previousMillis = 0; 
int RedTrainSensorValue = 0;
int GreenTrainSensorValue = 0;
int light_sensitivity = 500;
int interval = 5000;


//the digital pins for the LEDs
const byte RED_LIGHT = 40; //power to Red train 
const byte GREEN_LIGHT = 26;  //power to Green train

void setup() {
  //enable output on the digital pins
  pinMode(RED_LIGHT, OUTPUT);
  pinMode(GREEN_LIGHT, OUTPUT);
  
  //turn on LEDs
  digitalWrite(RED_LIGHT, HIGH);
  digitalWrite(GREEN_LIGHT, HIGH);
  
  Serial.begin(9600); //configure serial communication
}
  
void loop() { 
 
switch(state)
 {
   case TRACK_SECTION_CLEAR:  //both light sensors off, power to both trains
     { 
       RedTrainSensorValue = analogRead(RED_TRAIN_SENSOR);
       GreenTrainSensorValue = analogRead(GREEN_TRAIN_SENSOR); 
       
       digitalWrite(RED_LIGHT, HIGH);
       digitalWrite(GREEN_LIGHT, HIGH);
     
     if (GreenTrainSensorValue > light_sensitivity) {
       previousMillis = millis();	//save the time
       TrainPresent = true;
       state = GREEN_TRAIN_DETECTED;
     }
  
     if (RedTrainSensorValue > light_sensitivity) {
       previousMillis = millis();	//save the time
       TrainPresent = true;
       state = RED_TRAIN_DETECTED; 
     }
 }
     break;
     
   case RED_TRAIN_DETECTED:  //Red train detected, power to both trains
     {
     GreenTrainSensorValue = analogRead(GREEN_TRAIN_SENSOR); 
 
      if (GreenTrainSensorValue > light_sensitivity) {
        state = GREEN_TRAIN_OFF;
      }
        
      else if(millis() - previousMillis > interval){ 
        TrainPresent = false;
        state = TRACK_SECTION_CLEAR;
      }
   }
     break;
     
   case GREEN_TRAIN_DETECTED: //Green train detected, power to both trains
     {
     RedTrainSensorValue = analogRead(RED_TRAIN_SENSOR);
 
      if (RedTrainSensorValue > light_sensitivity) {
        state = RED_TRAIN_OFF;
      }
        
      else if(millis() - previousMillis > interval){ 
        TrainPresent = false;
        state = TRACK_SECTION_CLEAR;
      }
  }
     break;
     
   case RED_TRAIN_OFF:  //Both trains detected (Green first), turn off Red train
     {
         if(millis() - previousMillis < interval){
        digitalWrite(RED_LIGHT, LOW); 
    }
     else {
       state = TRACK_SECTION_CLEAR;
          }
     }
     break;
     
   case GREEN_TRAIN_OFF:  //Both trains detected (Red first), turn off Green train
     {
        if(millis() - previousMillis < interval){
       digitalWrite(GREEN_LIGHT, LOW);
     }
     else {
       state = TRACK_SECTION_CLEAR;
          }
     }
     break;
 }
}

RESULTS:

Block Sensor0 - nothing happens (good).
Block Sensor 0, then Sensor1 - GreenLED turns off (good). It turns back on if Sensor0 is blocked or unblocked (good). It turns back on after 5 seconds whether Sensor1 is blocked or unblocked (good). If Sensor1 is blocked after 5 seconds, RedLED turns off (bad).
Block Sensor1 - RedLED turns off (bad). It stays off for 5 seconds regardless of Sensor1 state (strange).
Block Sensor 1, then Sensor0 - RedLED off, GreenLED on (bad). If sensors stay blocked for 5 seconds, GreenLED turns off and RedLED turns on (strange).

So it seems the Finite State Machine SORT of works, in the sense that both LEDS are responding to both sensors. But I’m completely confused about how to fix the results.

I'd suggest that the sensor_0 and sensor_1 names would be easier to use if they had red and green in them respectively - I started reading the code and gave up when I had to scroll back to the top to find out which was which. Some of the state names would benefit from similar treatment too.

UPDATE: IT WORKS!

I went back through the code checking for bugs, and I had made one tiny error.

case SENSOR_1_ACTIVE was checking sensor0, but still comparing sensor1 to light_sensitivity. By changing all instances of Sensor0 to Red Sensor and Sensor1 to Green Sensor, the error became obvious. So it now operates like this:

Block Red Sensor: nothing happens (good) Block Red Sensor, then Green Sensor: GreenLED turns off immediately. GreenLED turns back on after 5 seconds, unless Red Sensor is still blocked. (good). If both sensors stay blocked, RedLED stays on, GreenLED stays off. Block Green Sensor: nothing happens (good) Block Green Sensor, then Red Sensor: RedLED turns off immediately (good). RedLED turns back on after 5 seconds if Red Sensor is unblocked. If both sensors stay blocked, after 5 seconds GreenLED turns off and RedLED turns on (unexpected).

So there is still an uxpected issue here for me to resolve, but lots of progress too.

case SENSOR_1_ACTIVE was checking sensor0, but still comparing sensor1 to light_sensitivity. Changing that statement to sensor0 means:

Well, I didn't expect to be proved right quite so quickly ;)

Yes Will, you were completely right! And I apologise for making my code non-user-friendly. I've updated the earlier code, and will edit my other comments to replace "Sensor0" and "Sensor1" with RedSensor and GreenSensor respectively.

I'd also like to say thanks to you for your FSMs in other threads; the examples there really helped me in structuring my own code.

Hi,

This is my approach:

Assumptions:

 1 detector for train
 Maximum speed so that waiting a fixed time you make sure the crsossing is clear.
 When you start the system crossing is clear.

States:

0 System inactive
1 Red moving. Green stopped.
2 Both moving
3 Green moving. Red stopped

Events

1 System on. (Main switch. An “start” switch will do too)
2 Red passes by detector
3 Timer count finished
4 Green passes by detector

Transitions

1 Start Red.
2 Start timer
3 Start green
4 Stop red. Start Timer
5 Stop green. Start timer

I’d like to insist in the versatility of this technique.

Regards