Source code for project.
Header file: "DCC_Debug.h"
Include File <DCC_Probe.h>
Constructor: DCC_Debug - no arguments
Public Methods: begin( ); - initialises D2 as interrupt
DCC_Debug( ); - displays captured DCC commands
DCC_Dump(); - part of DCC_Probe code that is visible to the DCC_Probe class.
-----------------------------------
Summary: This project enhance the DCC_Probe class/library to display the commands embedded in the signals on the tracks of a Model Railway DCC (Digital Command Control) system** .
Possible Audience: Model railway enthusiasts who are familiar with C++ programming in the Arduino environment and interested in extracting the DCC commands..
Required components: The prototype used an Arduino NANO (A UNO could be used). To extract the signals an Optocoupler is used. The power for the NANO comes from the USB during program development but the power was taken from the tracks for the final testing.
Keywords: C++ Class, C++ Library, DCC track protocol, NANO, Interrupts, Interrupt Service Routines, Interrupt modes, attachInterrupt( ), digitalPinToInterrupt( ).
Software: The project enhances the DCC_Probe library/class and demonstrates inheritance in C++..
** Digital Command Control (DCC) is a standard for a system to operate model railways digitally
-------------------------------
This project builds on the DCC_Probe project:
The DCC_Probe project has captured the information embedded in the DCC signal on the model railway tracks and placed this in an array. The DCC_Dump( ) method wrote the data to the serial monitor. A typical snapshot is shown:
In this example the instructions are 4 byte instructions to set the speed for locos 3 and 4. The objective of this project is to do the translation of the raw data and printout the actual instructions in a readable format.
This program will build on the DCC_Probe class to display the results in an English like format rather than bytes. That is the DCC_Debug class will inherit the DCC_Probe class.
For starters the code will be formatted to inherit the DCC_Probe class.
The starting code will be:
The client/application or test code will be:
An instance of the DCC_Debug class (DCC) will be created.
The setup() code will include a call to the class begin() method while for testing as an intermediate step the program loop will call the method DCC_Dump( ). Once the implementation and header code are developed the DCC_Dump( ) method will give the same results as obtained with the class DCC_Probe.
-----------------------------------------
The header file will be:
//DCC_Debug.h Header FileNote the class DCC_Debug inherits the DCC_Probe Class.
At this point only one method is necessary. ie begin();
---------------------------------
The implementation code will be:
///DCC_Debug.cpp Implementation File#include "DCC_DeBug.h"DCC_Debug::DCC_Debug( ): DCC_Probe( ) { };As illustrated the DCC_Debug::begin( ) method will call the begin method for the DCC_Probe class
All public methods in the DCC_Probe class are available to the new class DCC_Debug. Hence the method DCC_Dump( ) is known to the new class and is used in the test/client/application code.
The program at this point should give the same results as the test code for the DCC_Probe class.
Notes: (i) The DCC_Probe class has read the data on the model railway tracks and placed the result in an array "the_bytes[ ]".
(ii) "the_bytes[0]" gives the size of the array.
(iii) message_done is set when there is new data available. The routine DCC_Dump() includes the method clear_mesage_done to clear message_done when the data has been used.
(iv) When the_bytes[1] == 255 it is an idle command. This happens all the time so would saturate the output display hence is ignored by the DCC_Dump( ) method.
----------------------------------------------------------------------------------------------------------
The objective of this program is to generate the function or method DCC_Decode( ) that will be part of the program loop. DCC_Decode( ) will print out all the activity on the DCC bus in an English like format.
The client code will become:
.....
void loop( ) {
DCC.DCC_Decode( );
}
DCC_Decode() must be declared as a public method as part of the class definition. That is
........
class DCC_Debug : DCC_Probe {
For starters in the implementation file DCC_Decode( ) will make a copy of the data to be analysed. That is
void DCC_Debug::DCC_Decode (){
if (meaurement_done{
_len = get_result[0];
_opcode = get_result[1];
_operand1 = get_result(2);
_operand2 = get_result(3);
_operand3 = get_result(4);
clear_message_done( );
if (_operand1 == 255) return; //ignore idle commands
//following actions
}
]
The 4 variables to save the data must be declared as part of the class definition. That is
class DCC_Dedebug : DCC_Probe {
public : DCC_Debug( ); void begin( ); void Decode( ); private: byte _len,_opcode,operand1,operand2. operand3 ; };The next step is to parse the DCC messages.
------------------------------
The various DCC messages are described in the html page DCC_Track_Protocols
Byte 1 (_opcode) maybe parsed as follows:
0: Special Commands. Code will not be implemented.
1-127: Locos 1-127. The complete command requires two more bytes plus an error byte.
128-191: Accessory Command. The lower 6 bits (0-63) give part of accessory address. The complete command is continued in _operand2
192-231: Multifunction decoders with 14 bit addresses (The code will need to read additional bytes)
232-254: Reserved for future use.
255: Idle command. Byte 2 = 0000,0000. Byte 3 or Error byte = 1111,1111. Runs continuously to ensure power to the track. This project will ignore.
Starting code might be:
This code was tested with an NCE DCC Twin that had commands for locos 3 and 4 only.
For locos op2 is parsed as follows:
0-127: Loco speed given in next byte.
128- 191: Loco functions.
192-255: Other.
For speed operand2 is as follows:
0-127 speed in reverse (0 to maximum)
128-255 forward speed - 128 is zero, 255-128 is speed.
The complete code for loco speed becomes:
A possible output with locos 3 and 4 active was:**
** The results were tested a NCE DCC Twin that had provision for two locos at address 3 and 4 only. The second byte had a value of 63. At this point I do not understand the details. Using the DCC_Debug program with a different controller gave a value of 69.
The DCC protocol for long locos will require 4 parameters:
(i) The first byte gives the long loco command along with the high part of the loco address
(ii) Byte 2 gives the low part of the address
(iii) Byte 3 gives the speed, and
(iv) The checksum.
The declaration for long_loco will be
void long_loco(int cmd1, int cmd2, int cmd3, int cmd4 ){
To call the long_loco method the decode( ) method must be enhanced: That is
void DCC_Debug :: DCC_Decode( ){
if (message_done( )) {
............
if (_opcode == 255)return; //ignore idle commands
if (_opcode == 0) Serial.print("Special:");
else if (_opcode <132) do_locos(_opcode,_operand1,_operand2); else if (_opcode <192) do_access(_opcode,_operand1 ); else if (_opcode <= 199) long_loco(_opcode,_operand1,_operand2,_operand3);
else if ((_opcode <= 241)&&(_opcode >= 234))long_loco(_opcode,_operand1,_operand2,_operand3);
else Serial.print("Opcode not implemented");
........................................
}
Possible code for the long_loco( ) method will be
void long_loco(int cmd1, int cmd2, int cmd3, int cmd4 ){
//combine cmd1 and cmd2 to obtain long address
int addr = cmd1&0x3F;
addr *= 256;
addr += cmd2; //long address
//use lower 6 bits of cmd3 to obtain speed
if (cmd3 <64) Serial.print("NA");
else if ((cmd3&0xF) ==1 ){
Serial.print("EMERGENCY"); }
else { cmd3 = cmd3&0x3F;
if (cmd3 >= 64) cmd3 = 128 + (cmd3&0x1F); //move sign
do_locos(addr,cmd3,cmd4);
}
}
For locos the information in byte 2 is parsed as follows
0xxx,xxxx. Part of Loco Speed - See earlier section
10xx,xxxx 128-191 Controls Functions.
100x,xxxx: 128-159: Bit 5 controls light. Bits 3 .. 0 control functions F4..F1, "1" for on - "0" for off
1010,xxxx: 160 - 175: Bits 3 .. 0 control functions F8..F5, "1" for on - "0" for off
1011,xxxx: 176-191: Bits 3 .. 0 control functions F12 ..F9, "1" for on - "0" for off
11xx,xxxx 192-255 Feature Expansion Instruction
This may be implemented with the following code:
void do_fns(int fn){ if (fn >= 192) Serial.print(" Feature Expansion Instruction"); else { if (fn<160){ Serial.print(" Light "); if (fn & 16) Serial.print("On"); else Serial.print("Off"); } Serial.print(" Function(s) "); if (fn >= 176) { if (fn&1) Serial.print("9 "); if (fn&2) Serial.print("10 "); if (fn&4) Serial.print("11 "); if (fn&8) Serial.print("12 "); } else if (fn >= 160) { if (fn&1) Serial.print("5 "); if (fn&2) Serial.print("6 "); if (fn&4) Serial.print("7 "); if (fn&8) Serial.print("8 "); }else { if (fn&1) Serial.print("1 "); if (fn&2) Serial.print("2 "); if (fn&4) Serial.print("3 "); if (fn&8) Serial.print("4 "); } if (fn&15) Serial.print("Active"); else { if (fn<160) Serial.print("1-4 "); else if (fn <176) Serial.print("5-8 "); else Serial.print("9-12 "); Serial.print("All inactive"); } } }Note. fn < 160 controls both functions 1 thru 4 as well as the lights. Bit 4 is tested first to give preference to the lights in the display. (see below)
An example of the monitor display is shown below. In this example the lights going from "on" to "off" has been captured while functions 3 and 4 are active.
In the second snip function 1 is active and function 2 which is a toggle switch is active for 4 cycles.
The previous sections have looked at loco commands. ie commands/messages where the first byte is in the range 0 to 127 decimal (0 to 7F Hex). This section will look at commands for accessories that are in the range 128 to 191. With accessory commands the printout will be of the form "accessory X on/off" where the on/off is determined by the last bit of the second byte. The address X is a combination of bits in the first and second bytes. This is illustrated below:
The code becomes:
void do_access(int op1, int op2){ unsigned int accessory; Serial.print("Access: "); accessory = ((~op2)&(0x70))<<2 ; accessory += op1 &0x3F; accessory -= 1; accessory *= 4; accessory += (op2&0x06)>>1; accessory += 1; Serial.print(accessory); if (op2&1) Serial.print(" on"); else Serial.print(" off");}My controller does not include an accessory control, so it was necessary to simulate accessory inputs (to be verified later using controllers at my model railway club). A possible solution to test the do_access( ) function is to add the code:++
The resulting display will be:
++ This effectively changes all the commands, speed and function to accessory 1411.
-------------------------------------
This page has provided code to debug commands or messages from the NCE DCC Twin Controller plus accessory commands. The page will be expanded when a bigger controller is available to permit more extensive testing.