Model Railroad DCC Command Station V2




Update 30 Jan 2019: Modified sketch to eliminate timing issue when sending repeat data packets. Please download latest sketch above.

Update 27 Jan 2019: Now includes option of momentary F2 F3 functions for horn sounds, if not required simply comment out the new lines of code in void loop()

if (f2a[locoAdr] == 1){ // momentary action on F2
fun2 = 0; }
if (f3a[locoAdr] == 1){ // momentary action on F3
fun3 = 0; }

Update 16th Aug 2018:
Now includes a CV1 write function to programme loco address numbers. This is a software modification only with no change to hardware. See updated Arduino Nano sketch. To access this CV1 write facility, hold down button 'A' on membrane keypad while turning on power. Ensure that only the loco to be addressed is on the track. Choose loco number 1-19 from the keypad then press 'A' again to programme new value for CV1. See step 3 for more details.

This is version 2 of my original DCC Command Station Instructable.

I have learned a number of things from the original experience about the Arduino Nano, the h-bridge LMD18200t and the TFT LCD display.

1. The analog pins on the Nano may be used for serial communications e.g. A3/A4 for Rx/Tx. The original used D0 and D1 making serial monitoring impossible. I have also used A2 and A5 as a digital outputs to control the on/off of the bridge output in event of over-current. Note: A6 and A7 cannot be used as digital output pins on the Nano.

2. The LMD18200t current sense pins on the actual IC package, do not work well and in some cases not at all. Therefore, I have added a 0.1 ohm 5 watt resistor to monitor current through the bridge. This is sensitive to within 50mA which is ok for the purpose of detecting overloads/shorts.

3. The TFT LCD used here requires a voltage divider from 5v to 3v on the input pins, 1k0 and 1k5 ohm values work fine. Check your TFT requirements and if it can take 5v inputs, the 1k5 resistors are not required. The TFT is slow in response compared to response of turning the speed pot and recording on the display. The time lag gives incorrect values. I have included a special void in the Arduino sketch to transmit speed data only and change one line on the display instead of refreshing the whole display.

4. The DCC command signal timing has been improved by switching state of the output using binary register coding instead of digitalWrite(D10, High) e.g.
DDRB = B00000100; // register B for digital pin 8 to 13, pin 10 as an OUTPUT
PORTB = B00000100; // using register control to speed up switching speed pin 10 switched HIGH (DIR)
PORTB = B00000000; // pin 10 switched LOW (DIR)

5. The input keys on the membrane key pad are selected in void get_key() I have improved the response time taken in the void by using 'case' and 'return' rather than 'if' statements.

6. The NMRA standard requires a continuous flow of DCC data every 30 ms. This has been added to the sketch for speed and direction on all running locos (not for function controls which are only sent when keypad is pressed).

7. The DCC Command packet for 128 speed steps is 4 bytes long whereas the function Commands are 3 bytes long. I have included a means to assemble 4 or 3 byte packets by altering the length of the Message array:
void amend_len4 (struct Message & x) { x.len = 4; z = 4; }
void amend_len3 (struct Message & x) { x.len = 3; z = 3; }

8. On review of the power requirements of the Nano and LCD and the H-bridge voltage (12v to 14v) I have used a 9v regulator to supply the Vin pins of the Nanos and the input of the 3.3v regulator for the LCD. This gives optimum power dissipation on the regulators. The Arduino Nanos should not be relied upon to supply enough current to the LCD display from their 3v outputs.

9. A PCB design from Fritzing and manufacturing in China has enabled me to provide bare PCB's of the circuit included here. If you are interested purchasing one, please search e-bay for 'DCC Command Station PCB' (now available from August 2018)

Step 1: Components:

All available on eBay:

1 off PCB eBay link
4x4 Matrix Array 16 Key Membrane Switch Keypad £1. eBay

2.2 inch 240x320 Serial SPI TFT LCD Display Module £5 eBay
Nano V3.0 For Arduino with CH340G 5V 16M compatible ATmega328P 2 x £3.50 = £7 eBay

