Header File: LDR_Serial_Sensor_Class.h
Constructor: LDR_Serial_Sensor_Class(ser,load,clk) arguments give circuit wiring
Public Methods: begin( ) Initialisation. Initialises pins as defined in the constructor.
train_over_sensor(sen) sen - Sensor of interest
Abstract/Summary: This project develops a method train_over_sensor( ) that is part of a class that reads up to 8 LDR sensors. To avoid false readings the design includes a 1 second filter. To reduce the micro-controller pin count a parallel to serial converter is used.
Possible Audience: Hardware enthusiasts interested in parallel to serial converters and programmers who are interested in developing small Cpp classes/libraries.
Keywords: LDR (Light Detecting Resistor), Arduino IDE, Cpp Class, Parallel to Serial, Cpp Constructor, Scope resolution operator, non blocking code
Required Hardware: Micro-controller (The Arduino NANO was used for testing). 74HC165 Parallel to Serial Converter. Up to 8 LDRs + pull up resistors (Prototype used 4K7 ohm but actual value will depend upon the light level. Starting with 10K trim pots might be a better option.)
___________________________________
To monitor the position of a model train LDR sensors are placed in the track.
An earlier project developed a LDR_Sensor_Class for an individual LDR sensors. In that project each sensor was wired to individual input pins of the micro-controller.
However many train layouts require more sensors than there are pins so some form of combining is necessary. In this project the LDRs are wired to the inputs of a parallel to serial chip and the results clocked into the micro-controller. This project will be known as LDR_Serial_Sensor_Class.
One feature of the original project was a 1 second filter that attempted to eliminate false readings that could occur when the train couplings were over the sensors.
The snippet shows the output of two wagons moving across a sensor. Without the filter there is a '0', approximately half way through the trace when the couplings are above the sensor.
LDR_Serial_Sensor_Class will include the 1 second filter.
The project LDR_Sensor_Class created a method train_over_sensor( ) that returned true when a train was present. LDR_Serial_Sensor_Class will duplicate train_over_sensor( ) except that it will be necessary to include an argument to specify the sensor of interest.
----------------------------
The project LDR_Serial_Sensor_Class will use a 74HC165 parallel to serial converter chip as shown below. The 74HC165 has 8 parallel inputs++.
The circuit shows an Arduino NANO although any other microcontroller may be used**.
The operation will be controlled by the Shift/Load pin. On the load signal the LDR circuits are sampled and the results placed in flip flops in the '165. On a clock pulse the contents of the flip flops are rippled into the micro-controller.
Note the LDR circuit shows a 4K7 pull up resistor. I have found this appropriate in my situation. It is suggested that a 10K trim pot might be used and the value adjusted to give optimum performance.-See page Testing the LDR
++If more than 8 sensors are required several 74HC165 can be wired in series. The discussion on this page assumes up to 8 sensors.
** For a later project I wish to generate a HTML page to display the train position on a Smartphone. For that application an ESP8266 will be used.
---------------------------------
The project will be developed using the Arduino IDE.
1. The first step is to create a new file/project. I have given it the name LDR_Serial_Sensor_Class which summarises the project and also matches the name of this page. Note the IDE will give the file the extension*.ino and will save the file in a folder of the same name as a sub-folder of the working folder/directory. (See file-->Preferences under sketchbook location:)
2. Use the inverted triangle on the top right of the IDE to create two new tabs (files). Give these the names LDR_Serial_Sensor_Class.cpp and LDR_Serial_Sensor_Class.h. Note they must have the same name but extensions "cpp" and "h".
3. Select the appropriate target board. In my case this was a two step process:
(i) Select Tools-->Board--> and select the NANO from the list.
(ii) Use Tools-->Processor -->then select ATmega328P(Oldbootloader). The best step here is to try and run some working code -if it will not load try the other bootloader.
4. Select the correct communications port. In the example above it is COM7.
At this point the program should compile and run continuously executing the empty loop( ) function.
The next steps are to populate the three files.
__________________________________
The first step is the header file. Possible starting code will be:
1. The header file is included in a wrapper "#ifndef ..... #endif that avoids including the code a second time if it has previously been included.
2. The code includes the Arduino header file. This header may be required for some operations. The IDE will include this library automatically but libraries do not.
3. A clsss LDR_Serial_Sensor_Class is created. This will include 3 public methods.
(i). The constructor LDR_Serial_Sensor_Class(int ser, int load, int clk) where the arguments indicate how the circuit is wired.
(ii) The begin( ) method that will initialise the circuit. The load and clk pins will be set as outputs.
(iii) The train_over-sensor( int sen) method. It is anticipated that this will indicate the status of the sensor sen.
4. Three variables are defined in the private area. By convention these start with an underscore and will be assigned to the constructor arguments.
The program should successfully compile at this point.
--------------------------------------
The starting code for the implementation file is shown:
Notes:
1. The implementation code must include the header file.
2. The code for the constructor assigns the private variables _ser, _load and _clk to the arguments
3. The begin( ) method assigns the pins _load and _clk as outputs. Pin _ser is set to an input although it is an input by default.
4. For the moment the train_over_sensor( ) method will return '0' - no train detected. This method will be expanded as the project progresses.
5. Note the scope resolution is defined for each method via LDR_Serial_Sensor_Class :: This says the method is associated with that class. The :: is known as the scope resolution operator.
-------------------------------------
The first pass at the code that will be used to test the library/class is:
Notes
1. The file includes the class header ie LDR_Serial_Sensor_Class.h - note .h
2. An instance or object of the class is defined. It has been given the label sensors( ). Further the arguments are 2, 3 and 4 and these correspond to the circuit wiring.
3. The set up routine includes two begin statements
(i) Initialise the Arduino IDE serial port for debugging the design
(ii) Initialise the instant /object sensors. This will set up direction of the three pins (see *.cpp file)
4. In the loop( ) the result of the method train_over_sensor(4) is sent to the serial port. For testing LDR 4 will be observed. Currently the train_over_sensor method returns '0'. The method will be expanded in future sections.
5. There is a 500mS delay that effectively determines the sampling rate.
----------------------------------
My prototyping board required some wiring so I considered it sensible to perform some tests prior to inserting the 74HC165 chip. As well as toggling the Load and Clock signals the Serial line was toggled. The code was
This gave the following results on a MSO (Mixed Signal Oscilloscope)
The next step is to populate the train_over_sensor( ) method in the implementation file (ie the *.cpp file)
-------------------------------------------------
The required sequence will be:
1. Initialise Pin 1 Low to perform a parallel load.
2. Set Pin 1 High to allow a serial shift.
3. Read pin 10 to read the Serial Output.
4. Internal uC operations with the input bit - place into array of bits.
5. Pulse pin 2 high to ripple input data across.
Repeat steps 3 through 5 8 times.
6. Return pin 1 low
The code will be:
With inputs 2 and 5 pulled low the waveforms are:
Note a read is performed before the rising edge of the input clock. The input sequence will be 1,1,0,1,1,1,0,1. ie _status = 1,1,0,1,1,1,0,1
This gives train_over_sensor(4) == 1 whereas train_over_sensor(5) ==0.
With fixed inputs the output on the serial monitor was a series of 1's (train detected) or 0's depending upon which output is tested.
------------------------------------
As presented train_over_sensor( ) will react by going high immediately there is a train over the sensor. However, while the train is over the sensor there could be false departure messages. For example, when the train couplings are over the sensor. This situation is captured below where the output train_over_sensor(4) is displayed on the serial monitor. For experimentation the sample rate has been set to 100mSec. The results show one "false" reading of 100ms and a second of 800ms. In the pages LDR_Sensor_Class and IR_Sensor_Class a 1 one second filter was included to try and eliminate these false readings. A one second filter will be included as part of this project.
To implement the filter an enumerated type train_state is defined. It has two states: no_train and a_train. By default these states have values 0 and 1. However, this has been spelt out in the definition to reinforce that the numerical values will be used by the main program.
The private area of the class will also define an integer train_detected along with the start_time .
A constant delay4flase is also defined. This defines the time the design will wait after no train is detected until it is confident that the train has really departed. In the final design it will be set to 1 second although 4 seconds was used for testing.
Not shown in the setup( ) routine train_status has been initialised to no train..
In the state no_train the code waits until a train is detected and moves to the state a_train. The start time is also set to the current millis( ) reading.
In the state a_train while there is a train the timing loop is restarted. When a train is no longer detected for the specified time the train moves back to the state no_train.
----------------------------------------
In the final product the following "collector" will be used to bring together the signals from the 8 LDRs. Around the edge of the board are 8 sets of terminals that will ultimately be connected to a LDR located on the train track. In the centre are the 4K7 ohm pull up resistors. On the right is the 74HC165 parallel to serial convertor with coloured wires going to the NANO pins. On the board there are also header pins used by the MSO (Mixed signal Oscilloscope) to monitor the signals.
For testing most LDRs are not connected so the 4K7 ohm pull up resistors take every input high corresponding to a train over all sensors. For testing two of the inputs are linked. This corresponds to an LDR in full illumination or no train present. In addition a LDR is wired to channel 4. Covering the LDR will correspond to a train present while left uncovered the LDR is fully illuminated equivalent to no train present.
Note 4K7 was found by experiment to be suitable in my environment. Other values may be necessary - see also page Testing the LDR
The project was tested by covering/uncovering the LDR and observing the output on the IDE Serial Monitor. With the LDR uncovered the output on the serial monitor was a string of 0's. Covering the LDR the output as designed goes to 1 immediately. The output will then remain at a "1" for a time set by delay4false after the sensor is uncovered. Setting delay4false to 4 seconds seemed an ideal value to visually observe the results.
Alternatively the following code may be used:
void loop() {for (int i=0; i<8; i++) Serial.print(sensors.train_over_sensor(i)); Serial.println(); delay(1000);}With the lower three bits hardwired and an LDR at the most significant bit the following results were obtained verifying the operation.
11111000111110000111100001111000111111000--------------------------------------------------
If the design is to include other functions/operations it is critical that the the routines be non blocking. In this example the train_over_sensor( ) method is non blocking. The program will enter the method, test everything and exit immediately. It will not "hang" around waiting until the 1 second is up. There are no while( ) loops that might block other functions.
By contrast the delay( ) method in the main or client code is a blocking function. We know that the delay is there and what its implicationsa might be. However an objective in using C++ classes is to create libraries that are completely invisible to the end user and any blocking operations will defeat that objective.
Also of interest is the performance of the software. The output waveforms for the train_over_sensor( ) operation are illustrated across. As can be seen the complete operation takes 112 uSec with most of the time being taken by the read sensor, pulse clock operation. That time between the last clock going low and the load signal going low is when the testing/filtering is performed and as can be seen it is of the order of 10 uSec.
The code as given will only monitor 1 sensor. To monitor multiple sensors there will need to be an array for each of the variables sensor_state, start_time and train_detected.
That is:
private: int _ser; int _load; int _clk;int _status[8]; enum train_state{no_train=0,a_train=1} sensor_state[8];int train_detected[8];long int start_time[8];The sensor_state for all sensors will need to be initialised as part of the setup( ) routine. That is
for (int i = 0; i<=7; i++) sensor_state[i] = no_train;
See LDR_Serial_Sensor_Class_code
----------------------------
The class LDR_Serial_Sensor_Class that will read the signal from up to 8 LDR sensors has been successfully. The class has a public method "train_over_sensor(sen)" that will return the sensor status. This method includes a 1 second filter that waits for 1 second after the train is no longer detected to guard against false readings such as the sensor over couplings.
In general the 8 clock pulses were evenly spaced and the complete operation complete in 112uSec. However about once every 20+ seconds the clock pulse was extended and the whole procedure took 118uSec.
As shown it was a different pulse that was being extended. The relevant code is the two lines digitalWrite(_clk,HIGH); digitalWrite(_clk,LOW);. The results indicate that occasionally either as part of these routines or between them the Arduino background or operating system takes over for 6 uSec. This could involve some timing operations.
As this will have no impact on the operation of the class I have not attempted to resolve the situation at this time.