This page will develop an Arduino based system that will interface with the NCE Bus. The project will
(i) Read and display the activity on the NCE Bus (ie RS485 bus)
(ii) Capture and display any broadcast messages from the NCE Controller
(iii) Interact with the controller by placing messages such as "I'm here" on the bus
(iv) Capture and display messages from the controller for this project
(v) Send requests to the controller. The example given will be requesting a macro that will control several points.
This page will present a brief description and the code. This discussion is expanded in the page NCE_Bus_Interface_Development that presents the project development step by step.
The NCE bus is used to combine several NCE devices. For example, a controller plus individual controllers. Shown as a "Host Controller" and "Slave Controllers" in the following diagram. The Slave Controllers are also known as CABs or Throttles. This project develops a slave controller.
The photo shows a RS485 card mounted on an Arduino UNO. The wires to the left are Black Ground, Red B- and Green A+. Yellow is not connected. Some documentation on the www suggest that the Green and Red should be reversed.
A trace of the NCE Bus activity is shown below. This was captured on an NCE Twin DCC Controller, (For clarity in the display spaces are placed between each byte and a new line for the broadcast address (0x80).
In general the NCE controller will ping each address in turn and if that device requires service or even just to let the controller that it is on the bus it must acknowledge.
Note at the broadcast address (0x80) the controller sometimes issues a global message (Highlighted) This project will extract these messages.
Basically the class definition will consist of
(i) a constructor. This has one parameter that will be the bus address of this project. I have chosen 0x86.
(ii) a begin( ) method. In this case there are two. With no parameter the program runs continuously but with a parameter only parts of the program will execute. This is for debugging and education purposes. (The above listing/trace can be duplicated by compiling the code using begin(1).).
(iii) a method do_my_address() . Basically this will read the data and exits when my_address (0x86) occurs
One additional inclusion in the header file is to include the SoftwareSerial library for the interface to the NCE_Bus (RS485)
In addition to implementing the methods declared above the implementation code (*cpp) includes creating an object of the SoftwareSerial class and defining how it is wired. That is
SoftwareSerial RS485(12,11); //Rx Tx
At this point the client code is quite simple. It initializes the class and loops forever. This example creates an object bus of the class NCE_Bus_Interface associated with the address 6. (The *.cpp code adds 0x80 to make the address =0x86 )
The trace given previously captured the line
80 C1 E0 F0 F4 FA F5 F7 C1 CD 82 83 84 85 86 87 88 89 8A
where the string C1 E0 F0 F4 FA F5 F7 C1 CD is a broadcast message.
If the messages are used they need to be made available to the client. Steps begin(2) through begin(6).
Step 2 includes creating a state machine for the project while steps 3 through 5 investigate the messages and pace the results in a buffer. The only complication is that NCE use their own form of ASCII so the results needed to be translated to normal ASCII.
In the message above the first byte indicates where the message starts on the display of an NCE controller. C1 indicates the top right. The remaining bytes are the message. In this case space, "0", "4" ":" "5","7","A","M" that is 04:57AM
Step 6 will transfer the message to the client using a Callback or Interrupt.
In order to create a link between the library and the client code need to define a type MessHandler , in the header file. For this project MessHandler has provision for 10 parameters.
typedef void (*MessHandler) (uint8_t , uint8_t , uint8_t, uint8_t , uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
To transfer the message from the NCE_Bus_Interface class/library to the client need a public routine setMessHandler(MessHandler funcPtr) that the client can call to point to the client function that handles the message
class NCE_Bus_Interface {
public: NCE_Bus_Interface (uint8_t my_address );
void setMessHandler(MessHandler funcPtr);
There needs to be a private link of the type MessHandler that will do the actual transfer.
class NCE_Bus_Interface
...
private:
..
MessHandler MessHandlerFn;
The implementation code NCE_Bus_Interface.cpp implements the two methods:
(i) setMessHandler(MessHandler clientPtr) that is used by the client to point the the callback method to display the message, and
(ii) the handle_message() operation that will use MessHandlerFn to transfer the message to the client. This code will be skipped if the client has not set up the link
void NCE_Bus_Interface::setMessHandler(MessHandler clientPtr){
MessHandlerFn = clientPtr;
}
void NCE_Bus_Interface::handle_message( ){
if (MessHandlerFn) //if client has not set up link this code is skipped
{ MessHandlerFn(_ping, _BusBuffer[0],_BusBuffer[1], _BusBuffer[2],_BusBuffer[3],_BusBuffer[4], _BusBuffer[5],_BusBuffer[6], _BusBuffer[7], _BusBuffer[8]);
}
}
To receive the message the client code includes a method NCE_Message that receives all 10 parameters.
void NCE_Message(uint8_t ping, 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)
{
Method will handle the messages as required' The example code prints the results to the monitor. ie Serial.print(char a);
}
To let the class know there is a method ready to receive the messages the setup() routine sets up the link. ie
void setup() {
......
bus.setMessHandler(&NCE_Message);
}
One message captured from the NCE_Bus at this point was C1 E0 F0 F2 FA F1 F4 D0 CD that translate to the message 02:14PM. With the ping address and explanation added this presents on the serial monitor as highlighted.
14:24:48.156 -> 80 81 82 83 8A 8B 7D 7F 8C 91 7D 7F 95 A7 AA 14:24:48.203 -> 80 C1 E0 F0 F2 FA F1 F4 D0 CD 114:24:48.250 -> PingAddr = 80 Mess: 02:14PM14:24:48.250 -> Message transferred to clientFrom step 7 the code is advising the NCE controller that it is present and making requests. This requires writing to the NCE_Bus. That is
void SendByteRS485(uint8_t mess) {
delayMicroseconds(200);
RS485.write(mess);
}
By experiment it was found a delay of 200 microseconds was required after my_address was received and the NCE controller would accept data.
At step 13 the following trace was obtained:
17:18:43.261 -> RS485 Available17:18:43.261 -> 17:18:43.261 -> 80 82 83 84 85 86 '7E' '7F'17:18:43.308 -> DB E0 C0 C0 C0 87 17:18:43.308 -> Response 'a' == Dumb terminal. 17:18:43.308 -> 88 89 8A 17:18:43.308 -> 80 82 83 84 85 86 '7D' '7F' CE 17:18:43.355 -> PingAddr = 86 Mess: CE 4017:18:43.355 -> 87 88 89 8A 17:18:43.355 -> 80 82 83 84 85 86 '7D' '7F' C0 CC CF C3 FA E0 F0 F0 F0 17:18:43.402 -> PingAddr = 86 Mess: LOC: 00017:18:43.402 -> 87 88 89 8A 17:18:43.402 -> 80 82 83 84 85 86 '7D' '7F' C2 C6 D7 C4 FA E0 F0 F0 F0 17:18:43.449 -> PingAddr = 86 Mess: FWD: 00017:18:43.449 -> 87 88 89 8A 17:18:43.449 -> 80 D4 0A 17:18:43.449 -> PingAddr = 80 Mess: D4 0A17:18:43.449 -> 82 83 84 85 86 '7D' '7F' C3 E0 ED ED ED ED ED ED ED 17:18:43.496 -> PingAddr = 86 Mess: -------17:18:43.496 -> 87 88 89 8A 17:18:43.496 -> 80 C1 E0 F0 F9 FA F2 F8 C1 CD 17:18:43.496 -> PingAddr = 80 Mess: 09:28AM17:18:43.496 -> 82 83 84 85 86 '7D' '7F' 87 88 89 8A 17:18:43.543 -> 80 82 83 84 85 86 '7D' '7F' 87 88 89 8A 17:18:43.589 -> 80 82 83 84 85 8In summary
(i) at start up the NCE controller must be told there is a new device on the bus . The device (this project) sends the code 7E/7F saying I am here.
(ii) The NCE controller respond with the byte 0xDB which requests the type of terminal. The new device responds with 'a' a dumb terminal. (It only requests actions)
(iii) On receipt of all subsequent address 86 pins the device responds with the code 7D/7F.
(iii) there are then a number of message including the broadcast message of the time PingAddr = 80 Mess: 09:28AM
(iv) Included there are a number of messages to the new device. (Ping address 86). If this was a NCE CAB it would be initializing the LCD display. The CE/40 is a cursor control while the remainder are the message. (The NCE Controller assumes it is responding to a valid NCE Throttle)
In practice the new device will request actions such as a loco, the loco speed and direction, an accessory or a macro.
This will require setting up a buffer with the required pattern and each time the ping address is received the 7D is replace with a value for the buffer. The only complication appeared to be that the NCE controller could not keep up with receiving a new byte each cycle. Hence there is a delay between sending each byte. The implementation code will contain variables that monitor the buffer status but there will be a public method bool free2send(); to indicate when the buffer is empty
Steps 14 and 15 will handling setting up a macro where the setMacro(uint8_t) method translates the macro number into the byte pattern for the NCE. For macro 171 the pattern 5C F1 F7 F1 40 is sent. 5C for macro, F1, F7, F1 is the macro number 171 and 40 return or exit.
For testing the client code has been enhanced to rotate through requesting macros 173 and 174.