Summary: This project develops a library/class that uses a microcontroller with serial inputs to read up to 16 inputs from parallel to serial converter. The project is orientated towards part of a control system for model railway yards known as the Coal Basin
Include file: <Coal_Basin_Buttons.h>
Constructor: Coal_Basin_Buttons(int load, int clk, int ser); //no arguments will assume 10,11,12
Public Methods void begin( ) initialise object
int get_route( ); returns button pressed**
Required Hardware: Arduino NANO wired to two Parallel to Serial 74HC165 convertors with toggle buttons as inputs
Required libraries Coal_Basin_LCD and/or Coal_Basin_LEDs used for debugging.
Possible Audience. Microcontroller designers requiring to read more input buttons/switches LEDs than available inputs. 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.
** 0 == NULL (no button pressed), 1 == SILOS, 2 ==WHARF1, 4==WHARF2, 8==SIDING1, 16==SIDING2, 32==COAL1, 64==COAL2, 128==YARD, 256==STATION, 512==TURNTABLE, 1024==BASIN, 2048==EASTSIDE1, 4096==EASTSIDE2.
---------------------------------------------
This project is part of a larger project to control the points in a model train layout known as a Coal_Basin. The control panel for the project contains push button switches that are used to select the various routes labled "A" through "M". For example, selecting route "G" the points at "1", "2" , "4", "6" and "7" would be set to allow a train to transition to the coal siding.The objective of this project is to read what buttton is pressed.**
A photo of one control panel is shown. The original design had provision for 3 but only two were actually used.
The control panel. The black push buttons will request the chosen route. The microcontroller will then acknowledge by driving the appropriate LEDs.Â
** Controlling the points will be the subject of a different project.
The circuit is shown below. Since there are more buttons to read than spare pins on the Arduino Nano two parallel to serial chips are used**
The actual implementation consists of two PCBs.
1. The main or controller board consisting of the Nano interfaced with the 1602 LCD (See project LCD_1602_Class). Not shown is a board that will drive the points and interface with track signals. These will be the subject of future projects. This board will be mounted hidden underneath the model railway layout.
2. The control panel that consists of the input switches and parallel to serial chips illustrated above. This is part of this project. Also on the control panel will be two serial to parallel chips (74HC164) that drive LEDs. These were part of an earlier project (Cola_Basin_LEDs) and will be used to verify the switch operation. The control panel will be mounted in easy access of the (human) train controllers/drivers/engineers.
** A display, Coal_Basin_LCD using a 1602 shown on this circuit diagram was developed in an earlier project. This will be used to assist debugging and verifying the operation.
-------------------------------------------------------------------------
1 .In the Arduino IDE select File --> New and create a file/project.
2 .Save_As Coal_Basin_Buttons. By saving the Arduino IDE creates a new folder/directory in the work folder called Coal_Basin_Buttons and saves the file in that folder with extension *.ino
3. Use the inverted triangle in the top right of the Arduino IDE to create two more files labeled Coal_Basin_Buttons.cpp and Coal_Basin_Buttons.h. Note extensions must be included
---------------------------------------------------------------------------
For starters the code in the *.ino file will be:
//Coal_Basin_Buttons.ino //test buttons/
#include <LCD_1602.h>
#include <Coal_Basin_LEDs.h>
Coal_Basin_LCD LCD;
Coal_Basin_LEDs dsp_route;
void setup() {
dsp_route.begin( );
LCD.begin( );
LCD.Dsp_route("Basin Controller","Testing Buttons");
}
void loop() {
}
At this point the program just anticipates that an object/instance of the class LCD_1602 AND Coal_Basin_LEDs** will be useful for debugging so these are included and a default message over two lines of the LCD will be displayed.
** See projects LCD_1602, Coal_Basin_LEDs
----------------------------------------------------------------------------
The Coal_Basin_Button header file should include 3 public methods:
(i) The constructor that will include as parameters how the buttons are wired**.
(ii) The begin( ) method that performs any initialisation, and
(iii) The get_route( ) method that reads the buttons
//Coal_Basin_Buttons.h Header file
#ifndef COAL_BASIN_BUTTOS_H
#define COAL_BASIN_BUTTON_H
#include "Arduino.h"
class Coal_Basin_Buttons {
public:
Coal_Basin_Buttons (int load, int cklin, int serin );
void begin( );
unsigned int get_route( );
};
#endif
** In the actual prototype board there is provision for 3 wirings, pins 4, 5 & 6, pins 7,8&9 or pins 10,11,12. Pins 4,5&6 are actually used.
----------------------------------------------------
At this point the implementation file is not populated.
//Coal_Basin_Buttons.cpp implementation file
#include "Coal_Basin_Buttons.h"
Coal_Basin_Buttons:: Coal_Basin_Buttons(int load, int clkin, int serin )
{ }
void Coal_Basin_Buttons::begin( ){
}
unsigned int Coal_Basin_Buttons::get_route( ){
}
---------------------------------
An instance or object, route1 of the class Coal_Basin_Buttons is created. This has parameters 4,5,6 corresponding to the load, clkin and sein pins as wired to the Nano microcontroller.**
//Coal_Basin_Buttons.ino //test buttons/
#include "Coal_Basin_Buttons.h"
Coal_Basin_Buttons route1(10,11,12);
#include <Coal_Basin_LCD.h>
Coal_Basin_LCD LCD;
void setup() {
route1.begin( );
LCD.begin( );
LCD.Dsp_route("Basin Controller","Testing Buttons");
}
void loop() {
unsigned int route_request = route1.get_route( );
}
The application code calls two methods, begin( ) and get_route( ) from the route1 object.
In addition, an object LCD of the class Coal_Basin_LCD is created. This object is used to display messages to the LCD. LCD is part of the test or *.ino code. It is not part of the class Coal_Basin_Buttons.
** The PCB layout has provision for 3 control panels hence the use of route1. If all three panels were used three objects of the class Coal_Basin_Buttons would be created. ie route1(4,5,6), route2(7,8,9) and route3(10,11,12)..
-------------------------------------------------------
Typically, the first step is to assign the constructor parameters to private variables**. This will require adding the line
private: int _load; int _clkin; int _serin;
to the Coal_Basin_Buttons class definition in the header file.
In the implementation file the constructor becomes:
Coal_Basin_Buttons:: Coal_Basin_Buttons(int load, int clkin, int serin )
{
_load = load; _clkin=clkin; _serin = serin;
}
** There are other possibilities for the constructor. For example, without parameters the implementation file could define the wiring.
Coal_Basin_Buttons:: Coal_Basin_Buttons( )
{
_load = 10; _clkin=11; _serin = 12;
}
If both options are included in the class
class Coal_Basin_Buttons {
public:
Coal_Basin_Buttons (int load, int clkin, int serin );
Coal_Basin_Buttons ( );
the compiler would select the appropriate constructor depending upon how the object is created. That is
Coal_Basin_Buttons route1(10,11,12); or
Coal_Basin_Buttons route1;
---------------------------------
To initialise the Nano micro-controller to read the parallel to serial convertor the begin( ) function becomes:
void Coal_Basin_Buttons::begin( ){
pinMode(_load,OUTPUT);
pinMode(_clkin,OUTPUT);
digitalWrite(_load,HIGH);
digitalWrite(_clkin,LOW);
}
This begin( ) is associated with the object route1 so is called from the setup() function:**
void setup() {
route1.begin( );
LCD.begin( );
LCD.Dsp_route("Basin Controller","Testing Buttons");
delay(2000);
}
**A delay is added to setup( ) to allow the message to be physically observed
------------------------------------------------
The numerical value of the pressed button is read by
(i) generating an active low pulse on the _load pin.
(ii) reading the pin _serin input into a variable buttons.
(iii) clocking the _clkin signal 16 times to build up the complete result
unsigned int Coal_Basin_Buttons::get_route( ){
unsigned int buttons =0;
digitalWrite(_load,LOW); //pulse to read buttons into Par 2 Ser cct
digitalWrite(_load,HIGH);
for (int pp = 0; pp<16 ; pp++) //read 16 bits
{
buttons *= 2; //left shift
buttons += digitalRead(_serin); //add bit
digitalWrite(_clkin,HIGH); //clock Par to ser
digitalWrite(_clkin,LOW);
}
// return buttons;
return 2053; //for testing without buttons
}
get_route( ) will return a numeric value depending upon the button that is pressed.
---------------------
In order to minimize the number of vias/crossovers in the PCB the buttons have been wired to the parallel to serial chips in what appears like a random fashion: For example, reading the I_SIDING2 button the result will be in the least significant bit. That is value 1. However, the expected value for SIDING2 is 16 (bit 4 is set) so a conversion function is required.
The code becomes**:
unsigned int Coal_Basin_Buttons::_convert(unsigned int buttons){
if (buttons&I_SILOS) return(SILOS); //1
if (buttons&I_WHARF1) return(WHARF1); //2
if (buttons&I_WHARF2) return(WHARF2); //4
if (buttons&I_SIDING1) return(SIDING1); //8
if (buttons&I_SIDING2) return(SIDING2); //16
if (buttons&I_COAL1) return(COAL1); //32
if (buttons&I_COAL2) return(COAL2); //64
if (buttons&I_STATION) return(STATION); //256
if (buttons&I_TURNTABLE) return(TURNTABLE); //512
if (buttons&I_YARD) return(YARD); //128
if (buttons&I_BASIN) return(BASIN); //1024
if (buttons&I_EASTSIDE1) return(EASTSIDE1); //2048
if (buttons&I_EASTSIDE2) return(EASTSIDE2); //4096
return NULL; //unused buttons
}
The get_route( ) method now returns the result of the _convert(buttons) routine rather than just buttons.
unsigned int Coal_Basin_Buttons::get_route( ){
................
return (Coal_Basin_Buttons::_convert(buttons));
}
** The definitions for I_SILOS, SILOS etc are added to the header file and _convert( ) included in the private area of the class definition. Line1 reads "if 128 return 1", line 2 reads "if 64 return 2" etc
-----------------------------------------------------------
For testing the code will continually read the keys and if a new key is pressed display the result on the LCD and/or LEDs depending upon which is available.
Testing should verify that the conversion function is operational and pressing a key activates the corresponding LED and gives the correct result on the LCD (If used). That is 1==SILOS, 2 == WHARF1, 4 == WHARF2, 8 == SIDING1 etc
To create the library Coal_Basin_Buttons:
1. Pack the two files, Coal_Basin_Buttons.cpp and Coal_Basin_Buttons.h into a *.zip file. Zipping will give the default label Coal_Basin_Buttons.zip
2. In the Arduino IDE use the sequence sketch -> Include Library -> Add *.zip Library and select Coal_Basin_Buttons.zip
3. In the Arduino work directory there will now be a sub-directory "libraries" and a further subdirectory "Coal_Basin_Buttons" that contains the *.cpp and *.h files.
4. To include the Coal_Basin_Buttons library in any project include the line #include <Coal_Basin_Buttons.h> **
5. As per this project, Coal_Basin_Buttons route(4,5,6); will create an object of the class Coal_Basin_Buttons. ++
** 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.