Source Code and Trace of Program Activity.
This project develops a library to interface with the NCE Bus of a DCC Model Train System.
Libraries to include: <NCE_Bus_Probe.h> this project
Constructors bus(5); //project at address 5
LiquidCrystal lcd(8,9,4,5,6,7);
The NCE DCC Twin is a simple controller that will control two locos at address 3 and 4 on a DCC track.
It also has an RS485 interface where additional controllers may be added to enhance the capability of the NCE Twin. This project develops an interface that will take external inputs and send them to the NCE Twin over the RS485 or NCE_Bus. The interface will also capture messages from the NCE Twin and put them in array that may be used as required.
This project is concerned only with the interface. Generating the input requests and taking the required action with received data are the objectives for future projects. The projects contribution is to provide the interfaces to simplify the input and output operations. This is illustrated in the following figure.
A flow chart of the activity on the NCE bus is shown below. The Twin will ping the addresses 80 through 8A in turn checking for a response from the addressed device. At address 80, the broadcast address which should be listened to by all devices the NCE Twin will give out messages every 100 or so cycles.
If any device responds to a ping it should respond (always) telling the NCE there is a device on the bus. After the response the NCE Twin send infrequent messages.
The firmware for this project will first loop through all the activity on the NCE Bus.
The second step will be to capture the broadcast messages. (The messages for the active device will be of a similar format so separate development is not required.)
The final step will be to include code to respond to the ping for the device of interest. In this example it is assumed to be device 85. (85Hex)
Further projects will actually use the NCE messages. This project will only collect the raw data.
For this project the response to an address 85 ping will tell the NCE "I'm here" but will not request any action. The code to do this will be part of a later project.
The project uses a TTL to RS485 breakout board to perform the interface between the Arduino and the NCE Bus. On one side of the board are the two RS485 signals "A" and "B" or "+" and "-" (**) plus a ground. On the other side there are 4 or 5 pins that include the power (Vcc) and Ground plus 2/3 pins for the TTL signals that are wired directly to the Arduino I/O pins. These include the Receive and Transmit pins. If present the third pin will select the board function to either receive or transmit. For boards without this third pin the direction is selected using the transmit signal as illustrated in the circuit below.
In the circuit below the heart of the breakout board is the SN65HVD3082 TTL to RS485 converter (See also MAX485) where the direction is controlled by the Data Enable (DE) and the (NOT Receive Enable Pins). As shown, these are typically wired together, and it the 3-wire interface exposed to the Arduino to control the direction. With the 2-wire interface the enable signals are derived from the transmit signal. Normally TX0 will idle high but when the Arduino wishes to transmit it pulls the TX0 line low which forces the DE active and the RE inactive the device going into transmit mode. To hold the device in transmit mode a 1000pF capacitor is used.
** Examples on the internet indicate that the manufacturers definitions are not consistent, and it may be necessary to swap the wiring to A/B.
Photo of the TTL to RS485 interface mounted on an Arduino based prototyping board. On the left the cable to the NCE Bus while on the right are pins that underneath are wire wrapped to the prototyping board socket that in turn is wired to the Arduino. The centre chip is the RS485 to TTL converter while the bigger chip on the right is a hex buffer (**).
** This surface mount chip had a dry joint and the link between its output pin and the RO pin had to be resoldered.
-----------------------------------------------------------------------------------------------
Once the TTL to RS485 interface board is powered the received signal at RO may be observed with a mixed signal oscilloscope (MSO). The left-hand trace below shows a typical result (**). By using the timing features of the MSO the signal can be determined to be 9600 baud and the time between each byte approximately 3.88 mS (See cursors). Further the data may be deduced/read as 0x80, 0x82, 0x83 through 0x8A (^^). That is the NCE Bus first pings address 80, the broadcast address, it misses device 1, but then pings/addresses devices "2" through "10" before repeating.
Every so often the broadcast address (0x80) is followed by a string of data. With much patience this can be captured. (!!) For later information this appears to be inserted at what would normally be the 0x82 ping and consists of 9 bytes followed by the 0x82 ping.
** To synchronise the trace the trigger has been set for a negative pulse of greater than 800uS which synchronises on the 0x80 or broadcast ping address.
^^ To extract this data it will be necessary to set the cursors to one-bit and scroll through each byte to conclude from the timing that the most significant bit must be an "8". Scrolling through the waveforms will show the sequence repeating after the 0x8A.
!! In this example the resultant trace was captured after 84 attempts.
The above trace has captured the signal on the NCE bus. Note it goes in the sequence 0x80, 0x82, 0x83 through 0x8A and then repeats. Each byte is 9600 baud and the time between each byte is 3.88mS,
Occasionally following the 0x80 Broadcast ping/command a burst of activity is inserted before the 0x82 ping. It will be the job of the project firmware to capture this string and take the appropriate action,
Note. In this example the scan was repeated 84 times before this stretched sequence was captured.
Normally the recommended procedure for developing a library to interface with the NCE Bus is to collect all the bus specifications and design the project using these specifications. In this example, except for observing the waveforms given above I have assumed nothing about the bus protocol and developed a program to probe the bus to determine the protocol. This is a learning project. This technique has the disadvantage that there might be a number of back tracks required and the end result might not be as good as knowing exactly what is required before you start.
The first step is to generate 3 files DCC_NCE_Bus with extensions ino, cpp and h
1. Create a new project with the label DCC_NCE_Bus. This will present a new file NCE_Bus_Probe with the extension .ino.
2. Using the inverted triangle in the Arduino IDE to create two new tabs. Give these the labels NCE_Bus_Probe.cpp and NCE_Bus_Probe.h
-------------------------------------------------------------
It is possible to start generating the files at either end. For example, in the *ino file there needs to be a function/method to read the bytes on the NCE bus. That is a method of the form handleByte(). Alternatively, starting with the *.h file there needs to be a class NCE_Bus_Probe that ultimately performs all the underlining detailed work and provides functions available to the client (*.ino) code.
Possible client or *ino code will be: (The code at this point the code will initialise the serial port and write a message to the monitor. The serial port will not be part of the actual NCE bus interface but will be used for debugging). The setup( ) method/function will also initialise the new class NCE_Bus_Probe.
Since the basic operation of the project will be to wait until a ping of my address is received and then do something a method bus.wait_for_myaddress() is required,
The possible header file including the definition of the NCE_Bus_Probe might be:]
Notes
The header file has created a class that has four methods: NCE_Bus_Probe (the constructor), begin( ), get_NCE_Byte( ) and wait_for_my_address.
The target address (ie the address used by this project) is defined as part of the constructor.
The interface to the NCE bus will require the SoftwareSerial library that is included in the header file. The standard serial port cannot be used as it is reserved for loading code and debugging messages. There are definitions which are associated with the software serial interface.
The methods get_NCE_Byte ( ) and wait_for_myaddress() are available to clients or part of the application program. In the example the main program loops waiting for my address. (0x85 in this example)
Possible implementation code will be as shown. There is the constructor and the begin operations associated with any class. In addition there is the wait_for_myaddress that is available for the client. The wait_for_myaddress( ) will use the get_NCE_Byte( ) to actually read the bytes on the NCE bus.
Notes:
The constructor specifies the address of the object. In the example the value "5" is passed as the argument with the constructor adding 0x80 to obtain an address of 0x85. That is NCE_Bus_Probe :: NCE_Bus_Probe ( uint8_t my_address) {_my_address = 0x80+my_address; };
The header file has included the SoftwareSerial library, and the implementation code creates one instance/object RS485 of that class. The SoftwareSerial library The object RS485 is attached to Arduino pins 12 and 11.
The begin method initialises the RS485 object and sets its baud rate to 9600.
The given code is for a RS485 interface with 3 pins Transmit (Tx), Receive (Rx) and Enable (defined as RS485_Tx_En). The Tx and Rx pins were defined when the object was created while RS485_Tx_EN is defined as pin 13 in the header file and set to a LOW output. (Receiver enabled) If a RS485 board with internally generated enables is used then any lines referring to RS485_Tx_En are not required and may be omitted.
To handle the NCE data a method wait_for_myaddress() is included in the NCE_Bus_Probe class. This calls the get_NCE_Byte( ) which performs the nuts and bolts operation of reading the data on the RS485 or NCE bus. This returns the data byte to the wait_for_myaddress() that loops until my_address (0x85) is received. To display the bus activity Serial.print( ) statements are included. For clarity of the display a new line is created at the broadcast address (0x80) and spaces are added between each byte.
Notes:
The left hand trace below shows the start up code with a number of dots.
In general the captured data will be the pings to addresses 0x80 through 0x8A (less address 0x81.) as per the two traces below.
Occasionally in the Broadcast state there will be a message. This message could be 2 bytes starting with D4 (telling all controllers to turn on a green light) or 9 bytes starting with 0xC1 - C1 tells LCD to display message on top row to right. The following 8 bytes are the message. In the example " 12:28PM"
Looking ahead a following step in the project will be to extract the data from these longer lines and take the appropriate actions. That is if an LCD is available actually display 12:28PM!
To display the received data a conditional compile is included. In the header file "#define TRACE 1" will direct the compiler to include all the conditional print out lines that will be used to understand/verify the program operation. Once the code is operational the define may be set to 0 or the comment lines removed. Normally Serial.print( ) functions will take time that may be limited. For this project it was found that there was no need to eliminate the Serial.print or debug operations
The monitor display following reset. After the messages the code has captured the data 0x80, 0x82, 0x83 .... 0x8A. For clarity the program adds a new line prior to printing each 0x80, the broadcast ping. Note the trace has also by chance captured a short broadcast message 0xD4 0x0A
By trial and error a trace was obtained that captured the broadcast message 0xC1, 0xE0, 0xF1, 0xF2, 0xFa, 0xF2, 0xF8, 0xD0,0xCD. This will be the NCE Fast Clock. The line is completed with the pings of address 0x82,0x83 etc.
To handle the broadcast messages the wait_for_myaddress( ) routine will test for the broadcast address (0x80). On receipt the method _HandleBC() will be called.
The handleBC() method will test the received bytes. Normally the broadcast ping will be followed by a ping of the next address. However if the next address is not a ping the method _handleBCmess( ) will be called.
}
The _handleBCmessage uses the first byte to determine the length of the message and places the received bytes in a buffer. At this point if the first byte is in the range 0xC0 through 0xC7 it will be a 9 byte message otherwise it will be 2 bytes. Once the code for _my_address there will be single byte message also.
With the 9 byte message except for the first byte the remainder are characters. Unfortunately the NCE uses modified ASCII so a method to modify the received characters to true ASCII is required.
The routines have included the liberal use of Serial.prints. The trace of a two byte message is shown below. When the broadcast ping (0x80) is found an earlier routine _handleBC will print the message "HandleBC". The routine get_NCE_Byte will then print the next byte received in this case "D4". Sine 0xD4 is not a ping address the routine handleBCmess will print "Mess" along with the actual byte and the length of the message. The get_NCE_Byte function will display the next byte (0x0A).
At this point the _handle_message() method looks like a set of print statements. In the above trace it has printed "Message" and the contents of the message "D4" and "0A".
However, for client convenience the routine will also generate a callback function MessHandlerFn( ) that is discussed in the next section.
void NCE_Bus_Probe::_handle_message(uint8_t len) { #if TRACE Serial.print("\nMessage "); for (int i= 0; i <len ; i++) {Serial.print(_BusBuffer[i],HEX); Serial.print(" ");} if (len==9) //only do ASCII for (int i= 0; i <len ; i++) {Serial.print(char(_BusBuffer[i])); Serial.print(" ");} //while(1); //freeze display to have a look #endif //This will be part of the Broadcast State and part of ItsMe state. if (MessHandlerFn) //if client has not set up link this code is skipped MessHandlerFn(_BusBuffer[0],_BusBuffer[1],_BusBuffer[2],_BusBuffer[3],_BusBuffer[4],_BusBuffer[5],_BusBuffer[6], _BusBuffer[7], _BusBuffer[8]); }From the document NCE Cab bus protocol-2006.pdf possible messages are:
0xc7 print next 8 chars to 4th line right of LCD (addr = D8h)
0xc6 print next 8 chars to 4th line left of LCD (addr = D0h)
0xc5 print next 8 chars to 3rd line right of LCD (addr= 98h)
0xc4 print next 8 chars to 3rd line left of LCD (addr = 90h)
0xc3 print next 8 chars to 2nd line right of LCD (addr = C8h)
0xc2 print next 8 chars to 2nd line left of LCD (addr = C0h)
0xc1 print next 8 chars to 1st line right of LCD (addr = 88h) Note: this is where the clock prints
0xc0 print next 8 chars to 1st line left LCD (addr = 80h)
Depending upon the final objectives of the project the handle_message function may or may not be required. For this project it is assumed a 16x2 LCD is available so the handle_message( ) will generate messages for the LCD.
The question then becomes where are the LCD routines located? One option is to make them part of the DCC_NCE class that each time a new message is generated the LCD is updated.
Another option is to have the LCD routines as part of the client code (ie the *.ino). Here there are two options: (i) the client code can poll testing when a new message is available, or (ii) the DCC_NCE object can inform the client code when new data is available. This will effectively be an interrupt service or callback routine. This is the chosen option. As well as the code in both the client and implementation programs the links between the two will need to be programmed. This will require code as part of the class, there will be some initialisation code and then code when the LCD is to be updated.
Working from the client/application/test code (*.ino) a possible method that displays the message to a monitor is:There are a number of steps.
1.In the header file define a type
typedef void (*MessHandler)(uint8_t loc, uint8_t a, uint8_t b,uint8_t c,uint8_t d,uint8_t e, uint8_t f, uint8_t g, uint8_t h);
where the type definition includes nine arguments that match those of the target or client method.
2. To transfer the clock result from the library to the client the class needs a public routine that the client can call to setup the link and a private method to perform the actual transfer operation. That is
class DCC_NCE_Bus {
public:
DCC_NCE_Bus ( );
void setMessHandler(MessHandler funcPtr);
private:
MessHandler MessHandlerFn;
3.To set up the link the setMessHandler( ) method is called from the client set up function where the address of the MessHandler( ) method is passed. That is in the *ino setup code include
void MessHandlerFn(_BusBuffer[0],_BusBuffer[1],_BusBuffer[2],_BusBuffer[3],_BusBuffer[4],_BusBuffer[5],_BusBuffer[6], _BusBuffer[7], _BusBuffer[8]);
void setup( ){
....
bus.setMessHandler(&MessHandler);
}
4. The setMessHandler implementation needs to be part of the implementation file or *.cpp file. That is
void DCC_NCE_Bus::setMessHandler(MessHandler clientPtr)
{
MessHandlerFn = clientPtr;
}
5. To transfer the results the following code is added to the handle_message( ) method.
void DCC_NCE_Bus::handle_message( ){
if ((_BusBuffer[0] >= 0xC0) && (_BusBuffer[0] <= 0xC7)) //this is a message to the controller screen where C0 - C7 give position on screen
{ if (MessHandlerFn) //if client has not set up link this code is skipped
MessHandlerFn(_BusBuffer[0],_BusBuffer[1],_BusBuffer[2],_BusBuffer[3],_BusBuffer[4],_BusBuffer[5],_BusBuffer[6], _BusBuffer[7], _BusBuffer[8]);
Note if the pointer MessHandlerFn does not exist the function exits.
In summary when a new message is received the library routine handle_message( ) will vector the program to the client method NCE_Message that will take the required actions (display message on LCD).
For this example any received messages will be displayed on the monitor.
Depending upon the final objectives of the project the handle_message function may or may not be required. For this project it is assumed a 16x2 LCD is available so the handle_message( ) will generate messages for the LCD.
Further the question then becomes where are the LCD routines located? One option is to make them part of the DCC_NCE class that each time a new message is generated the LCD is updated.
Another option is to have the LCD routines as part of the client code (ie the *.ino). Here there are two options: (i) the client code can poll testing when a new message is available, or (ii) the DCC_NCE object can inform the client code when new data is available. This will effectively be an interrupt service or callback routine. This is the chosen option. As well as the code in both the client and implementation programs the links between the two will need to be programmed. This will require code as part of the class, there will be some initialisation code and then code when the LCD is to be updated.
Possible code in the DCC_NCE class will be
void DCC_NCE_Bus::handle_message ( ) {
if (MessHandlerFn) //if client has not set up link this code is skipped
MessHandlerFn(_BusBuffer[0],_BusBuffer[1],_BusBuffer[2],_BusBuffer[3],_BusBuffer[4],_BusBuffer[5],_BusBuffer[6], _BusBuffer[7], _BusBuffer[8]);
}
where MessHandlerFn includes the 9 parameters for each of the elements of the data buffer (_BusBuffer[ ]) and will match the code NCE_Message that is written is code written as part of the client code.
void NCE_Message(uint8_t loc, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h)
{
// Serial.print("\nNCE Message ");
................
//display operations
}
}
A brief explanation of the trace is as follows. When the broadcast ping (0x80) is found an earlier routine _handleBC will print the message "HandleBC". The routine get_NCE_Byte will then print the next byte received in this case "C1". Since 0xC1 is not a ping address the routine handleBCmess will print "Mess" along with the actual byte and the length of the message. The get_NCE_Byte function will display the remaining bytes.
The method handle_message prints out the actual message followed by the message in ASCII form. Finally the client code NCE Message will print the third line that includes the actual data. In this case the NCE clock 12:44AM
In the last line the program is waiting for myaddress.
The design will be looping waiting for myaddress. In this example 0x85. Upon receipt of 0x85 it needs to respond to the NCE host.
Initially there will be some initialisation required.
Following this the target (this project) must respond to the NCE with either a "I'm still here" or a command/request that will be the subject of a separate project.
The NCE may or may not send a message. These routines will be similar to the Broadcast messages.
The question will be where the dividing line between the class and the client responsibility is. To assist the client all the initialisation will be performed by the class along with the "I am here" response. Minor changes will be made to the Broadcast messages to accommodate the myaddress operations.
The following code should be added to the setup method to handle the initialisation operations.
Serial.print("\nMy Address on reset "); Send2Bytes485(0x7E,0x7F); Serial.print("Send 7E-7F "); uint8_t rxByte = get_NCE_Byte( ); if (rxByte == 0xD2) {SendByteRS485('a'); //dumb terminal Serial.print(" Response 'a' == Dumb terminal. ");} else _handleBCmess(rxByte); }Explanation of code:
When myaddress is first received the target must respond with the bytes 0x7E and 0x7F - request refresh - there has been no speed change.
The NCE Twin will then send a message. If it a single byte 0xD2 the NCE is asking what is the type of terminal. The response should be 'a' indicating a dumb terminal.
If the NCE Twin response is not D2 this will imply it already knows it's a dumb terminal and is up to the next step of sending a message. This message may be handled using the existing method _handleBCmessage(rxByte).**
** This situation could occur during code development - the code is run once when the NCE Twin determines its a dumb terminal. When the code is reloaded it does not ask for the type of terminal but continues to the next message.
The trace of the bus activity is given blow below and shows the myaddress response (7E/&F), followed by the NCE Twin D2 request and the 'a' from myaddress indicating a dumb terminal. That completes the handshake - the NCE Twin is now pinging each address in turn:
There are 2 methods presented to send either one or two bytes. It was found by experiment that a 200 uSec delay is required.
//digital writes required if using RS485 device with separate Tx enable.The client code should be modified as shown:
A timing trace of the received and transmitted data is shown below.
In detail the top channel shows a 85 ping is received from the NCE Twin, the project responds/transmits with the two bytes and then the NCE Twin sends the message/command/request which by observation may be seen to be 0xD2.
** The NCE TWIN expects two byte commands. In this example 7E requests that the data be displayed and 7F that the loco speed is zero.
The following trace shows the start up activity that includes 'a' the dumb terminal response. The next time myaddress occurs 7D/7F is sent and the NCE Twin responds with the message "NCE " that would be written to column '0' row '0' of the display. Not shown but later messages would build up the complete display.
ie DCC TWIN V1.12
For interest this project was interfaced to a more complex NCE Controller that was communicating with controllers (CABs) at address 11 (0x8B) and 15 (0x8F). A trace at start up is shown below, (The trace was captured at a different version of the program so most debugging messages are not displayed.
Some snippets gathered from the trace.
The controller does not always ping every address or every address in sequence.
The device at address 0x8B is continually sending the message 7D/7F - no key pressed and speed zero.
Device 0x8F is also sending 7D/7F although there was an operator on that controller and other messages were obtained.
LOCO: 5025
FWD: 000 ------
PRESS <EXPN> FOR
A NORMAL DISPLAY
With a 2 line display only the first two lines can be displayed.
Sometime later the time is broadcast. This will go to the top row right of all the devices on the bus. For device 0x85 beside the LOCO: 5025**
** Why Loco 5025 is sent to device 0x85 is unknown. It was found later that there was another controller with address 0x85 and it was controlling loco 5025
To make the NCE_Bus_Probe available for other users one method is to:
Combine the two files NCE_Bus_Probe.cpp and NCE_BusProbe.h into a *.zip file. In Windows the operation will be <share> <make *.zip file>.
IN the Arduino IDE (Any project) select <sketch> <Include Library> <add *.zip> and select NCE_Bus_Probe.zip.
Future projects that will use this library/class are:
NCE_Bus_LCD. Where the results will be displayed on an LCD as per the original NCE controllers
NCE_Bus_Point. Where the library will be used to control points (accessories) on a layout.
NCE_Bus_Macros: Where the library will send messages to the NCE Bus to control macros.
NCE_Bus_Loco: Where the library will be used to control the speed of a loco.