Crossing Signals - Part 4 of 4 - The Arduino Code

If you have not experience with the coding of the programming language for Arduino microcomputers this is not the blog entry where you will learn about it. However, if you have been following along and would like to see the code I developed, please feel free to copy the following Arduino sketch and upload it to your own Arduino. However, as your model railroad will almost certainly have a different track configuration you will need to modify the coding as necessary to suit your particular situation. I would be pleased to help anyone who has a question. You can email me at rupertsinsights@gmail.com.

Here is my code:

/*---------------------------------------------------------------------------
  This Arduino sketch and the circuit diagram in Part 3 of 4 is used to control model railroad crossing
  lights that warn vehicular traffic about the danger of an approaching train. This design does not include
  the use of sound as listening to crossing sounds quickly becomes tiresome. However, sound
  could easily be added with the addition of an Arduino sound shield and a small addition to
  the coding. The particular circumstance on the model railroad for which this was designed
  has the highway crossing two tracks, the main line and a branch line, which means there
  are two level crossings in use. Also, at the east side of the level crossing there
  are four possible tracks from which a train can approach the level crossing (the main
  line from the east, a siding that joins the main line immediately to the east of the crossing,
  the branch line from the east and a spur that joins the branch line immediately to the east
  of the crossing).

  The presence of train cars is detected with the use of light sensitive photoresistors. A photoresistor
  is a device allows electrical current to pass through with very little resistance when it
  is exposed to light. When the light source is removed (by the light being extinguished or the
  photoresistor shaded by the presence of an object such as a model train) the photoresistor's
  resistance increases. The amount of resistance is continuously variable (i.e., is analog and not
  digital). This is the same principal used by many devices in our daily lives (smart phones and smart
  TV's use photoresistors to detect how much light is present in order to vary the brightness of the
  screen, street lights use photoresistors to control when they turn on, etc.). In this instance
  an Arduino microcontroller is used to sense the increased resistance caused by the shading of
  photoresistors which, in turn, causes the Arduino to flash pairs of red LEDs which are mounted
  in the crossing signals facing both sides of the highway.
  
  Photoresistors can be wired in series. If two are wired together in series, shading either one
  or both photoresistors will cause the current passing through them to drop.

  In this design, photoresistors are mounted in the centre of the model railway tracks, between
  the ties and with the tops of the photoresistors flush with tops of the ties. There are six
  photoresistors which have been designed as the "OUT" locations - those locations described in the
  first paragraph above that a train will cover is it approaches the crossing from a distance. In
  addition, photoresistors are mounted immediately adjacent to the level crossing roadway on both
  sides of the roadway, again in the centre of the track with the tops of the photoresistors flush
  with the tops of the ties (the "CROSSING" photoresistors).
  
  The shading of any one of the "OUT" photoresistors tells the Arduino that a train is approaching
  the crossing and the Arduino starts flashing the crossing lights. The shading of any one of the
  "CROSSING" photoresistors tells the Arduino that the train has now reached the crossing so the
  Arduino keeps flashing the crossing lights. Once the last car has passed the last "CROSSING"
  photoresistor the lights stop flashing after a predermined amount of time that can be adjusted
  (it is not desirable for the lights to stop flashing while a train is still across the crossing
  but it equally undesirable if the lights continue flashing long after a train has passed).
  
  This program self-calibrates the photosensors based on the amount of light falling on the
  photosensors at the momment the Arduino is first turned on. This means that all photosensors must
  not be covered when the Arduino is first turned on. Also, it helps if the ambient light in the room
  remains fairly constant during the time that the Arduino is operated. If the ambient light reduces 
  the self-calibration will take this into account but is not able to cope with large increases in 
  ambient light. If there is a large increase in ambient light then it is simply matter of removing
  power from the circuit momentarily and then allowing it to reboot.
  
  It would have been possible to wire all six OUT photoresistors in series. However, as they are
  located several feet away from each other it is very likely that the amount of ambient light falling
  on each one is quite different. This would mean that the self-calibration would likely not be as
  sensitive as one would like. For this reason, photoresistors that are located in close proximity are
  wired in series and the Arduino self-calibrates each pair together. One pair is designated OUTWest, another is
  OUTEast1, a third is OUTEast 2 a fourth is CROSSING1 and the last is CROSSING2.
 
For ProMini or UNO September 24, 2021
  -------------------------------------------------------------------------------*/
