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.
/*---------------------------------------------------------------------------
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;
}
}