Motor Driver Module LMD18200T for Arduino £5. eBay
*Voltage Regulators: 3v3 (UA78M33C) £2.53 eBay
*Voltage Regulators: 9v (7809) in TO220 packages £1 eBay

3v3 zener diode £1 for 10 eBay
1N4004 rectifier diode £1 for 10 eBay
1N5817 Schottky diode £1 for 50 eBay
Connectors, wire, vero board, resistors, potentiometer: approx £3
PCB (available on e-bay from Aug 2018) £5.50
Box / Enclosure

*Note: Voltage regulators will run at 23 degrees C above ambient and may require a require heatsink, depending on air circulation within enclosure. If Vsupply is 14v , then a temp rise of 36 degrees C above ambient on 9v Regulator will probably need a heat sink.

Step 2: Arduino Nano With Keypad

Make serial communications between the Nanos using Analog pins A3/A4:

#include "SoftwareSerial" // download this library
SoftwareSerial dcc(A3,A4); // RX TX

The 4x4 membrane keypad is configured in the sketch as follows:

#include "Keypad.h" // download this library

const byte ROWS = 4; //four rows const byte COLS = 4; //four columns
char keys[ROWS][COLS] = { {'1','2','3', 'A'}, {'4','5','6', 'B'}, {'7','8','9', 'C'}, {'*','0','#', 'D'} };
byte rowPins[ROWS] = {2,3,4,5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6,7,8,11}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
char key = keypad.getKey();

void SetupTimer2()
This void starts the clock which is amended by the DCC binary code to provide a DCC signal.
Each of 4 byte of loco/speed commands:
byte 1 = loco number
byte 2 = speed steps
byte 3 = speed value / direction / front light on/off
byte 4 = checksum (error)

For function commands, 3 bytes:
byte 1 = loco number
byte 2 = function (F1 to F8) on/off
byte 3 = checksum (error)