//Assign the various sensor pairs to Arduino pins:
const int sensorOUTWest = A0;
const int sensorOUTEast1 = A1;
const int sensorOUTEast2 = A2;
const int sensorCROSSING1 = A3;
const int sensorCROSSING2 = A4;

//Designate a counter for how long sensorOUT is covered
//and a limit after which the lights will stop flashing:
int countOUT = 0;
int countOUTLimit = 8;

//Designate a counter that starts at the value of
//countDownStart which determines how long crossing lights
//flash after a train has passed the level crossing.
//The higher the value of countDownStart, the longer
//the crossing lights will flash after the train has passed.
int countCROSSING = 0;
int countDownStart = 1;

//Counter to allow a train to pass over a second OUT sensor
//after it has passed over the level crossing to avoid
//triggering a new flashing light sequence.
int countFALSE = 0;
                                                       
//Following variables adjust how much time passes for
//the Arduino to reset once a train has passed over
//a second OUT sensor (the higher these values the
//longer it takes to reset)
int countFALSELimit = 15;
int delaySensitivity = 200;

//Following values are assigned based on whether a
//photosensor is shaded enough to trigger a crossing
//light sequence (in effect converts the analog
//photoresistor into a digital device, simply to
//make the program logic simpler.
int coveredOUT = 0;
int coveredCROSSING = 0;
int currentSensorState = 0;
int lastSensorState =0;

//The value of inTransit depends on where a train is
//located in its progression towards, over and beyond
//the level crossing. The value of inTransit is what
//causes the crossing lights to either flash or not.
int inTransit = 0;

//The amount by which the light falling on eash sensor
//must drop to start detection logic.
int lightThreshold = 75;                            

//measures amount of light falling on sensor OUT:
int valOUTWest; 
  
//Starting point of variable valOUT (will increase to
//highest reading of val:                                  
int valOUTWestHigh = 0;

//Starting point of variable valOUT (will decrease to
//lowest reading of val):
int valOUTWestLow = 1023;

//A variable that will be equal to the value of
//valOUTWestHigh MINUS the value of lightThreshold:
int valOUTWestTest;                                 

//The following are all similar variables as the above
//for the various other photoresistor pairs
int valOUTEast1;
int valOUTEast1High = 0;
int valOUTEast1Low = 1023;
int valOUTEast1Test;

int valOUTEast2;
int valOUTEast2High = 0;
int valOUTEast2Low = 1023;
int valOUTEast2Test;

int valCROSSING1;
int valCROSSING1High = 0;
int valCROSSING1Low = 1023;
int valCROSSING1Test;

int valCROSSING2;
int valCROSSING2High = 0;
int valCROSSING2Low = 1023;
int valCROSSING2Test;

int LED1 = 2;
int LED2 = 3;

