Header File <Signals2_Class.h>
Constructor Signals2_Class(int red_led) Assumes green_led = NOT red_led
Signals2_Class(int red_led, int green_led) Pins wired to red and green leds.
Public Methods begin(int pol) Positive signals pol =1
Negative signals pol = 0
signal_control(int dsp) Argument dsp controls RED signal
signal_control(int dsp, int wait) Signals return to GREEN after time wait
Abstract/Summary: This HTML page/project develops a class/library Signals2_Class that controls a two aspect railway signal. The signal may be either active high or active low. The class constructor is overloaded to permit one or two arguments. The class generates a public method signal_control that controls signals. This is overloaded to allow a delay before the signals return to green.
Possible Audience: Model railway hobbyists interested in electronically controlling signals and programmers interested in simple examples of classes and overloading in the C++ environment.Also software design of state machines.
Keywords: Two aspect signals, C++ Class, C++ overloading, state machine, Arduino UNO NANO
Required Components: Micro-controller (Project tested with Arduino NANO) plus LEDs and LDR for on bench testing.
Required Libraries: either LCD_Sensor_Class or IR_Sensor_Class
With model railways two common places where signals are used are:
1. At points/switches/turnouts to indicate if the train can proceed. See photo to left of full size train signals. Trains can proceed up the main track. Trains cannot turn left.
2. Along blocks of track to indicate if the block is occupied. In a simple model railway the signal will go RED immediately a train enters a block telling following trains not to enter. It will not return to GREEN until sometime after the train has passed. In a full size railway there will be a sensor further up the track that is used to set the signal back to GREEN
This HTML page/project develops a C++ class for a RED-GREEN ( 2 aspect) signal. The constructor has two options:
1. One argument that specifies where the RED signal (LED) is wired. This design assumes that if there is a GREEN signal it is externally generated from the RED signal using a NOT gate. In illustration pins 3 and 5.
2. Two arguments that specify where the RED and GREEN signals are wired. Effectively the NOT gate of option 1 is now generated in software. In illustration pins 0/1 and 6/7
Since model railway signals maybe activated by pulling them high or pulling them low this option will be determined by the parameter in the begin( ) method.
There will also be an option for adding a delay before the signal returns to GREEN.
The program will be developed using the Arduino IDE.
In the IDE create 3 files:
1. file-->new--> Signals2_Class. The Arduino will give this file the extension "*.ino"
2. In the editor use inverted triangle near the top right and select new_tab and give it the label Signals2_Class.cpp. Note the extension.
3. Repeat use inverted triangle select new_tab and give it the label Signals2_Class.h. Note the extension.
The *.cpp and *.h files must have the same label.
I have chosen to give the *.ino file along with this HTML page the same name to assist with the documentation.
All three files will be compiled/verified together.
The next step is to populate the three files.
As a starter the skeleton header file is generated as shown below.
The header file has the following features:
1. The #ifndef Signals2_Class_h ......#endif ensures that the header file is only included once in a project.
2. #define Signals2_Class_h If Signals2_Class_h was not previously defined it is now defined and the included code is implemented.
3. #include "Arduino.h" This includes the Arduino header file. It is implicitly included in *.ino files but not library routines. It will be needed for pin definitions.
4. class Signals2_Class { defines a class Signals2_Class. This class will have two regions
public : any method listed in the public area can be invoked by external code. eg Signals2_Class.ino can call the begin( ); method
private: all variables in the private area can only be accessed by the Signals2_Class.cpp code only : All other files including Signals2_Class.ino cannot
The class has two constructors both with the same name/label but a different number of parameters/arguments. At compile/verify time the compiler will pick the correct one to use. This is known as overloading and illustrates C++ polymorphism. That is Signals2_Class can have more than one form.
In this example the constructor with one argument is for a design that uses an external NOT gate to generate the green signal. (Pins 3 or 5) in the previous diagram. With two arguments the NOT logic is generated by the micro-controller.
For more information on C++ classes/libraries see either the LDR_Sensor_Class or IR_Sensor_Class pages. For more information on overloading see LDR_Overload_Class.
The first pass at the implementation or *.cpp code is shown below:
The features of the *.cpp file at this point are:
1. #include "Signals2_Class.h" that includes the header file of the previous section.
2. Two constructors of the form: Signals2_Class:: Signals2_Class ( ) { };
3. The begin ( ) method. void Signals2_Class::begin( ){} The :: is the scope resolution operator that says begin( ) is associated with the class Signals2_Class.
In the constructors two private variables, _red_led and _green_led declared in the header file are assigned to the constructor arguments. By convention these start with an underscore. Being private they may only be accessed from inside the library.
The other private variable _external_gate is a flag used later in the program to flag if there is an external NOT gate. Assuming there is _external_gate is set in the header file. Since there is no external gate with the two argument version _external_gate is cleared as part of the constructor implementation.
The first pass at the application code is shown below. This is the code that will test the class/library Signals2_Class
The program creates two instances/objects of the Signals2_Class. signal1 has one argument - the RED signal is attached to pin4 whereas signal2 has two arguments where the RED signal is attached to pin 6 and the GREEN to pin 7. (See header file for what is interpreted as red and what as green).
At this point the program should verify/compile
To drive the signals the micro-controller pins will need to be set as outputs. This will be part of the begin( ) method. The begin will be invoked as part of the setup() routine.
There will be a begin( ) method associated with the object signals1 and a begin method associated with the object/instance signals2.
While there are two constructors there will only ever be one "begin( )" method. The "begin( )" method must handle both forms of constructor.
void Signals2_Class::begin( ){ pinMode(_red_led,OUTPUT); digitalWrite(_red_led,HIGH); //turn on for testing if (!_external_gate) { pinMode(_green_led,OUTPUT); digitalWrite(_green_led,HIGH); //turn on for testing }};
In the above code the first two lines of the method will initialise the _red_led pin as an OUTPUT and set it HIGH. With just these two lines after compiling and loading, assuming positive polarity signals the two RED lights on pins 4 and 6 in the previous design will be ON.
The last two lines are conditional and only apply for the instance where the constructor has two arguments. As well as the two REDs the GREEN light on pin 7 will be ON.
The begin( ) as given assumed positive polarity signals. If you are testing with negative polarity use digitalWrite(_red_led,LOW);.See next section.
The next section will handle both polarities as part of the code.
To allow for both positive and negative signals rather than add an additional argument to the constructor the begin( ) method will be overloaded. An argument/parameter will be added to define the signal polarity. The new header code will become:
...class Signals2_Class { public: Signals2_Class(int red_led); Signals2_Class(int red_led, int green_led); void begin( ); void begin(int pol); private: int _red_led, _green_led; int _external_gate = 1; //assume NOT gate used int _pol = 1; //positive signals . ....Note a new private variable _pol is declared and initialised to 1 for positive logic. The new begin(int pol) method will over-write _pol. See additional implementation code below:
The design should be tested using various combinations of the begin method. For negative signals the lights will be turn on with a LOW signal.
To test the project the following code will be added to the program loop. Since signal1 was declared with one argument the test circuit should flash the red led for 250mS every second. Since signal2 had two arguments the red should flash as per signal1 but the associated green should be "on" in the 750mS the red was "off".
The signal_control(int _dsp) will be a little messy. It must handle 8 combinations:
1. Negative and positive signals. These are defined by the private variable "_pol" that was set by the begin( ) method.
2. The argument "_dsp" that determines if the light signal is "on" or "off"
3. The variable "external_gate" that defines if the green light is generated by an external NOT gate or by internal software (this program).
The final code will be:
void Signals2_Class::signal_control(int _dsp ){ int red,green; if (_pol) { if (_dsp) { red = HIGH; green=LOW;} else {red=LOW, green = HIGH; } } else if (_dsp) { red = LOW; green=HIGH;} else {red=HIGH, green = LOW; } digitalWrite(_red_led,red); if (!_external_gate) { digitalWrite(_green_led,green);}}Note since signal_control( ) has been called by the *.ino code which is external to the class so signal_control( ) must be public. That is:
class Signals2_Class { public: Signals2_Class(int red_led); Signals2_Class(int red_led, int green_led); void begin( ); void begin(int pol); void signal_control(int dsp); private: int _red_led, _green_led; int _external_gate = 1; //assume NOT gate used int _pol = 1; //positive signalsCompiling and loading verified the program operation
In a practical situation the Signals2_Class will receive its input from an external sensor. In the example below the LDR is used. An instance, sensor of the LDR_Sensor_Class is declared. sensor is attached to pin A0.
Notes:
1. The IR_Sensor_Class may also be used instead of the LDR_Sensor_Class.
2. The LDR_Sensor_Class ( and the IR_Sensor_Class) provide an output train_over_sensor( )
3. The sensor class contains a one second filter causing train_over_sensor( ) to go inactive after a one second delay
4.The argument for signal_control( )is now sensor.train_over_sensor( )
5. This code does not require any changes to the Signals2_Class.
6. The example assigns sensor to pin A0. In the Arduino A0 is a digital pin but because it has analog capabilities Arduino have give it the label A0. The example is using the digital features.
With a RED-GREEN sequence it is sensible for a delay before the signals go GREEN to allow a following train to enter the block. This may be implemented by overloading the signal_control( ) method with a second argument - the wait time in seconds.
Once a train is detected the signal should turn RED and remain RED until a wait_time after the train departs. This is illustrated with the following FSM (finite state machine).
The design has two states and requires a variable to store the start time. These are declared in the class private region.
class Signals2_Class { ........private: ........ long int start_time; //start time of delay loop enum state {wait4train, wait4end} signal_state =wait4train;The FSM of the previous diagram is implemented in the Signals2_Class.cpp implementation file as shown:
The above code performs all the logic operations to implement the timing aspects. Both states call the single argument signal_control( ) which sorts out the signal levels to be applied to the pin(s), taking into account the polarity of the signals and if/if not an external NOT gate is used.
Note that the design using the state machine is Non-blocking. By contrast using a delay( ) function will block other code from executing.
To test the modified class the following code was added to the main program loop.
When the LDR was covered by a train the signal went RED. When the train passed after 6 seconds the signal returned to GREEN (1 second due to filter in sensor + 5 seconds set in the signal_control( ) method.)
This project has used the LDR_Sensor_Class::train_over_sensor( ) method as input to the Signals2_Class. (IR_Sensor_Class could also be used as an input). Possible combinations are:
Signals2_Class( ) One argument gives RED pin. GREEN implemented using a NOT gate:
Two arguments: First argument gives RED pin. Second argument GREEN
begin( ) No argument assumes positive signals
Argument = 0 negative signals
Argument = 1 positive signals
signal_control( ) One argument: 1 RED active, 0 RED inactive. No delay
Two arguments: Second argument: Delay before signal turns GREEN
This project was for a two aspect signal (RED-GREEN). A following project Signals3_Class will look at three aspect signals. (RED-AMBER-GREEN)