Header file: Single_Track_Class.h
Constructor Single_Track_Class(int,int,int,int)
sensor,red,amber,green pins
Public Methods begin(pol)
pol =1 for positive signals.
pol = 0 for negative signals
int train_over_sensor( );
Returns 1` if train over sensor
void signal_control(int dsp);
dsp = 1 sets signal to RED
dsp = 0 signals return GREEN in a RED-AMBER-GREEN sequence.
int my_track(int my_sensor, int your_sensor);
Monitors sensors at both ends of track and returns 1 when train exits.
void your_track( );
Controls signals for trains from other direction,,
Summary/Abstract: This project will control the signals at both ends of a single railway track. When a train arrives the signals at both ends will turn RED. When the train departs the other end both sets of signals will pass through a RED-AMBER-GREEN sequence. The project will develop a limited Cpp Class.
Possible Audience: Model railway enthusiasts who are interested in using a micro-controller to operate the signals at both ends of a single track and programmers interested in Cpp classes. Some basic knowledge of programming will be assumed.
Keywords: Arduino NANO, Cpp Classes/Libraries, Cpp Inheritance, Non-blocking functions, Scope Resolution Operator
Components: The project has been prototyped with Arduino NANO, 2 LDR sensors, 2 each of RED, AMBER, GREEN LEDs. The project has also been tested using IR sensors.
Required Libraries: LDR_Signals3_Class, LDR_Sensor_Class. Signals3_Class.
This HTML page/project is about placing signals at both ends of a single track that trains can enter from both directions. When a train is detected at one end the signals at the other end are set RED. When the train enters the block of track (last wagon passed sensor) those signals are set to RED. Both signals remain RED until the train leaves the block of single track when they return to GREEN via a RED-AMBER-GREEN sequence.
The project will inherit the class/library LDR_Signals3_Class that in turn inherits the classes LDR_Sensor_Class and Signals3_Class. This has the advantage that the final code is fairly simple with all the detail hidden in the libraries. It has the disadvantage that this project includes any assumptions made by those libraries. The LDR_Sensor_Class includes a 1 second filter/delay to compensate for loss of signal when couplings pass over the sensor. The Signals3_Class will remain RED for 5 seconds, then go to AMBER for 5 seconds before returning to GREEN. All the source code is provided in the appropriate pages so if desired users can modify all the timings.
Photo shows train layout where two single tracks go through a mountain and across a viaduct. Since the operators at the ends of the track cannot see each other there is the potential for a collision with expensive model trains crashing into the ravine below.
The basic circuit is shown in the following diagram.
The project will be developed using the Arduino IDE.
1 If libraries LDR_Sensor_Class, Signals3_Class and LDR_Signals3_Class are not in list of contributed libraries:
Its messy but probably the best solution go to each of the projects in turn and create the libraries
(i) In the Arduino IDE create a new file LDR_Sensor_Class and copy/paste the *.ino file from Programs section.
(ii) In the IDE use the inverted triangle towards the top right of the window to create two new tabs/files: Give them the labels LDR_Sensor_Class.cpp and LDR_Sensor_Class.h. Note they must have the same label but extensions *.cpp and *.h
(iii) From Programs section copy/paste LDR_Sensor_Class.cpp and LDR_Sensor_Class.h.
(iv) Compile/verify code to be sure there are no embedded formatting characters in the copy/paste.
(v) Use a program such as Windows Explorer to create a *.zip file of the LDR_Sensor_Class.cpp and LDR_Sensor_Class.h files.
(vi) In Arduino IDE use sketch -->Add Library --> Add .zip library and select file to add to your contributed libraries.
(vii) Repeat for other files:
2. Create a new project:
(i) In the IDE create a new file - give it the label Single_Track_Class. The IDE will give the file the extension *.ino
(ii) In the IDE use the inverted triangle towards the top right of the window to create two new tabs/files: Give them the labels Single_Track_Class.cpp and Single_Track_Class.h. Note they must have the same label but extensions *.cpp and *.h
3. Include LDR_ Signals3_Class header files in the new project.
In LDR_Signals3_Class.h use sketch -->Add Library -->and select LDR_Signals3_Class from your contributed libraries.
LDR_Signals3_Class inherited LDR_Sensor_Class, Signals3_Class so these needed to be available
4. Add the following wrapping to the header file to ensure the header is only ever included once.
In the header file define the class Single_Track_Class. Since this class inherits the LDR_Signals3_Class this class is included in the definition:
When an instance/object of the class Single_Track_Class is created it will attach to 4 pins that may be defined by the user. These pins become the arguments for the constructor Single_Track_Class( ...) defined in the public area.
As illustrated in the class definition, Single_Track_Class inherits all the methods of the class/library LDR_Signals3_Class. That class was concerned with reading an LDR sensor and controlling 3 aspect signals (RED,AMBER,GREEN). As illustrated LDR_Signals3_Class also inherits two classes LDR_Sensor_Class and Signals3_Class. All of this will be transparent to the end user/client.
The first step is to implement the constructor.. The format is shown below.
//Implementation file Single_Track_Class.cpp
#include "Single_Track_Class.h"As shown the Single_Track_Class constructor is defined as part of the class of the same name. The linkage is highlighted with the scope resolution operator ::, The Single_Track_Class inherits the properties of the LDR_Signals3_Class prefaced with the colon :. In this example the Single_Track_Class definition defines 4 arguments and in this example all of these are passed to the inherited class LDR_Signals3_Class.
At this point the client code must create two instances labled lights1 and lights2 of the class Single_Track_Class. As per the circuit given earlier one instance is attached to the pins A0 (sensor), 2 (red), 3 (amber) and 4 (green) while the second is attached to pins A1, 5, 6 amd 7.
This design may be implemented using either positive or negative signals. The difference is illustrated in the circuit to the right. The top three LEDs need to be pulled low to activate - this is known as negative logic while the lower three diodes are positive logic and are pulled high.
Which type is used is specified as a parameter with the begin( ) method.
In the example of this HTML page / project positive logic is used - this is specified as "1" using the #define directive. (included in code of previous section).
At this point the Single_Track_Class::begin(int polarity) method simply invokes the LDR_Signals3_Class::begin(polarity) of the same name. Note use of scope resolution operator to distinguish which "begin" is being referred to.
The begin will need to be defined as part of the header file:
//Header file: Single_Track_Class.h .....class Single_Track_Class : public LDR_Signals3_Class { public : Single_Track_Class(int sensor, int red, int amber, int green); void begin(int polarity); //polarity of signals};Compiling and running the program at this point will turn all the LEDs ON.
All the client has done is called Single_Track_Class::begin( ).
The client knows nothing of what is underneath. Single_Track_Class::begin( ) calls LDR_Signals3_Class::begin( ) that in turn calls Signals3_Class::begin( ) that turns all the leds on.
Two methods available from LDR_Signals3_Class (train_over_sensor( ) and signal_control(dsp)) will be useful so are included as part of Single_Track_Class. Since these "new" methods will perform exactly the same operatioin they are given the same name/label. This willl require in the constructor the scope resolution operator :: be used to indicate which version the code refers to.
The header file becomes:
class Single_Track_Class : public LDR_Signals3_Class { public : Single_Track_Class(int sensor, int red, int amber, int green); void begin(int polarity); //polarity of signals int train_over_sensor( ); void signal_control(int dsp);and the constructor has the two methods added:
//Implementation file Single_Track_Class.cppThe methods can be tested by including the above code in the main program loop( ):
void loop() { lights1.signal_control(lights1.train_over_sensor()); lights2.signal_control(lights2.train_over_sensor()); }As given a signal will turn RED when its LDR is covered and go through a RED-AMBER-GREEN sequence when the covering is removed.
The following shows the proposed design in the form of a state sequence diagram:
In the above example if a train hits sensor t1 first the top sequence is taken. However if the other train hits sensor t2 first the lower path is taken. Since the top and bottom paths are the same they can be implemented within the class implementation file. As a starter this might involve two methods:
1. my_track( ) that will be the sequence the signals go through to allow train to proceed along the single track. Train t1 in top path and train t2 in lower.
As shown what happens with my_track( ) depends upon the two sensors t1 and t2. Hence these should be passed as arguments.
my_track(my_sensor,your_sensor) where my_sensor will be t1 for the top train and t2 for the lower. your_sensor will be t2 for the top and t1 for the lower.
2. your_track( ) will be the sequence the signals go through if the other train is proceeding along the single track. Train t2 in top path and train t1 in lower.
The wait4train state will be implemented by the client.
my_track( ) and your_track( ) are defined in the header file:
Note there are 4 states in the upper and lower paths. These have been added as an enumerator to the private region.
The two methods should be added to the implementation file. At this point my_track( ) will just return 0. However your_track( ) will set its signals to RED.
As shown in the previous state diagram the client must implement 3 states:
1. vacant - no trains detected
2. train1 - one train detected - will need to call my_train( ) for that train and your_train( ) for the other.
3. train2 - other train detected - will need to call my_train( ) for that train and your_train( ) for the first train.
This may be implemented in the client code:
Notes: (i) the state is initialised in the set_up( ) method.
(ii) sen1 and sen2 are defined to de-clutter the code.
(iii) Before a train is detected signal_control(0) sets the signals to GREEN
(iv) when a train is detected the appropriate my_track( ) and your_track( ) will be called. The signals will be controlled by these two methods.
(v) for lights1 the arguments are sen1 and sen2. For lights2 they are sen2 and sen1.
Rather than test on a real layout a prototyping board with provision for 4 sensors and 4 signal sets was used. However/mistake the LDRs were placed too close to the GREEN LEDs so were never in shadow. Replacing the series 1K ohm resistors with 10K reduced the light intensity of the leds and made the board use able.
An enumerated type enum track_status with 4 states: waiting, wait2pass, wait2arrive, wait2dept has been defined in the header file. The code for my_track( ) must follow these 4 states as the relevant criteria are met.
1. Initially my_track( ) will be in the state waiting. That is train_location = waiting.
2. Once a train is detected the method my_track( ) is invoked it will move to the state wait2pass where the design is waiting for the complete train to pass cross the sensor. (this will be the entry sensor)
3. Once the train passes the entry sensor it is in the state wait2arrive where it is waiting to arrive at the exit sensor (your_sensor)
4. Once the train reaches the exit sensor it is waiting for the complete train to pass over the sensor. (state ==wait2dept)
5. Once the train completely passes, that is the operation is complete it returns to the state waiting. This time the method returns "1" to the client. The client then goes to the state vacant and when the next train arrives the cycle repeats.
5. Once the train completely passes, that is the operation is complete it returns to the state waiting. This time the method returns "1" to the client. The client then goes to the state vacant and when the next train arrives the cycle repeats.
Note that the use of the two state machines makes the code non-blocking. All the library methods are also non-blocking.
In this example the Arduino background code calls the loop( ) method.
In the loop( ) method the code tests the state train_status and switches to the relevant code.
It will execute the methods my_track( ) and your_track( ).
Neither of these methods contain a while { } statement or a delay ( ) function.
Once my_track( ) and your_track( ) have executed their tasks the routines will exit.
If the main program loop( ) contains additional code that will be executed.
Once the loop( ) completes it will return to the Arduino background code and the cycle will repeat.
This HTML page demonstrated creating a library/class for a single track controller. While the library does hide much of the implementation details there is still significant client code required.
This project will provide the basis for two future projects: a dual single track controller and a "single track" with four entry/exit points.
With the track layout still in the "experimental" stage as shown the signals were LEDs on a PCB. Photo shows LDR on track wired to screw terminals on PCB. Well camouflaged is the 4.7 K pull up resistor. The orange (3.3V), black (ground) and blue (LDR output) are wired back to the micro-controller. The red, yellow and green wires come from the micro-controller to control the LEDs. They then go via a 1K ohm resistor to the black (ground) wire.