Summary: This project will develop a Cpp class to control 16 LEDs on a control panel.
Summary of Coal_Basin_LEDs Library.
Include File: <Coal_Basin_LEDs.h>
Constructor: Coal_Basin_LEDs object(int clk, int serout); User to define where Clock and Serial are wired.
Coal_Basin_LEDs object; No arguments will assume Clock wired to pin A0, Serial to pin A1Â
Public Methods: begin( ) Sets pins (clk) A2 and (serout) A3 as outputs.
control_signals(int routes). routes determines signals that are active.
Definitions: NULL, SILOS, WHARF1, WHARF2, SIDING1, SIDING2, COAL1, COAL2, YARD, STATION, TURNTABLE, BASIN, EASTSIDE1,EASTSIDE2.
Required Hardware: Arduino NANO wired to two Serial to Parallel 74HC164 convertors
Possible Audience. Microcontroller designers requiring to drive more LEDs than available outputs. This is a very simple project that is implemented in C++ so is a suitable project as an introduction to C++ (Cpp) using the Arduino IDE.
-----------------------------------
An area known as a Coal Basin as shown below is being developed for a model train layout.
Station Area Progess
The Wharf
The Silos
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 allow trains to travel on the routes 'A' through 'M'.
This project includes a small LED associated with each push button to indicate on the control panel which routes are selected.
The operational sequence will be:
1. A push button on the control panel is pressed. See the project Coal_Basin_Button.
2. A small LED located close to each push button will be activated to confirm what button was pressed. This project**
3. The appropriate switches will be activated to enable the selected routes. (Separate projects)
**The project develops a class library Coal_Basin_LEDs.
----------------------------------------------------------------.
Since there are more LEDs to be controlled than there are pins on the Arduino Nano two serial to parallel convertors as shown are used**.
** The design does not include an Output-Load. When the patterns change (very infrequently in micro-controller terms) the patterns will ripple across so quickly that flickering will not be an issue.
----------------------------------------------------
Assuming that a C++ class that can be included in other projects:
1. In the Arduino IDE open a new file and save as "Coal_Basin_LEDs". The Arduino IDE will give the new file the extension *.ino and also greate a new folder Coal_Basin_LEDs
2. In the Arduino IDE use the inverted triangle in the top right hand corner to create two "New Tab" or files. Give these the labels Coal_Basin_LEDs.cpp and Coal_Basin_LEDs.h.**
3. Populate the *.h file as follows:
//Coal_Basin_LEDs.h The header file
#ifndef COAL_BASIN_LEDS_H
#define COAL_BASIN_LEDS_H
#include "Arduino.h"
class Coal_Basin_LEDs {
public:
Coal_Basin_LEDs ();
Coal_Basin_LEDs (int clk, int out);
void begin( );
void control_signals(int routes);
};
#endif
Notes: The code is wrapped in the #ifndef statement to avoid the header file being included in a project more than once.
The program creates a class Coal_Basin_LEDs. This class contains 3 public methods.
(i) The constructor Coal_Basin_LEDs( ) that is used to create an instance/object of Coal_Basin_LEDs. Two options are given (a) with no arguments it is assumed the wiring is fixed and will be specified in the *.cpp file++ and, (b) the wiring will be specified when an object is created.
(ii) A begin( ) method that will initialise any I/O or variables
(iii) It is anticipated that a method control_signals(int routes) will be required so it is declared at this point
----------------------------------
This step will add the constructor code and the methods from the header file to the *.cpp or implementation file
//Coal_Basin_LEDs.cpp Implementation file.
#include "Coal_Basin_LEDs.h"
Coal_Basin_LEDs::Coal_Basin_LEDs( ){ }
void Coal_Basin_LEDs::begin( ){ }
void Coal_Basin_LEDs::control_signals(int leds){ }
Notes
(i) The code includes the header file. That is #include "Coal_Basin_LEDs.h".
(ii) The code also includes the title for the constructor Coal_Basin_LEDs( ), the begin() and the control_signals( ) methods. The details will be developed later.
--------------------------------------
Possible code for the basic test or client code *.ino will be:.
//Coal_Basin_LEDs.ino Test or application file
#include "Coal_Basin_LEDs.h"
Coal_Basin_LEDs route_dsp;
void setup() {
route_dsp.begin();
}
void loop() {
route_dsp.control_signals(65535); //should turn on all leds for testing
}
The program includes the header file and creates an object or instance route_dsp of the Coal_Basin_LEDs class.
The program calls the begin( ) method as part of the setup. and then continually calls control_signals( ).
At this point the program should compile but since there is no code in the implementation program it will do nothing.
** The *.cpp and *.h will form the Coal_Basin_LEDs class or library
++ Alternatively, a constructor could be declared with parameters. That is Coal_Basin_LEDs(int clk, int serout).
-------------------------------------------------------------
//Coal_Basin_LEDs.cpp Implementation file.
#include "Coal_Basin_LEDs.h"
Coal_Basin_LEDs::Coal_Basin_LEDs(int clk, int out){
_clk = clk; _serout = out;
}
Coal_Basin_LEDs::Coal_Basin_LEDs(){
_clk = A2; _serout = A3;
}
void Coal_Basin_LEDs::begin( ){
pinMode(_clk,OUTPUT);
pinMode(_serout,OUTPUT);
digitalWrite(_clk,LOW);
digitalWrite(_serout,LOW);
control_signals(0); //all off
}
void Coal_Basin_LEDs::control_signals(int number){ }
There are two options for the constructor with and without (two) arguments/parameters. The arguments are the clock (clk) and the serial output (out) which are assigned to the private variables _clk and _serout that are declared/included as part of the Coal_Basin_LEDs class.
class Coal_Basin_LEDs {
public:
Coal_Basin_LEDs ();
Coal_Basin_LEDs (int clk, int out);
void begin( );
void control_signals(unsigned int leds);
private: int _clk; int _serout;
};
In the implementation file these are assigned to the actual wiring pins A2 and A3.
In the begin method these two pins are set to digital outputs and set low.
-----------------------------------
void Coal_Basin_LEDs::control_signals(unsigned int leds){
for ( int i = 0; i<= 16; i++ )
{
digitalWrite(_serout,leds&1);
digitalWrite(_clk,1); //clock
digitalWrite(_clk,0);
leds = leds>>1;
}
}
The desired pattern is passed to the control_signals( ) method as an argument leds.
Bit 0 of leds is placed on the _serout pin (Serial in for the 74HC164) and the _clk pin is pulsed. On the clock pulse everything in the two 74HC164's is shifted across one bit and the value on the _serout pin loaded into the 74HC164 serial to parallel shift registers.
The variable leds is shifted across one bit with bit 1 becoming bit 0.
The loop is repeated 16 times with the variable leds being rippled into the 74HC164 shift registers..
Depending upon the value of leds different lights on the control panel will be active.
--------------------------------------
Ideally the numbering should have some form of pattern. That is SILOS = bit 0 or value 1, WHARF = bit 1 or value 2, WHARF2 = bit 2 or value 4 etc
However, to avoid crossovers in the PCB layout the wiring was taken to different or what appears random pins. See diagram where the actual translation was found by experiment.
Two tables are added to the header file giving the desired and actual values.
// Desired definitions
#define NULL 0
#define SILOS 1
#define WHARF1 2
#define WHARF2 4
#define SIDING1 8
#define SIDING2 16
#define COAL1 32
#define COAL2 64
#define YARD 128
#define STATION 256
#define TURNTABLE 512
#define BASIN 1024
#define EASTSIDE1 2048
#define EASTSIDE2 4096
////////////////////////
//The following definitions were determined by the PCB Wirings
#define O_NULL 0
#define O_SILOS 128
#define O_WHARF1 64
#define O_WHARF2 1
#define O_SIDING1 4
#define O_SIDING2 8
#define O_COAL1 2
#define O_COAL2 4096
#define O_YARD 8192
#define O_STATION 16384
#define O_TURNTABLE 2048
#define O_BASIN 1024
#define O_EASTSIDE1 32768
#define O_EASTSIDE2 512
and a function convert ( ) included in the application program.
unsigned int convert(unsigned int leds){
unsigned int lights= O_NULL;
if (leds&SILOS) lights |= O_SILOS; //1
if (leds&WHARF1) lights |= O_WHARF1; //2
if (leds&WHARF2) lights |= O_WHARF2; //4
if (leds&SIDING1) lights |= O_SIDING1; //8
if (leds&SIDING2) lights |= O_SIDING2; //16
if (leds&COAL1) lights |= O_COAL1; //32
if (leds&COAL2) lights |= O_COAL2; //64
if (leds&YARD) lights |= O_YARD; //128
if (leds&STATION) lights |= O_STATION; //256
if (leds&TURNTABLE) lights |= O_TURNTABLE; //512
if (leds&BASIN) lights |= O_BASIN; //1024
if (leds&EASTSIDE1) lights |= O_EASTSIDE1; //2048
if (leds&EASTSIDE2) lights |= O_EASTSIDE2; //4096
return lights;
}
void Coal_Basin_LEDs::control_signals(unsigned int leds){
leds = convert(leds);
for ( int i = 1; i<= 16; i++ )
{
digitalWrite(_serout,leds&1);
digitalWrite(_clk,1); //clock
digitalWrite(_clk,0);
leds = leds>>1;
}
} //ripple_out
---------------------------------------------
For testing the client or *.ino code was modified to continuously display every LED in turn.
//Coal_Basin_LEDs.ino Test or application file
#include "Coal_Basin_LEDs.h"
Coal_Basin_LEDs route_dsp(A2,A3);
void setup() {
route_dsp.begin();
Serial.begin(115200);
route_dsp.control_signals(0); //clear display
while(1){ //turn on all LEDs in turn for testing
for (unsigned int count = SILOS; count <= EASTSIDE2; count *= 2)
{ route_dsp.control_signals(count);
delay(500);
}
}
}
void loop() { }
By fiddling with this code the actual table of output values could be verified.
A second issue identified by the testing was that the Leds were too bright, so the dropping resistors needed to be significantly increased.
----------------------------------------
To create the library Coal_Basin_LEDs:
1. Pack the two files, Coal_Basin_LEDs.cpp and Coal_Basin_LEDs.h into a *.zip file. Zipping will give the default label Coal_Basin_LEDs.zip
2. In the Arduino IDE use the sequence -> Include Library -> Add *.zip Library and select Coal_Basin_LEDs.zip
3. In the Arduino work directory there will now be a sub-directory "libraries" and a further subdirectory "Coal_Basin_LEDs" that contains the *.cpp and *.h files.
4. To include the Coal_Basin_LEDs library in any project include the line #include <Coal_Basin_LEDs.h> **
5. As per this project, Coal_Basin_LEDs route_dsp; will create an object of the class Coal_Basin_LEDs.
** 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 folde