Include File: <ir_sensor_class.h>
Constructor: IR_Sensor_Class(int ) Parameter is pin where sensor is wired.
Public Methods begin( ) Initialises sensor pin as an input.
train_over_sensor( ) Returns status of pin. 1= train present.
Abstract: This page develops a C++ Class and Library in the Arduino environment. The sample code develops a library for an IR Sensor used on a Model Railway. The class includes provision for eliminating false readings due to couplings when testing for the end of the train. The class includes a public method train_over_sensor( ) that returns 1 when there is a train, 0 otherwise.
Possible Audience: Hobbyist who wish to use an IR sensor in a model railway and programmers who would like to learn about developing C++ class/libraries in the Arduino environment.
Keywords: C++, C++ Class, C++ Method, C++ Instance, C++Library, IR Sensor, Infrared Sensor, ESP8266, Arduino IDE, Arduino NANO
Required Components: TCRT5000 Sensor Module. Micro-controller. This page was tested with ESP8266 but an Arduino UNO/NANO may be used.
This page has two audiences:
1. Model railway enthusiasts who wish to use IR sensors to detect model trains. The sensors will be coded using the C++ language, or
2. Programmers who wish to learn about C++ classes/libraries in the Arduino environment. A class is developed using an IR sensor as an example
An advantage of using Classes and Library functions is that they permit reuse of the code for multiple sensors. Future projects involve monitoring Model Trains using (multiple) IR Sensors. This HTML page develops an IR_Sensor_Class that will allow code reuse.
An added advantage will be that the code detail is hidden (encapsulated) from the top level or user code.
An introduction to the IR detector/sensor that is used in this project was presented in an earlier project.
The project code is developed using the Arduino Integrated Development Environment (IDE).
In my example the LOIN(WEMOS) D1 Mini Lite was chosen from the Tools : Board Manager option in the IDE.
Some of my later projects used the Arduino NANO. These projects effectively verified the operation of this class in a different enironment.
The test circuit is shown above. The ESP8266 will read the output of the IR sensor module on pin D1. The circuit was also tested using pin 3 of an Arduino NANO.
A conventional/traditional program is as follows:
void setup() { Serial.begin(115200); //baud rate serial monitor}The following sections will convert this simple program into C++ format to illustrate Classes and generate a C++ Library.
(Note as given the train_over_sensor routine appears trivial but will be expanded in the final application.)
The program to illustrate Classes and Libraries is given above:
The train_over_sensor( ) function/method might be written in a different file or it could be a library sold by a different vendor. The project would then consist of two files::
#include "name_of_included_file"void setup() { Serial.begin(115200); //baud rate serial monitor}void loop() { Serial.print(train_over_sensor(D1)); delay(100);}To generate a C++ Class and library the included file is split into two: a header file and an implementation file. Both must have the same name but different extensions (.h and .cpp). For this page they are given the labels IR_Sensor_Class. I have chosen to also give the test code the label IR_Sensor_Class. However the Arduino IDE editor will give it the extension "*.ino".
There are now three files:
//file IR_Sensor_Class.ino : Test Program#include "IR_Sensor_Class.h" the codeThe best way to generate the two additional files is to include them in the same project. To do this use the inverted triangle close to the top right of the Arduino IDE and select the option "New Tab" and name the new file "IR_Sensor_Class.cpp". Repeat for the file "IR_Sensor_Class.h".
These new files will be in the same directory/folder as the original file "IR_Sensor_Class.ino". At compile time all files will be compiled simultaneously as a single project.
The following sections will fill in the details of the two new files, to create a library plus the changes to the original test/application program to use the C++ library.
The header file will define a Class. The Class will define functions (methods) and variables that are available to users (Public) and those that the uses are not allowed to use (Private). The code for the header file IR_Sensor_Class.h will be:
#ifndef IR_Sensor_Class_h #define IR_Sensor_Class_hIn this example a class IR_Train_Sensor is created. It defines two groups of variables/functions
The public group that includes
1. the class constructor that has the same name, IR_Sensor_Class as the class, (Constructors usually initialize the data members of the new object. ) and
2. the function/methods begin( ) and train_over_sensor( ) that will be called externally by our test program "IR_Sensor_Class" (extension .ino)
The private group consists of variables that will be used internally.
The header file has a two additional features:
1. It includes the header file "Arduino.h". This file is included by the compiler in all basic/raw Arduino code but not in library functions.
In the example code digitalRead( ) is defined by"Arduino.h" (Note this include appears to work both when enclosed by quotes or "< " and ">". It also works with a lower case "a".
2. The first #ifndef IR_Sensor_Class_h and last lines #endif of the header file are to avoid the class being defined more than once.
The code in the header file is only implemented if the variable IR_Sensor_Class_h is not defined.
If IR_Sensor_Class_h is not defined then this header defines the variable and creates the class IR_Sensor_Class
The next step is to generate the implementation file IR_Sensor_Class.cpp to implement the functions/methods declared in the header file.
The implementation file for the Class IR_Sensor_Class will be:
#include "IR_Sensor_Class.h" IR_Sensor_Class::IR_Sensor_Class(int pin) { _pin = pin; }; void IR_Sensor_Class::begin ( ){ pinMode(_pin,INPUT);}; int IR_Sensor_Class::train_over_sensor() { return(!digitalRead(_pin)); //pin low for train }An explanation is as follows
The first line includes the header file. With the quotes it tells the compiler to look in the same directory.
There are three functions/methods: IR_Train_Sensor(int pin), begin( ), and train_over_sensor( ). Both are prefaced with the class separated by two colons ( scope resolution :: operator). These indicate that the three functions are part of the class IR_Sensor_Class.
The first method IR_Sensor_Class (it has the same label as the class) is the class constructor. The constructor is executed each time an instance of the class is implemented. In the example it has one parameter- the pin we wish to use. The constructor sets the private variable _pin to the parameter pin. Note the underscore is a convention used for private variables.
The second method begin( ) sets _pin to be an input. (Since all pins are input by default this statement and the begin( ) method are redundant but are left for completeness.)
The third method train_over_sensor( ) does not have the parameter of the original code, instead it uses the parameter (_pin) from the constructor.
The library IR_Sensor_Class has been created. The next section will illustrate how to modify the original application code to use the library.
The final application program for using/testing the IR_Train_Sensor class is given below.
The first line tells the compiler to include the header file IR_Sensor_Class.h for the class library. Note the quotes indicating that the file will be found in the same directory as the test program IR_Sensor_Class.ino.
The second line creates an object or instance of the IR_Sensor_Class class. It is given the label/name sensor1(D1). If we wished to use a different pin that is defined when creating the object rather than as the parameter for the function train_over_sensor( ).
sensor1.begin( ) - see previous section.
In the main program loop the train_over_sensor( ) routine/method operates on the object sensor1.
The next step is to test the code in actual hardware.
Running the code on the target/test hardware gave the following results on the Arduino Serial Monitor with two coupled wagons running across the sensor.
As shown the trace shows a strings of 1's (train detected), a single "0" (train not detected) followed by a further set of "1's". The "0" would be when the couplings are above the sensor.
If we wish to detect when the train has passed we need to modify our code until we have a series of "0's". This will be undertaken in the next section. Readers who are only interested in developing a library can go directly to the section that places the code in the Arduino Library.
The program to date, implemented in the class implementation file IR_Train_Sensor.cpp will correctly detect the arrival of a train. However there are "false" readings due to couplings and areas under wagons that do not adequately reflect the IR beam. See HTML page on testing the IR sensor
The library code IR_Sensor_Class.cpp needs to be enhanced to determine when the train has actually passed. This will be assumed to have happened when there are a series of '0's'.
Note depending on the number of "0's" this will imply that the signal "train not over the sensor" will be sometime after when the train actually passes/leaves. It will also imply that if the train stops with its couplings above the sensor there will be a false "end of train"delay.
The final modified code is given below. Note the method train_detected(int pin); will need to be added to the private area of the class definition.
#include "IR_Sensor_Class.h" IR_Sensor_Class::IR_Sensor_Class(int pin) { _pin = pin; };void IR_Sensor_Class::begin ( ){ pinMode(_pin,INPUT); sensor_state = wait4train;} int IR_Sensor_Class::train_detected(int pin) { return (!digitalRead(pin)); //pin low for train} int IR_Sensor_Class::train_over_sensor() { switch (sensor_state) { case wait4train: if (train_detected(_pin)){ sensor_state = wait4end; start_time = millis( ); } break; case wait4end : if (train_detected (_pin)) start_time = millis( ); //train detected so keep restarting else if (millis( ) - start_time >= delay4false) //exit now as no-train for awhile sensor_state = wait4train; //ready for next train break; } return (sensor_state); }Note there are two states wait4train and wait4end declared in the private region of the header file. These are used by variable sensor_time. In the begin( ) method sensor_time is set to wait4train.
Initially the code is waiting for a train. Once a train is detected it moves to the state wait4end. In wait4end each time a train is detected a timer start_time is (re)set to the current microcontroller time millis(). The timer will only timeout if no train is detected after a time delay4false which is currently set to 1 second in the header file.
Testing now gave a solid set of 1's while the wagons were over the sensor and for one second afterwards.
The final step is to implement the code as an Arduino Library.
The code is currently located in the directory IR_Sensor_Class under my preferences directory C:\00rmit\Projects\Train. ( See File > Preferences) . The Arduino IDE will create the library under that same directory.
The final steps in creating a Library are to:
1. Create a .zip file containing the two files IR_Sensor_Class.cpp and IR_Sensor_Class.h. It will have the name IR_Sensor_Class.zip.
2. In the Arduino IDE use Sketch > Include Library > Add .Zip Library and select the file IR_Train_Sensor.zip. (This is also how a second user would use your library)
Under the preference directory the Arduino IDE has created two sub-directories/folders: libraries and IR_Sensor_Class. The sub directory IR_Sensor_Class will contain the two files IR_Sensor_Class.cpp and IR_Sensor_Class.h..
To use the library replace the quotes with " < " and " > ": The Arduino compiler will now look for the libraries in the libraries folder.
The final code becomes
#include <IR_Sensor_Class.h>IR_Sensor_Class sensor1(D1); void setup() { Serial.begin(115200); //baud rate serial monitor sensor1.begin( ); }void loop() { Serial.print(sensor1.train_over_sensor()); delay(200); }Note the simple application code: it knows nothing of the messy code implemented in the train_over-sensor( ) routine/method of the implementation code IR_Train_Sensor.cpp. (One advantage of using libraries)
The order of program execution will be: 1. The Constructor, 2. The setup( ) function, and 3. The loop( ) continuously.
To use the IR_Train_Sensor Class and Library
1. Create a new file: For example Train_Signals: the label of a new project.
2. From the toolbar select Sketch > Include Library > Contributed libraries and scroll down to find the library IR_Sensor_Class
3 Clicking will add the library to the new program.
#include <IR_Sensor_Class.h>//#include "IR_Sensor_Class.h"IR_Sensor_Class sensor(D2);void setup() {}4. Declare a new object of the IR_Train_Sensor Class. In sample code it is given the label "sensor" and the target destination will be pin D2 as opposed to D1 used in the development of this HTML page.
5. Use the Class methods in the program as required.
That concludes this HTML page on developing libraries for the Arduino. Future pages will give additional examples of Arduino Libraries. See for example Train_Signals_Class where the object sensor is the input to railway signals.