void setup()
{
  pinMode(sensorOUTWest, INPUT);
  pinMode(sensorOUTEast1, INPUT);
  pinMode(sensorOUTEast2, INPUT);
  pinMode(sensorCROSSING1, INPUT);
  pinMode(sensorCROSSING2, INPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
  checkAllOUTSensors();
  checkAllCROSSINGSensors();

//Outer sensor detects train; train has not reached crossing
  if (coveredOUT == 1 && coveredCROSSING == 0 && inTransit == 0)        
  {
//Add to value of counter as long as outer sensor detects train  
    countOUT = countOUT + 1;  
    
//Train is deemed to be in transit between outer and inner sensors
//and inTransit is assigned value of 1                                          
    inTransit = 1;                                                
  }

//Outer sensor continues to detect train; train has not reached
//crossing; train deemed in transit
  if (coveredOUT == 1 && coveredCROSSING == 0 && inTransit == 1)        
  {
//Counter value stops increasing because train is either continuing
//its travel toward crossing or it has stopped on the OUT sensor    
    countOUT = countOUT;                                                 
  }

//Outer sensor no longer detects train; train has not reached
//crossing; trained deemed in transit
  if (coveredOUT == 0 && coveredCROSSING == 0 && inTransit == 1)
  {
//Add to value of counter as long as train is in transit and is not
//detected by outer or crossing sensors    
    countOUT = countOUT + 1;                                            
  }

//Train doesn't reach crossing in time (as for a train that stops
//on a sensor and then reverses direction)
  if (countOUT > countOUTLimit && countCROSSING == 0)                    
  {
    countOUT = 0;
    inTransit = 0;
  }

//Train reaches crossing
  if (coveredCROSSING == 1 && inTransit == 1)                             
  {
//Value of countDownStart is assigned to countCROSSING    
    countCROSSING = countDownStart; 
    
//inTransit is assigned a value of 2                                      
    inTransit = 2; 
     
//countOUT is reset to 0 because countOUT is no longer
//needed for this train passage                                                      
    countOUT = 0;                                                         
  }


  if (coveredCROSSING == 0 && inTransit == 2 && countCROSSING > 0)
  {
    countCROSSING = countCROSSING - 1;
  }


  if (countCROSSING == 0 && inTransit == 2)
  {
    inTransit = 3;
  }

  if(inTransit == 3 && coveredOUT == 1)
  {
    countFALSE = countFALSELimit;
    inTransit = 4;
  }

  if(inTransit == 4 && coveredOUT == 0 && countFALSE > 0)
  {
    countFALSE = countFALSE - 1;
    delay(delaySensitivity);
  }

  if(inTransit == 4 && coveredOUT == 1 && countFALSE > 0)
  {
    countFALSE = countFALSE;
  }

  if(inTransit == 4 && coveredOUT == 0 && countFALSE == 0)
  {
    inTransit = 0;
  }
  
  if (inTransit == 1 || inTransit == 2)
  {
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, HIGH);
    delay(500);
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    delay(500);
  }

  else if (inTransit == 0 || inTransit == 3 || inTransit == 4)
  {
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
  }

  {
    //    Serial.print(" valOUTWest= ");
    //    Serial.print(valOUTWest);
    //    Serial.print(" valOUTWestHigh= ");
    //    Serial.print(valOUTWestHigh);
    //    Serial.print(" valOUTWestLow= ");
    //    Serial.print(valOUTWestLow);
    //    Serial.print(" valOUTWestTest= ");
    //    Serial.print(valOUTWestTest);
    //    Serial.print(" valOUTEast1= ");
    //    Serial.print(valOUTEast1);
    //    Serial.print(" valOUTEast1High= ");
    //    Serial.print(valOUTEast1High);
    //    Serial.print(" valOUTEast1Low= ");
    //    Serial.print(valOUTEast1Low);
    //    Serial.print(" valOUTEast1Test= ");
    //    Serial.print(valOUTEast1Test);
    //    Serial.print(" valOUTEast2= ");
    //    Serial.print(valOUTEast2);
    //    Serial.print(" valOUTEast2High= ");
    //    Serial.print(valOUTEast2High);
    //    Serial.print(" valOUTEast2Low= ");
    //    Serial.print(valOUTEast2Low);
    //    Serial.print(" valOUTEast2Test= ");
    //    Serial.print(valOUTEast2Test);
    //    Serial.print(" coveredOUTEast= ");                              
    //    Serial.print(coveredOUTEast);
    //    Serial.print(" valCROSSING1= ");
    //    Serial.print(valCROSSING1);
    //    Serial.print(" valCROSSING1High= ");
    //    Serial.print(valCROSSING1High);
    //    Serial.print(" valCROSSING1Low= ");
    //    Serial.print(valCROSSING1Low);
    //    Serial.print(" valCROSSING1Test= ");
    //    Serial.print(valCROSSING1Test);
    //    Serial.print(" valCROSSING2= ");
    //    Serial.print(valCROSSING2);
    //    Serial.print(" valCROSSING2High= ");
    //    Serial.print(valCROSSING2High);
    //    Serial.print(" valCROSSING2Low= ");
    //    Serial.print(valCROSSING2Low);
    //    Serial.print(" valCROSSING2Test= ");
    //    Serial.print(valCROSSING2Test);
    //    Serial.print(" coveredOUT= ");                            
    //    Serial.print(coveredOUT);
    //    Serial.print(" coveredCROSSING= ");                      
    //    Serial.print(coveredCROSSING);
    //    Serial.print(" countOUT= ");
    //    Serial.print(countOUT );
    //    Serial.print(" countCROSSING= ");
    //    Serial.print(countCROSSING );
    //    Serial.print(" countFALSE= ");
    //    Serial.print(countFALSE );
    //    Serial.print(" inTransit= ");
    //    Serial.print(inTransit );
    //    Serial.println();
    //    delay(100);
  }
}

