Abstract/Summary: This page will develop client code that will turn a signal RED when a train is detected. The signal will turn AMBER when the train reaches a second detector/sensor and GREEN when the train reaches the third. The design will use the LDR_Signals_RAG class.
Possible Audience: Railway enthusiasts who wish to develop some "automated" signalling for their layouts. Some moderate programming required. Programmers interested in Finite State Machines (FSM)
Keywords: Cpp Class/Libraries, Arduino UNO, Blocking code, Finite State Machines
Required Components: Microcontroller with 12 output pins and 4 input pins. Example used an Arduino UNO although a NANO could be used. 4 LDR sensors (with 5Kohm resistors for each), 4 three- aspect signals or equivalent Red/amber/green diodes and 1kohm resistors
Required Libraries: LDR_Signals_RAG which inherits LDR_Sensor_Class
This page is concerned with monitoring 4 LDR sensors on a model train track and controlling the signal in a manner that duplicates a real railway
As shown there are 9 states. Initially there will be no trains present - the system will be in the state Waiting and all signals will be GREEN. When a train arrives it will cross over the top sensor and enter the state Coming. In the state Coming the signal will be set to RED. When the train reaches the second sensor it enters the state Arriving. The second signal is set to RED.
After the train passes the sensor Arriving and enters the state Station the first signal is switched to AMBER to warn a following train that it may enter the Approaching block of track but the next signal at Arriving will be RED
When the train passes over the Departing sensor that signal is set to RED, the signal at Arriving to AMBER and the signal at Coming is returned to GREEN.
The project uses the LDR_Signals_RAG library that includes two methods:
i . train_over_sensor( ) that may be used to detect the presence of a train, and
ii. drive_signals(red,amber,green) that may be used to control the signals.
This project is about taking the train_over_sensor( ) information and using that to drive_signals( ) as described above.
This project manipulates the train_over_sensor( ) information to drive_signals( ) as described above. The project will develop client code that will monitor 4 sensors and monitor 4 sets of signals. The lights on pins 2,3 and 4 will be controlled by the sensors on pins A0, A1 and A2. Pins 5,6 and 7 by A1,A2 and A3. etc For evaluation on the workbench the following prototyping board was used. (Note the LDRs are physically too close to the GREEN Leds so the LDRs were permanently at a low resistance (=no train). The solution was to dim the LEDs by limiting the current by replacing the 1Kohm resistors with 10Kohm resistors.
The firmware is developed using the Arduino IDE. Copies of the LDR_Sensor_Class and LDR_Signals_RAG_Class libraries are required. if these are not available the best (safest) path is to re-create them.
(i) Open a new file with the label LDR_Sensor_Class and copy/paste the *.ino file from the page LDS_Sensor_Class_code.
(ii) Using the inverted triangle in the IDE create two new tabs and give them names LDR_Sensor_Class.cpp and LDR_Sensor_Class.h
(iii) From the page LDS_Sensor_Class_code copy/paste the files LDR_Sensor_Class.cpp and LDR_Sensor_Class.h.
(iv) Verify/compile the code and correct any errors due to embedded formatting characters.
(v) In your working directory (see File-->Preferences) under the sub-folder LDR_Sensor_Class use for example Widows Explorer to create a *.zip file of LDR_Sensor_Class.cpp and LDR_Sensor_Class.h.
(vi) In the Arduino IDE (any project) use Sketch--> Add Library -->Add *.zip library and select LDR_Sensor_Class.zip.
Copies of the *cpp and *.h files will now in in the work folder under the sub folder libraries/LDR_Sensor_Class.
(vii) Repeat the above for the LDR_Signals_RAG_ class files.
In the Arduino IDE create a new file/project. Either use the page with the folded corner in the tool bar or use File-->New
I have chosen to give the file the name/label track_train to match the name of this page.
The Arduino editor will give it the extension *.ino
The starting point will be to use the code used to test the LDR_Signals_RAG Class/library. (RAG = Red - Amber - Green). That is:
With this code in the line #include "LDR_Signals_RAG_Class.h" the quotes imply that the file is in the same directory. Since a new project has been created this is not the case. What is needed is to include the library developed in the the LDR_Signals_RAG page/project. To include the library either enclose the file name with <...> or in the tools bar select sketch-->Add library --> and select LDR_Signals_RAG_Class from list of contributed libraries. See previous section if library not available.
At this point the program will simply turn on the red led when the sensor is covered and turn on the green when the covering is removed.
Even at this stage the program includes some wordy statements. ie if (s0) signal0.drive_signals(1,0,0); else signal0.drive_signals(0,0,1); To simplify some abbreviated forms will be defined. For example #define SIG0 signal0.drive_signals. The existing code now becomes:
As illustrated in the introduction this project will include a Finite State Machine (FSM) that has 9 states. {waiting..coming..approaching ..arriving..station..departing..leaving..going..vacant}. (The number of states may change as the design progresses or when testing dictates).
With a FSM the raw client code will become:
In the code the enumerator where_is_train is defined and the variable the_train is declared. In the setup( ) routine the initial state is set as waiting.
The method/function/routine train( ) is written. It takes as arguments the 4 sensor values. Ultimately the code will test the sensors and depending upon the results move from state to state. For testing all states are programmed to move to the next state immediately.
The signal conditions corresponding to each state are also included in the state machine. For example, in the state waiting all signals are GREEN. When a train arrives (state coming) the first signal turns RED. When the train is in the state station the first signal is switched to AMBER and then when the train reaches the state leaving the first signal is returned to GREEN. The second signal follows the same sequence but starts when the train is in state arriving.
In anticipation of a further project the method train( ) is of type where_is_train so returns the current state ie the_train.
Upon compiling/loading/running the code all LEDs except the last AMBER will be on. That is as expected as currently there is no SIG3(0,1,0) in the code.
To see more closely what is happening a delay may be added.
Usually for complex systems a state diagram should be draw to highlight the FSM actions. In this case the design is basically very simple. The method train( ) will be in the state waiting. On each pass the sensor s0 will be tested. If s0==1 indicating a train then the (next) state is set to coming. On the next pass in the state coming the test will be for the train to have passed. ie s0 ==0. When s0==0 the (next) state is set to approaching.
Note in the state vacant there are no sensors to test so the test program includes two 5 second delays. The implementation of these delays will be reviewed, They are blocking routines so are a NO-NO in embeded systems.
By covering the sensors in turn the program operation can be verified.
Note the design as given will only handle one train in the block of code at a time. To handle multiple trains significant changes will be required to the code. Effectively the signals are just for display only.
Currently the code includes two 5 second delays. These will block the program from undertaking any other actions for a total of 10 seconds. This might be OK if all the micro-controller is doing is controlling these four signals. In practice there might be other actions. Consider a loop( ) function of the form:
void loop( ) { handle_the_train_signals(); some_other_important function( );}With the delays everytime a train passes through the block of track the code will be stalled/blocked for 10 seconds. Normally this cannot be tolerated. The analogy is someone glued to their watch. What needs to happen is to look at the watch, do something else, comeback and look at the watch and then when the time is up take the appropriate actions. In this example the millis( ) function is the watch.
One further comment is that ideally with embedded systems each operation should take the same time. In general the method train( ) will take only microseconds to execute. To take 10 seconds in some circumstances is not acceptable.
The new non-blocking code becomes:
Notes:
1. A new state vacant2 is defined. This splits the operations previously in vacant into two: (i) SIG3 to red and SIG2 to amber then (ii) SIG3 to amber and Sig2 to green.
2. Two delay times are defined - these effectively define the time of state vacant (SIG3 RED after train has passed sensor) and state vacant2 (SIG3 AMBER before it goes GREEN)
3. The variable start_time of type static long is declared. It is long to match the type of the function millis( ) and static so that its value is retained each time the train loop is executed.
4. The state vacant now tests if the elapsed time since the train passed the s0 sensor is greater than 5 seconds (RED_LIGHT_ON). It then sets the nest state to the new state vacant2.
5. The new state vacant2 tests if the elapsed time since the train left state vacant is greater than 5 seconds (AMBER_LIGHT_ON). It then sets the next state to waiting.
Real embedded systems must take into account "what if" considerations. In this example one "what if" might be a spectator throwing a shadow causing s0 to assume a train has passed. The FSM will be stuck in the state approaching waiting for the train to reach sensor s1. This will never occur. In this example a timer has been included in the state coming. If ever the FSM is in that state for longer than 20 seconds the timer will time-out and the FSM will return to the state waiting.
This project has developed a method train( ) that monitors 4 sensors and controls 4 sets of signals to move through a RED-AMBER-GREEN sequence.
The method is only for one train at a time.
Other signal sequences are possible. The following sequence starts in the state waiting with the signals set to green, amber, red, and red. When the train reaches the coming state the green signal becomes red. After leaving coming and entering the state approaching the amber signal turns green in anticipation of a train. This green turns red when the train reaches the arriving state. Further the following red signal at departing is turned amber.
The sequence repeats with eventually all signals in the red state. At that point in the state vacant the first signal is set to amber. The sequence then returns to the waiting state after a timed delay.
(Note this sequence does assume that the distance between two sensors is greater than the longest train)
Both sequences will be interesting to spectators with signals flashing on and off as the train progresses. The second sequence has the "legal" advantage in that the very first signal at coming does not return to green until the train has completely vacated the block of track.
Other sequences are possible. It might be interesting to force the train to stop at the station by keeping the departing signal red for a time. (An addition state might be necessary)