A current sense resistor 0.1 ohm 2 watt monitors the current through the h-bridge.
The LMD18200t module used has a max current of 3 Amps
I have set the working limit to 2.6 Amps in the sketch:
void current(){
sensorValue = analogRead(AN_CURRENT);
// Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V) = 4.9mv per division
// 0.1 ohm resistor on current sense gives 200mv at 2 Amps or, 100mv per Amp
// 100/4.9 = 20 divisions per Amp or 20 divisions per 1000mA or 1 division = 50mA
A = 50 * sensorValue ; // mA }

Within void setup() :
Amax = 2600; // max current

Within the void read_loco() :
if (A >= Amax){ // current limit
Atrig = 1;
changed_l = true;
return changed_l; }

The system can control 1 - 19 engines, direction, lights, 8 functions, emergency stop and auto current limit.
The max current of 2.6 amps makes it suitable for all scales including G-scale (garden trains). The mains power supply and electronics are suitable for indoor use only unless you can make it all weather proof. I have the command station in the summer house with rail connecting wires running out through the wall to the track.

Step 3: CV1 Loco Address

Holding the 'A' key down while turning on power, will provide access to the CV1 write feature.

This is achieved in the Arduino Nano sketch through a sequence of reset, page preset and write packets as specified in NMRA S-9.2.3

//CV1 coding for Address only mode

void cv1_prog(){
4 x (preamble (16 x '1') plus reset_packet) // 3 or more required
6 x (preamble (16 x '1') plus page_preset_packet) // 5 or more required
7 x (preamble (16 x '1') plus reset_packet) // 6 or more required
6 x (long preamble (24 x '1') plus cv1_write_packet() // 5 or more required
11 x (long preamble (24 x '1') plus cv1_write_packet() // 10 or more required

void reset_packet()
Creates 'preamble' of 2 bytes of (8 x '1') followed by 3 bytes of '0'

void page_preset_packet()
Creates 'preamble' of 2 bytes of (8 x '1') followed by 3 bytes 01111101 00000001 01111100

void cv1_write_packet()
Creates 'long preamble' of 3 bytes of (8 x '1') followed by 3 bytes 0111C000 0DDDDDDD EEEEEEEE
C=1 write address used here (C=0 verify address)
Value of DDDDDDD is written into CV1
The decoder must be powered off after each CV1 write and switched on again prior to another CV1 write.
This is done by switching the pins on the h-bridge:
Turn ON : PWM = High : Brake = Low. Turn OFF : PWM = Low : Brake = HIGH
To sense the smaller currents, Analog Internal Ref is used during CV1 write
1.1v ref gives 1.08 mv per division, current Acv = 10.8 X sensor value using 0.1 ohm sense resistor
If Acv > 15mA then a valid CV1 write is achieved.

Step 4: Arduino Nano With TFT LCD Display

The Nano used to control the TFT display is configured as follows:

#include "SoftwareSerial.h"
SoftwareSerial dcc(A3,A4); // RX TX

#include "Adafruit_GFX_AS.h" // Core graphics library
#include "Adafruit_ILI9341_AS.h" // Hardware-specific library
#include "SPI.h"

// hardware SPI pins for Nano
#define _sck 13 // D13
#define _mosi 11 //D11
#define _cs 10 //D10
#define _dc_rs 9 //D9
#define _reset 7 //D7

Adafruit_ILI9341_AS tft = Adafruit_ILI9341_AS(_cs, _dc_rs, _reset);
A valid command received from the Nano with keypad must start with 'A', end with '\n' and have 18 ',' characters.
Each valid command is decoded and the result displayed.

2 People Made This Project!


  • Pie Contest

    Pie Contest
  • Trash to Treasure

    Trash to Treasure
  • Pocket Sized Contest

    Pocket Sized Contest

14 Discussions


2 months ago

Hello! Today got Your Board of this device. Thank you very much!!! Tell me where you can get the required libraries for sketsa display?The editor generates a compilation error. The editor does not know which folder to use the subroutine from. SoftwareSerial.h Maybe I do not understand? Thank you! Dmitri.


2 months ago

Bill, would be nice if some functions could be defined as "momentary" rather than on and off. Function F1 , F2 and F3 are often associated with sound and have to be turned off before turning on to give repeat sound again.

7 replies

Reply 2 months ago

Thanks for your feed back. I am delighted to read of your success with the project. The Arduino sketch may be modified to make the sound functions momentary if you wish. The turnout code should work 'as is' for accessory decoders (tested on Decoder Pro SPROG) (or you may want to build my project on this subject where 8 pairs of outputs can be switched).
The accessory address in the Command Station sketch is :
void amend_tun1 (struct Message & x)
{[0] = 0x81; // accessory decoder 0x80 & address 1
void amend_tun2 (struct Message & x)
{[0] = 0x82; // accessory decoder 0x80 & address 2
etc for address 3 and 4
If your turnout decoder is not reacting to this code, you may have to send multiple packets. Try adding a repeat line here with a small delay in between.
In void read_turnout();
if (changed_t){ // if turnout keys pressed

Try this for momentary F1 : (not tested by me yet)
In void read_function()
if (f1a[locoAdr] != fun1){
f1a[locoAdr] = fun1; changed_f = true;
if (f1a[locoAdr] == 0) {data |= 0; // f1 off
if (f1a[locoAdr] == 1) {data |= 0x01; // f1 on
if (f1a[locoAdr] != fun1){
f1a[locoAdr] = fun1; changed_f = true;
if (f1a[locoAdr] == 0) {data |= 0x01; // f1 on
if (f1a[locoAdr] == 1) {data |= 0x01; // f1 on


Reply 2 months ago

I'm not sure if my reply was sent or not, so I send again, I apologise if you get 2 replies.
Thanks for your helpful reply, I have enjoyed working on this project.
I have made your suggested change to F1 for momentary action but could not see any difference on the display, it still changed from 0 to 1 with key press then back to 0 with next key press. I did not check with actual loco ( it is late) so maybe the actual command was momentary but it did not look like it on the TFT display.
I have also been trying to understand the turnout addressing code in the sketch. Using a DCC sniffer I found that your code gives NMRA style addresses when using accessory (turnout) commands of address # 1,2,3 and 4 with ports 0,1,2,and 3 giving a total of 16 commands. Unfortunately my accessory decoder required addresses 11 and 12 with ports 1,2,3 and 4.
I thought I had found quick solution as turnout values in your sketch of 0x81, 82,83 and 84 gave direct address relationship to 1 ,2,3,4..So I thought changing to 0x91.92,93,94 might give me addresses 11,12,13,14. However to my surprise it gave addresses starting at 16 not 11. Obviously there is something in the code calculation beyond my present understanding that does not give a direct relationship. So I have set 0x88 and 89 to give me addresses 8 and 9. I am trying to modify my accessory decoder code to see 8 and 9 instead of the present 11 and 12 that it is working with.
Slowly I make progress and soon will hopefully replace the Laptop/RocRail that I presently use.
Once again thanks for your help and encouragement.


Reply 2 months ago

Email msg not received with jl...broadband address.
Please try again with 'billc at john-lewis (all small case with hyphen) dot com'


Reply 2 months ago

Alan, the display will still show 0 and 1 but should work as momentary switches for sound on F1 etc.
Not sure what is going on with the turnout address on your decoder. I shall have a look and get back if I find an answer.


Reply 2 months ago

Alan, thanks for your order on e-bay I shall post pub today.
If you want to contact me directly, my email address is billc@john-lewis 'dot' com


Reply 2 months ago

Hi Alan, I am not sure either about messages sent from this site! I thought I replied last night but no sign of it here today.
The momentary F1 will still appear as 1 or0 on the display but should work ok. Maybe you should only change the sound functions F2 and F3 though as F1 is usually engine sound on/off which should not be momentary.
The turnout address for your decoder should be 0x8b, 0x8c, 0x8d, 0x8e to provide decimal addresses 11, 12, 13, 14


Reply 2 months ago

Thank you for your helpful response, I have enjoyed working on this project and seeing it bear fruit. I will try your suggestion regarding momentary functions.
At the moment I am working on understanding the code regarding accessory (turnout) addressing. Having looked with a DCC sniffer, your code as presented issues turnout addresses in MNRA format with addresses of 1 thro' 4, and ports 0,1,2,and 3 giving a total of 16 commands.
Unfortunately my accessory decoder was looking for addresses 11 and 12 with ports 1,2,3 and 4 giving a total of 8 commands (turnouts). I thought I had found an instant solution by changing the 0x81, 82, 83, and 84 turnout values in your sketch to 0x91,92,93 and 94. But to my surprise the addresses produced jumped to 16,17,18 and 19, not my expected 11,12,13 and 14. With experimentation I found that using values in the range 0x81 to 89 gave a direct relationship to addresses 1 to 9 however going to 0x90 and above the addresses jumped and started at 16 increasing to 25 at 0x99. Obviously something in the code calculation that at present is beyond my understanding causes the apparent jump in addresses generated and I am not able to get my desired addresses of 11 and 12 with this simple change to the code. I am now using 0x88 and 0x89 to give me turnout addresses 8 and 9 and am modifying my accessory decoder software to try to make it see these addresses instead of the original 11 and 12 required.
Once again thanks for an interesting project, slowly I get there and replace the Laptop/RocRail.


3 months ago on Step 2


After getting yout PCB I am trying to construct the DCC Command Station, but I
have encountered some discrepancies between resistors values in schematic and
PCB. So I do not know the true value of the resistors.

Please could you help me?

Thank you in advance.




Reply 7 months ago

Simon, I have repeated the DCC signal to ensure continuous operation when locos have intermittent track connections. When CV11 is set to zero the loco does not respond to time outs. When set, it is usually in terms of seconds, I believe, rather than within 30mS. Thanks for your input.