Include File: < LDR_Sensor_Class.h>
Constructor: LDR_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 illustrates how to develop a C++ Class and Library in the Arduino environment. The sample code develops a library for a LDR 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.
Audience: Enthusiasts wishing to implement a LDR into a project and C++ programmers wishing to implement Clas/Libraries in the Arduino environment.
Keywords: C++, C++ Class, C++ Method, C++ Instance, C++Library, LDR, Light Detecting Resistor, ESP8266, Arduino IDE, Arduino NANO, Arduino UNO, scope resolution operator
Required Components: LDR, 1kohm resistor, 10kohm trim pot, Microcontroller- eg Arduino UNO/NANO. ESP8266 used in example
This HTML page could be viewed two ways:
1. It is a project that will develop code to monitor a LDR (Light Detecting Resistor) sensor. The code will be written in C++ and class/library developed for the LDR sensor.
2. It is a project that is an introduction on how to develop Classes and Libraries in the Arduino environment using the LDR sensor.
An advantage of using Classes and Library functions is that they permit reuse of the code for multiple sensors. Future projects will involve monitoring Model Trains using (multiple) LDR Sensors. This HTML page develops an LDR_Sensor_Class that will allow code reuse.
An added advantage will be that the code detail is hidden from the top level code.
An introduction to the LDR 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 both the LOIN(WEMOS) D1 Mini Lite and Arduino NANO were used for testing. Select the chosen device from the Tools : Board Manager option in the IDE.
The test circuit as used in the HTML page LDR_Test using the ESP8266 is shown above. The ESP8266 will read the output of the IR sensor module on pin D0. The circuit was also tested using pin 3 of an Arduino NANO. Other projects used the developed library with an UNO verifying the validity of the developed class/library.
A conventional/traditional program is as follows: (Note this is a working program)
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 that we are using 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::
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 LDR_Sensor_Class. I have chosen to also give the test code the label LDR_Sensor_Class. However the Arduino IDE gives it the extension .ino. There are now three files:
The 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 "LDR_Sensor_Class.cpp". Repeat for the file "LDR_Sensor_Class.h".
These new files will be in the same directory/folder as the original file "IDR_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, how 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 LDR_Sensor_Class.h will be:
#ifndef IR_Sensor_Class_h #define IR_Sensor_Class_h#endif
In this example a class LDR_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, LDR_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 "LDR_Sensor_Class" (extension .ino)
The private group consists of variables that will be used internally. That is in our example compiling LDR_Sensor_Class" will give an error if the main code tries to access "start_time".
The header file has 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 IDR_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 LDR_Sensor_Class_h is not defined. If LDR_Sensor_Class_h is not defined then this header defines the variable and creates the class LDR_Sensor_Class
The next step is to generate the implementation file LDR_Sensor_Class.cpp to implement the functions/methods declared in the header file.
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 LDR_Sensor_Class.h will be:
In this example a class LDR_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, LDR_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 "LDR_Sensor_Class" (extension .ino)
The private group consists of variables that will be used internally. That is in our example compiling LDR_Sensor_Class" will give an error if the main code tries to access "start_time".
The header file has 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 IDR_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 LDR_Sensor_Class_h is not defined.
If LDR_Sensor_Class_h is not defined then this header defines the variable and creates the class LDR_Sensor_Class
The next step is to generate the implementation file LDR_Sensor_Class.cpp to implement the functions/methods declared in the header file.
The implementation file for the Class LDR_Sensor_Class will be:
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: LDR_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 LDR_Sensor_Class.
The first method LDR_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 wished 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. However since all pins are input by default this statement and the begin( ) method are redundant. I have included begin( ) as a begin( ) will be necessary in some libraries/classes so I decided to include in all. August 2020: It was found convenient to activate the internal pull up resistor.This means the pin, if not connected will be pulled high. Thus the function train_over_sensor( ) will not detect a train. sensor1.begin( ) is no longer redundant.
The third method train_over_sensor( ) does not have the parameter of the original code, instead the it uses the parameter (_pin) from the constructor.
The library LDR_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 LDR_Sensor_Class is given below.
The first line tells the compiler to include the header file LDR_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 LDR_Sensor_Class.ino.
The second line creates an object or instance of the LDR_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 LDR_Train_Sensor.cpp will correctly detect the arrival of a train. However there are "false" readings due to couplings.
The library code LDR_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 code of the modified is given below
#include "LDR_Sensor_Class.h" LDR_Sensor_Class::LDR_Sensor_Class(int pin) { _pin = pin; };void LDR_Sensor_Class::begin ( ){ // pinMode(_pin,INPUT); pinMode(_pin,INPUT_PULLUP); //Modified August 2020 sensor_state = wait4train;} int LDR_Sensor_Class::train_over_sensor() { int train_detected = digitalRead(_pin); //pin high for train if (sensor_state == wait4train) { if (train_detected){ sensor_state = wait4end; //next state wait for no train start_time = millis( ); } return (train_detected); //normally will be no-train } else if (sensor_state == wait4end) { if (train_detected) //detected train so keep restarting timing loop { start_time = millis( ); return(train_detected); //will be train detected } else //no train detected { if (millis( ) - start_time >= delay4false) { //exit now as no-train for awhile sensor_state = wait4train; //ready for next train return(train_detected); //will be no train } else // maybe a false reading - need to wait return(!train_detected); //will be saying there still is a train } } else //error should never get here { sensor_state = wait4train; //go back to start return(3); //not a valid state - used in debugging only }}Note there are two states wait4train and wait4end declared in the private region of the header file. These are used by the 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 micro-controller time millis(). The timer will only timeout if no train is detected for 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.
When the following screen shots were taken the code was located in 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 LDR_Sensor_Class.cpp and LDR_Sensor_Class.h. It will have the name LDR_Sensor_Class.zip.
2. In the Arduino IDE use Sketch > Include Library > Add .Zip Library and select the file LDR_Sensor_Class.zip. (This is also how a second user would use your library)
Under the preference directory the Arduino IDE will created two sub-directories/folders: libraries and IR_Sensor_Class. The sub directory IR_Sensor_Class will contain the two files LDR_Sensor_Class.cpp and LDR_Sensor_Class.h..
Then to use the library in the application/test/client replace the quotes with " < " and " > ": The Arduino compiler will now look for the libraries in the libraries folder.
The final code becomes:
#include <LDR_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 knows nothing of the messy code implemented in the train_over-sensor( ) routine/method of the implementation code LDR_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: I have given mine the label 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 LDR_Sensor_Class
3. Clicking will add the library to the new program.
#include <LDR_Sensor_Class.h>//#include "LDR_Sensor_Class.h"LDR_Sensor_Class sensor(D2);void setup() {}4. Declare a new object of the LDR_Sensor Class. I have given it 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. The page LDR_Overload_Class discusses overloading the LDR_Sensor_Class by adding (overloading) the provision for modifying the filter time.