Summary: This project uses two serial to parallel chips (74HC164) to control upto 16 signals.
Include file <Coal_Basin_Signals.h>
Constructor: Coal_Basin_Signals sig; //design assumes A0 = clock, A1 = data
Public Methods: void begin( ); Will set all signals for RIGHT
void control_signals(int sig_no, int val); Signal number signal1 ..signal16 set either LEFT or RIGHT (only 1..11 used in Coal Basin Example).
Required Hardware: Arduino Uno or Nano microcontroller with two 74HC164. Model railway signals or LEDs to test operation.
Required Libraries. No external libraries used
Possible Audience: Model railway enthusiasts who wish to control a greater number of signals than the input-output pins available. Cpp programmers interested in developing simple Cpp classes/libraries.
Key Words: Nano, Serial to Parallel, Cpp Class, Implementation code, Header file. 74HC164, conversion time
---------------------------------------------------------------------------------------
 Background
An area known as the Coal Basin as shown below is being developed for a model train layout.
The control panel consists of a number of push buttons shown above as 'A' through 'M' that supply requests to control the points "1" through "11". These in turn control servos to switch the points to allow trains to travel on the routes 'A' through 'M'.
This project develops the class/library Coal_Basin_Signals that will operate the signals at points '1' through '11'.
At each point there can be 3 signals. There is provision in the design to use all three on all points if desired.**
** With 3 signals on each of the 11 points the whole area will look like a "Christmas" tree so in pracctice only a subset might be used.
----------------------------------------------------------------
The design consists of two serial to parallel converters (74HC164) interfaced to the Nano via pins A0 (Clock) and A1 Data). The output of the 74HC164 goes to a 3 pin connector where the lights for a signal are wired.**
When the output and signal is HIGH the three lower lights will be active. That is a RED for trains approaching from the left track and a GREEN for trains going in either direction on the straight track. With a LOW the top signals are active. That is GREEN for train approaching on the left track, AMBER to let the train approaching from the bottom the points are for the left track, and RED to tell the train approaching from directly above to stop.
** At this point provision is being made to have a full signal set at each point. In the final product it might be decided to use only a limited set.One question will be what are the signals for. If they are to tell the operator what points are valid the operators should know this by knowing what route they selected.
----------------------------------------------------------------
The program will consist of 3 files:
The *.ino test/client/application code to be named Coal_Basin_Signals (the IDE will add the extension *.ino)
The header file Coal_Basin_Signals.h and the implementation file Coal_Basin_Signals.cpp. These two files will form the Coal_Basin_Signals library.
In the Arduino IDE create a new file and save_as Coal_Basin_Signals. The Arduino IDE will create a new folder Coal_Basin_Signals and save the new file Coal_Basin_Signals in that folder giving it the extension *.ino
In the Arduinoo IDE use the inverted triangle on the top right of the screen to create two New_Tabs (files). Give these the labels Coal_Basin_Signals.cpp and Coal_Basin_Signals.h. Note extensions.
The next job is to populate the three files. For starters the line #include "Coal_Basin_Signals.h" could be added to both the *.ino and *.cpp files.
---------------------------------------------------
The first step in any design is to list the requirements. In this case it will be to set a specified output of the 74HC164 either high or low. This suggests that the Coal_Basin_Signal class includes a method control_signal(sig_no, val). To this must be added a begin( ) method that will initialise the appropriate input/output. For starters the class will look like
class Coal_Basin_Signals {
public:
Coal_Basin_Signals ();
void begin( );
void control_signals(int sig_no, int val);
};
This fragment includes the class constructor. If the pins A0 and A1 might change arguments could be added to the constructor.
The test code at this point might look like;
//Coal_Basin_Signal.ino the client/test/application code
#include "Coal_Basin_Signals.h"
Coal_Basin_Signals sig;
void setup() {
sig.begin( );
while(1) {
sig.control_signals(1,1);
delay(2000);
sig.control_signals(1,0);
delay(2000);
}
}
void loop() {
}
---------------------------------------
For starters the implementation code should include every method of the class. That is:
#include "Coal_Basin_Signals.h"
Coal_Basin_Signals::Coal_Basin_Signals( ) { }
void Coal_Basin_Signals::begin( ) { }
void Coal_Basin_Signals::control_signals(int sig_no, int val){ }
At this point the code will compile but not do anything.
The next step is to add the constructor code that will include the circuit wiring. Two variables _clk and _dat should be added to the private area of the class. That is:
class Coal_Basin_Signals {
public:
. ......
private: int _clk; int _dat;
};
The implementation of the constructor becomes:
Coal_Basin_Signals::Coal_Basin_Signals( ) {
_clk = A0; _dat = A1;
}
_clk and _dat should be programmed as outputs in the begin( ) method. That is
void Coal_Basin_Signals::begin( ) {
pinMode(_clk,OUTPUT):
pinMode(_dat,OUTPUT);
}
------------------------------------------
Ultimately to control the signals the required pattern will be stored in a variable which will be rippled 16 times into the 74HC164 serial to parallel converters.
For starters a constant value will be rippled across.
void Coal_Basin_Signals::control_signals(int sig_no, int val){
for (int i=0; i<16; i++){
digitalWrite(_dat,val);
digitalWrite(_clk,1);
digitalWrite(_clk,0);
}
}
One test signal can be patched as shown and wired to one of the signal outputs. With the *.ino test code as given the two lights will flash GREEN - RED etc.
For the practical situation the design is more complex. The status of all the signals need to be known. That is set up a global variable _signal_status.
unsigned int _signal_status;
The code for control_signals( ) then becomes:
void Coal_Basin_Signals::control_signals(int sig_no, int val){
unsigned int sig_data = _signal_status;
if (val) sig_data |= (1<<sig_no); else sig_data &= (~(1<<sig_no));
for (int i=0; i<16; i++){
digitalWrite(_dat,sig_data&1);
digitalWrite(_clk,1);
digitalWrite(_clk,0);
sig_data = sig_data>>1;
}
}
The waveforms are illustrated below.
There are 16 clock pulses. On the first pulse the data at the micro-controller output is clocked into the shift registers.
Note the cycle time is 184 mSec
By the 9th clock pulse it has rippled into the 9th shift register and become the output of that stage.
By the 16th clock the "pulse" has reached the final stage where it remains as the stable output until the next cycle.++Â
** In the layout of the PCB, to avoid cross-overs bit x in the shift register may not correspond to bit x of the data so a conversion function is required. See next section.
++ For clarity of the displays all other signals are in the "0" state and the delay between the two cycles dropped from 2 sec to 1 mSec.
-------------------------------------------------------
In the PCB to avoid crossovers the shift register output pins were not wired directly to the corresponding jumper pins. Further, there is a change in definitions. Signal1 becomes output "0", Signal2 output "1". To "realign" the pins are defined in the header file.
#define signal1 0
#define signal2 1
#define signal3 7
#define signal4 6
#define signal5 5
#define signal6 4
#define signal7 2
#define signal8 3
#define signal9 8
#define signal10 9
#define signal11 10
#define signal12 11
#define signal13 15
#define signal14 14
#define signal15 13
#define signal16 12
The above values were determined by observation. Any write involving signal1 ended up in bit 0 of the shift register. signal2 was output at bit 1. The third ouput (bit 2) was wired to bit 7 of the shift register so to manipulate the signal physically connected to bit 2 the signal3 data needed to end up in register 7 of the 74HC164 shift register.**
** All of this will be transparent to the end used. They will just program signal3 and the output will end up on the third set of output pins.
---------------------------------------------------
With the availability of a logic timing analyzer the operation of the design was easily verified. Some prototyping signals also helped verify the operation.
As shown on the timing analyzer waveforms the control_signals( ) method took 184uSec.(conversion time) The code as given changes one signal at a time so if all 11 signals were to be changed it would take 2mSec. Since the values all ripple through the shift registers there would be 2mSec of the signals flashing. It was decided to leave the design as is and see how that worked out in practice. It might be necessary to change both the hardware and the firmware design.
A second issue will be initializing the signal outputs. It was decided to set them all RIGHT and leave to the end user to deliberately program any signals they wish to change.
The begin( ) method includes the two lines where 65535 is bits 0 through 15 set to "1"
_signal_status = 65535; //turn right
Coal_Basin_Signals::control_signals(1,1); //forces output
To force bit 13 to LEFT include the code in the *.ino program
void setup() {----------------------------------------
To create the library Coal_Basin_Signals:
1. Pack the two files, Coal_Basin_Signals.cpp and Coal_Basin_Signals.h into a *.zip file. Zipping willl give the default label Coal_Basin_Signals.zip
2. In the Arduino IDE use the sequence -> Include Library -> Add *.zip Library and select Coal_Basin_Signals.zip
3. In the Arduino work directory there will now be a sub-directory "libraries" and a further subdirectory "Coal_Basin_Signals" that contains the *.cpp and *.h files.
4. To include the Coal_Basin_Signals library in any project include the line #include <Coal_Basin_Signals.h> **
5. As per this project, Coal_Basin_Signals sig; will create an object of the class Coal_Basin_Signals.
** With quotes the header file will only be found in the same directory/folder as the project. With < > the compiler will look in the libraries folder.