void checkAllOUTSensors()
{
//Following routine reads valOUT from photosensor, sets valOUTHigh and valOUTLow 
//and subtracts the value of lightThreshold from the High measurement to detect the presence of a train
  valOUTWest = analogRead(sensorOUTWest);            
                                                     
  if (valOUTWest > valOUTWestHigh)
  {
    valOUTWestHigh = valOUTWest;
  }                                                 
  if (valOUTWest < valOUTWestLow)
  {
    valOUTWestLow = valOUTWest;
  }                                                   
  valOUTWestTest = valOUTWestHigh - lightThreshold;


//Following routine reads valOUT from photosensor, sets valOUTHigh and valOUTLow
//and subtracts the value of lightThreshold from the High measurement to detect the presence of a train
  valOUTEast1 = analogRead(sensorOUTEast1);          

  if (valOUTEast1 > valOUTEast1High)
  {
    valOUTEast1High = valOUTEast1;
  }                                                 
  if (valOUTEast1 < valOUTEast1Low)
  {
    valOUTEast1Low = valOUTEast1;
  }                                                   
  valOUTEast1Test = valOUTEast1High - lightThreshold;

  
//Following routine reads valOUT from photosensor, sets valOUTHigh and valOUTLow
//and subtracts the value of lightThreshold from the High measurement to detect the presence of a train
  valOUTEast2 = analogRead(sensorOUTEast2);            
                                                     
  if (valOUTEast2 > valOUTEast2High)
  {
    valOUTEast2High = valOUTEast2;
  }                                                 
  if (valOUTEast2 < valOUTEast2Low)
  {
    valOUTEast2Low = valOUTEast2;
  }                                                   
  valOUTEast2Test = valOUTEast2High - lightThreshold;
                                             

//If valOUT is less than valOUTTest then sensor OUT is not covered
//so sensor is given a value of 1
  if (valOUTWest < valOUTWestTest || valOUTEast1 < valOUTEast1Test || valOUTEast2 < valOUTEast2Test)       
  {
    coveredOUT = 1;                                    
  }
//If valOUT is greater than valOUTTest then sensor OUT is covered
  else if (valOUTWest >= valOUTWestTest || valOUTEast1 >= valOUTEast1Test || valOUTEast2 < valOUTEast2Test)                        
  {
    coveredOUT = 0;
  }

}

void checkAllCROSSINGSensors()
{
//Following routine reads valOUT from photosensor, sets valOUTHigh and valOUTLow
//and subtracts the value of lightThreshold from the High measurement to detect the presence of a train 
  valCROSSING1 = analogRead(sensorCROSSING1);          
  if (valCROSSING1 > valCROSSING1High)
  {
    valCROSSING1High = valCROSSING1;
  }                                                 
  if (valCROSSING1 < valCROSSING1Low)
  {
    valCROSSING1Low = valCROSSING1;
  }                                                   
  valCROSSING1Test = valCROSSING1High - lightThreshold;


//Following routine reads valOUT from photosensor, sets valOUTHigh and valOUTLow
//and subtracts the value of lightThreshold from the High measurement to detect the presence of a train
  valCROSSING2 = analogRead(sensorCROSSING2);          
  if (valCROSSING2 > valCROSSING2High)
  {
    valCROSSING2High = valCROSSING2;
  }                                                 
  if (valCROSSING2 < valCROSSING2Low)
  {
    valCROSSING2Low = valCROSSING2;
  }                                                   
  valCROSSING2Test = valCROSSING2High - lightThreshold;
                                       

//If valCROSSING is less than valCROSSINGTest then sensor CROSSING is not covered
//so sensor is given a value of 1
  if (valCROSSING1 < valCROSSING1Test || valCROSSING2 < valCROSSING2Test)                             
  {
    coveredCROSSING = 1;                                    
  }
//If valCROSSING is greater than valCROSSINGTest then sensor CROSSING is covered 
  else if (valCROSSING1 >= valCROSSING1Test || valCROSSING2 >= valCROSSING2Test)                       
  {
    coveredCROSSING = 0;
  }
}