//DCC_NCE_Bus.h Header File
#ifndef DCC_NCE_BUS_H#define DCC_NCE_BUS_H#include "Arduino.h"#define TRACE 0#define SIM_ACCESS 0#include <SoftwareSerial.h> //Included SoftwareSerial Libraryenum NCE_STATE {START,BROADCAST,ITS_ME,OTHER};//#define ITS_ME_ping 0x85;//in order to create a link between the library and the client code need to define a typetypedef void (*ClockHandler)(uint8_t Hours, uint8_t Minutes); //, char Mode1, char Mode2); 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); typedef void (*LCDHandler)(uint8_t fn, uint8_t op);#define RS485_Tx_En 13 //use inbuilt LED#define TX digitalWrite(RS485_Tx_En, HIGH)#define RX digitalWrite(RS485_Tx_En, LOW)class DCC_NCE_Bus { public: DCC_NCE_Bus (uint8_t my_address ); void begin( ); void handle_NCE_Byte( ); //To transfer the clock result from the library to the client need a public routine that client can call to setup link void setClockHandler(ClockHandler funcPtr); void setMessHandler(MessHandler funcPtr); void setLCDHandler(LCDHandler funcPTR); void setAccessory(int acc,uint8_t dir); uint8_t accessFree(); private: uint8_t _my_address; uint8_t _BusBuffer[10]; uint8_t _BusPtr; void handle_me( ); uint8_t _BusSize; NCE_STATE BUS_STATE; // void handle_broadcast( ); void handle_message( ); void its_me_start( ); void build_message( ); void handleLCDcode( );//Need the private links that will do the actual transfers ClockHandler ClockHandlerFn; MessHandler MessHandlerFn; LCDHandler LCDHandlerFn; int _acc; uint8_t _dir; uint8_t _afree; uint8_t _avalid; uint8_t _accArray[7]; uint8_t _accPt; uint8_t _bufflen; int _mess_count;};#endif--------------------------------------------------------------
//DCC_NCE_Bus.cpp Implementation File
#include "DCC_NCE_Bus.h"SoftwareSerial RS485(12,11); //Rx TxDCC_NCE_Bus :: DCC_NCE_Bus ( uint8_t my_address) { _my_address = 0x80|my_address; }; uint8_t rxByte;void DCC_NCE_Bus::begin ( ){ BUS_STATE = START; RS485.begin(9600); //The following two lines are necessary for a RS485 board with a separate Tx enable pin pinMode(RS485_Tx_En, OUTPUT); digitalWrite(RS485_Tx_En, LOW); //receiver int count = 0; _mess_count = 0; while (!RS485.available( )) {if (count<50) //do not saturate screen with dots. {Serial.print("."); count++;}} Serial.println("\nRS485 Available"); _afree = 1; //allow 1 request _avalid = 0; //but no valid request until requested}uint8_t DCC_NCE_Bus:: accessFree(){ return _afree;}/* * Initially "_afree" will be 1 so an external request can load a new request * when the new request is loaded "_availd" will be set saying request can be printed */void DCC_NCE_Bus:: setAccessory(int acc,uint8_t dir){ _afree = 0; //no more requests until done _acc =acc; _dir = dir; //for example need to send 4E,53,54,55,40, then direction //then set _afree = 1; _accArray[0] = 0x4E; _accArray[1] = 0x50+_acc/1000; _acc %= 1000; _accArray[2] = 0x50+_acc/100; _acc %= 100; _accArray[3] = 0x50+_acc/10; _acc %= 10; _accArray[4] = 0x50+_acc; _accArray[5] = 0x40; _accArray[6] = 0x50+_dir; _accPt = 0;/* Serial.println("Set Accessory"); for (int i = 0; i<=6 ; i++) { Serial.print(_accArray[i],HEX); Serial.print(' '); } Serial.println();*/ _avalid = 1; //valid data to print}//LCD code is for when a LCD display is included//will display usual NCE information.//some of the commands from the NCE Twin will be used as acknowledgement//that a command/request has been received by the NCE Twin//For example whe request an accessory it appears the Enable Cursor(?) command//is sent - this will be used as an ack of the accessory request 0x45.void DCC_NCE_Bus::handleLCDcode( ){ /* for debugging * if (_BusBuffer[0] == 0xC8){ Serial.print("Move cursor "); Serial.println(_BusBuffer[1],HEX); } else if (_BusBuffer[0] == 0xCF){ Serial.println("Enable cursor "); } else if (_BusBuffer[0] == 0xCE) { Serial.println("Disable cursor "); } else if (_BusBuffer[0] == 0xCA) { Serial.print("Print Character "); Serial.println(_BusBuffer[1],HEX); } else {Serial.print(_BusBuffer[0],HEX); Serial.println(" Unknown.");} */ if (LCDHandlerFn) //if client has not set up link this code is skipped LCDHandlerFn(_BusBuffer[0],_BusBuffer[1]&0x3F); } //Need to implement the method that will point the library routine to the required client operation//Client will call this as part of initialisationvoid DCC_NCE_Bus::setClockHandler(ClockHandler clientPtr) { ClockHandlerFn = clientPtr; } void DCC_NCE_Bus::setMessHandler(MessHandler clientPtr) { MessHandlerFn = clientPtr; } void DCC_NCE_Bus::setLCDHandler(LCDHandler clientPtr) { LCDHandlerFn = clientPtr; } //NCE does not use straight ASCII code - this routine adjustsuint8_t NCE_ASCII(uint8_t rr){ if (rr & 0x20) return (rr & 0x3F); else return (rr & 0x7F);} //-----------------------------------------------------------//have a complete message in the buffer void DCC_NCE_Bus::handle_message( ){ //This will be part of the Broadcast State and part of ItsMe state. // Serial.print('m'); // Serial.print(_BusBuffer[0],HEX); if ((_BusBuffer[0] >= 0xC0) && (_BusBuffer[0] <= 0xC7)) //this is a message to the controller screen 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]); int hrs = (((_BusBuffer[2])&0xF)*10) +((_BusBuffer[3]&0xF)); int mins = (((_BusBuffer[5]&0xF)*10) + (_BusBuffer[6]&0xF)); // Only displaying data like the NCE Cabs so following display (hours - minutes)not required // if (ClockHandlerFn) //if client has not set up link this code is skipped // ClockHandlerFn(hrs, mins); //while(1); } else if ((_BusBuffer[0] >= 0xC8) && (_BusBuffer[0] <= 0xCF)) handleLCDcode(); else if (_BusBuffer[0] == 0xD4){ // Serial.print("Green Light "); //debugging } else { #if TRACE Serial.print(_BusBuffer[0],HEX); Serial.print(" Handle-unknown-mess "); // while(1); #endif }}//-------------------------void DCC_NCE_Bus:: build_message( ){ if (_BusPtr == 0) { if ((rxByte >= 0xC0)&& (rxByte <= 0xC7)) _bufflen=9; else if ((rxByte ==0xCE)||(rxByte==0xCF)) _bufflen= 1; else _bufflen = 2; } if (_BusPtr && (_BusBuffer[0]>= 0xC0)&& (_BusBuffer[0]<= 0xC7)) //an ASCII string _BusBuffer[_BusPtr] = NCE_ASCII(rxByte); //modified ASCII -don't modify first char else _BusBuffer[_BusPtr] = rxByte; //save new byte _BusPtr++; if ((_BusPtr == _bufflen)) { //message complete handle_message( ); _BusPtr = 0; // if (_BusBuffer[0] !=0xD4) while(1); //Trace BC_Message } }//-----------------------------------------------------/*void DCC_NCE_Bus::handle_broadcast( ){ //Serial.print(" Handle BC "); build_message( ); //while(1); }*///-----------------------------------//digital writes required if using RS485 device with separate Tx enable.//It appears a delay of 200 microseconds is required. 100 will not workvoid SendByteRS485(uint8_t mess){ //digitalWrite(RS485_Tx_En,HIGH); delayMicroseconds(100); RS485.write(mess);// digitalWrite(RS485_Tx_En,LOW);}void Send2Bytes485(uint8_t m1,uint8_t m2) { //digitalWrite(RS485_Tx_En,HIGH); delayMicroseconds(200); RS485.write(m1); RS485.write(m2); // digitalWrite(RS485_Tx_En,LOW);}//----------------------- void DCC_NCE_Bus::handle_me( ){ //should get to here when 85 ping is received. //in an attempt to avoid any timing issues all debug messages are placed after //the code actions so the timing should not change when debug removed. //. //If the NCE Twin issues a D2 command it is asking for the type of terminal //The NCE Twin will only ask this question once. //For a dumb terminal reply immediately with "a" //debug if (rxByte !=0x86) // {Serial.print(rxByte,HEX); Serial.print(' '); } if ((rxByte == 0xD2)&& (_BusPtr ==0)){ SendByteRS485('a'); //a dumb terminal _BusPtr = 0; //clear buffer _BusBuffer[0] = ' '; //Serial.print("'a' sent."); //while(1); } else { build_message( ); // while(1); //handle_me } } //----------------------------------------- void DCC_NCE_Bus::its_me_start( ){ /* * its_me_start occurs when the device is in the OTHER state but recieves a ITS_ME ping. In this example ping 85. * On the first occurance the message 0x7E and 0x7F should be sent. * If nothing is required from the NCE Twin the response should be 0x7D and 0x7F * //This will be the immediate response //This response must fall in a prescribed time window. */ #if SIM_ACCESS #define BASE 300 #define DELTA 100 if (!_mess_count) { //send 7E 7F once Send2Bytes485(0x7E,0x7F); //Serial.print("7E-7F "); } else if (_mess_count == BASE) { Send2Bytes485(0x4E,0x7F); //Serial.println(" -Request Acc- send-4E"); } else if (_mess_count == (BASE+DELTA)) { Send2Bytes485(0x53,0x7F); //Serial.println("Sending-3 "); } else if (_mess_count == (BASE + 2 *DELTA)) { Send2Bytes485(0x54,0x7F); //Serial.println("Sending-4 "); } else if (_mess_count == (BASE + 3 *DELTA)) { Send2Bytes485(0x55,0x7F); //Serial.println("Sending-5 "); } else if (_mess_count == (BASE + 4 *DELTA)) { Send2Bytes485(0x40,0x7F); //Serial.println("Send-ENTER "); } else if (_mess_count == (BASE + 6 *DELTA)) { Send2Bytes485(0x52,0x7F); // Serial.println("Sending-2=turn "); _mess_count = BASE/2; //redo ACCESSARY // while(1); } else { Send2Bytes485(0x7D,0x7F); // Serial.print('.'); }; _mess_count++; #else if (!_mess_count) { //send 7E 7F once Send2Bytes485(0x7E,0x7F); // Serial.println("7E-7F"); _mess_count++; } else if (_avalid) { //code for accessory if (_mess_count >20) { if (_accPt <7) { Send2Bytes485(_accArray[_accPt],0x7F); //Serial.print("Send "); //Serial.print(_accArray[_accPt],HEX); _accPt++; _mess_count = 1; //restart count but not at zero } if (_accPt == 7) { //while(1); //accessory_done _avalid = 0; //accessory handled _afree = 1; //can load another request _accPt =0; } } else { Send2Bytes485(0x7D,0x7F); _mess_count++; } } else { Send2Bytes485(0x7D,0x7F); // Serial.print('.'); } #endif } void DCC_NCE_Bus::handle_NCE_Byte( ) { //initially will be in START state so will skip all bytes (probably pings) until it gets the BROADCAST PING 0x80 //when it will move to the BROADCAST state. //In the BROADCAST state the next byte will either be the start of a broadcast message or //a further ping when it will move to the OTHER state while (RS485.available( )) { rxByte = RS485.read(); #if TRACE if (rxByte == 0x80) Serial.println(); //sync to broadcast if (rxByte < 16) Serial.print('0'); Serial.print(rxByte,HEX); Serial.print(' '); #endif switch (BUS_STATE) { case START : if (rxByte==0x80) { BUS_STATE = BROADCAST; _BusPtr = 0; } break; case BROADCAST : if ((rxByte & 0xf0)!= 0x80) { // Serial.print(" BC-mess "); // if (rxByte != 0xD4) while(1); build_message( ); //should loop through until next ping } else BUS_STATE = OTHER; //does assume my_address != 0x82 break; case OTHER : if (rxByte==0x80) { BUS_STATE = BROADCAST; _BusPtr = 0; } else if ( rxByte == _my_address) { // Serial.print("Its-Me-"); its_me_start( ); //need to tell NCE I'm on the bus. BUS_STATE = ITS_ME; // } break; case ITS_ME : if ((rxByte & 0xf0)!= 0x80) { handle_me( ); } else { BUS_STATE= OTHER; //does assume my_address != 0x8A } break; default : Serial.print("State-Unknown"); } }}-------------------------------------------------
//ino file - to control macro
#include "DCC_NCE_Bus.h"#include <LiquidCrystal.h>LiquidCrystal lcd(8,9,4,5,6,7);uint8_t cursor;DCC_NCE_Bus bus(5);//need the client code to handle the clock - void NCE_CLK(uint8_t Hours, uint8_t Minutes){ #if TRACE Serial.print(" Handle Bus Time "); if (Hours<10) Serial.print('0'); Serial.print(Hours); Serial.print(':'); if (Minutes<10) Serial.print('0'); Serial.print(Minutes); #endif}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 "); if (loc ==0xc0) { //Serial.print("Top row left "); //debugging lcd.setCursor(0,0);} else if (loc ==0xc1) { //Serial.print("Top row right "); lcd.setCursor(8,0);} else if (loc ==0xc2) { //Serial.print("2nd row left "); lcd.setCursor(0,1);} else if (loc ==0xc3){ //Serial.print("2nd row right "); lcd.setCursor(8,1);} /* not used in project * else if (loc ==0xc4) Serial.print("3rd row left "); else if (loc ==0xc5) Serial.print("3rd row right "); else if (loc ==0xc6) Serial.print("Lower row left "); else if (loc ==0xc7) Serial.print("Lower row right "); */ // if (loc != 0xc1) { lcd.print(char(a)); lcd.print(char(b));lcd.print(char(c));lcd.print(char(d));lcd.print(char(e));lcd.print(char(f));lcd.print(char(g));lcd.print(char(h)); Serial.print(char(a)); Serial.print(char(b)); Serial.print(char(c)); Serial.print(char(d)); Serial.print(char(e)); Serial.print(char(f)); Serial.print(char(g)); Serial.println(char(h)); } }void NCE_LCD(uint8_t cmd, uint8_t op){ if (cmd == 0xc8){ lcd.setCursor(op&0xF,1); cursor=op&0xF; //save as cursor postion could be changed with time update for example. } else if (cmd == 0xCA) { lcd.setCursor(cursor++,1); lcd.print(char(op&0x7F)); }}void setup(){ Serial.begin(115200); Serial.println("\n-NCE Twin Probe-"); bus.begin( ); lcd.begin(16,2); lcd.setCursor(0,0); lcd.print("NCE Bus."); //need to set up the link between library and code for client to handle clock bus.setClockHandler(&NCE_CLK); bus.setMessHandler(&NCE_Message); bus.setLCDHandler(&NCE_LCD);}void loop() { uint8_t key; static uint8_t dir; bus.handle_NCE_Byte( ); /* //If there is an input then if accessory free set a new accessory request if (analogRead(A0)<800) { if (bus.accessFree( )){ //method acts as debounce filter Serial.println("\nAcc Req"); // bus.setAccessory(1234,2); //accessory 1234 turn/reverse dir +=1; if (dir>2) dir = 1; bus.setAccessory(61,dir); //acc60 alternates between straight then turn } }*/ dir = 1; static uint8_t macro; static uint8_t slow; if (analogRead(A0)<800) macro = 1; if (macro) { if (bus.accessFree( )){ // Serial.println("\n Macro"); if (macro==1) { Serial.print(" M1 "); bus.setAccessory(61,dir); macro=2; slow =0; } else if (macro==2) { if ((slow++)>100){ Serial.print(" M2 "); bus.setAccessory(60,dir); macro=3; slow =0; } } else if (macro==3) { if ((slow++)>100){ Serial.print(" M3 "); bus.setAccessory(59,dir); macro=0; // slow =0; } } } } }