// Created: 22 Feb 25 - Last Edit: 16 Apr 25 // Author: Terry Chamberlain (terry@a-train-systems.co.uk) // Nano is powered at +9VDC from rectified DCC input (nominally 14VAC) via 7809 voltage regulator // Regulated +5VDC from the Nano is used to power the 6N137 optoisolator which then feeds an // .. isolated 5VAC version of the DCC input signal to Nano pin D2 // Nano Analog pins A1 and A3 are used to measure the District currents - all other Analog pins // .. are connected to 0volts (GND) to minimise spurious crosstalk between ADCs #include NmraDcc DCC ; const byte dpb_vern = 0x38; // Software Version = 3.8 const byte adr_prog = 11; // Set Accessory Address (pin D11) const byte en_pout1 = 3; // Power District 1 Enable (pin D3) const byte en_pout2 = 4; // Power District 2 Enable (pin D4) const byte man_rst1 = 5; // Manual Reset Pushbutton 1 (pin D5) const byte alm_out1 = 6; // Alarm Out 1 (Sounder) (pin D6) const byte alm_led1 = 7; // Alarm LEDs 1 - Red (pin D7) const byte man_rst2 = 8; // Manual Reset Pushbutton 2 (pin D8) const byte alm_out2 = 9; // Alarm Out 2 (Sounder) (pin D9) const byte alm_led2 = 10; // Alarm LEDs 2 - Red (pin D10) const byte sens_in1 = 15; // Current Sensor 1 Input (pin A1) const byte sens_in2 = 17; // Current Sensor 2 Input (pin A3) byte in_cmnd = 0; // Serial command in byte ser_prm1 = 0; // Serial data in / out byte ser_prm2 = 0; // Serial data in / out String ser_prmstr = ""; // Serial data in - as string of digits String sub_prmstr = ""; // Serial data in - without ending LF (0x0A) character byte flash = 0; byte prog_actv = 0; byte prog_done = 0; int set_adr = 0; bool debug = false; // To allow all debug messages to be displayed by the Serial Monitor, set debug = true bool mon_cur = false; // Set = true to display live values of current measured by Nano A-D converters // Array with output values from Nano ADCs corresponding to load currents of 0.25A up to 5.0A int trip_slct[] = {60, 103, 147, 190, 233, 276, 320, 363, 406, 449, 492, 536, 579, 622, 665, 709, 752, 795, 838, 881}; int sen1trip; // Power District 1 - Set break current level - approx 1.5A as default int sen2trip; // Power District 2 - Set break current level - approx 1.5A as default int sen1read; // Power District 1 - Current value from Sensor 1 - Nano ADC Pin A1 (15) int sen2read; // Power District 2 - Current value from Sensor 2 - Nano ADC Pin A3 (17) unsigned long tmstart1; // Time when Over-Current detected in Power District 1 unsigned long timenow1; // Time read after Over-Current detected in Power District 1 unsigned long elapsed1; // Duration of Over-Current in Power District 1 unsigned long ovrtime1; // Duration (ms) of Over-Current before breaking in Power District 1 unsigned long trycnct1; // Multiples of 250ms before attempting reconnection in Power District 1 unsigned long tmstart2; // Time when Over-Current detected in Power District 2 unsigned long timenow2; // Time read after Over-Current detected in Power District 2 unsigned long elapsed2; // Duration of Over-Current in Power District 2 unsigned long ovrtime2; // Duration (ms) of Over-Current before breaking in Power District 1 unsigned long trycnct2; // Multiples of 250ms before attempting reconnection in Power District 1 unsigned long tmautosw; // Time when AutoReverser switches - ready to check for overcurrent in active District unsigned long chkautsw; // Duration allowed for over-current detection // .. after AutoReverser switches = 4 x ovrtime1 or 4 x ovrtime2 unsigned long nxtadcop; // Next time to display ADC values in Serial Monitor if mon_cur = true (at 200msec intervals) bool enblout1 = false; // Output Enabled - Power District 1 bool enblout2 = false; // Output Enabled - Power District 2 bool ovrcurr1 = false; // Over-Current Detected - Power District 1 bool ovrcurr2 = false; // Over-Current Detected - Power District 2 bool pwbreak1 = false; // Power Breaker Active - Power District 1 bool pwbreak2 = false; // Power Breaker Active - Power District 2 bool autorct1 = true; // Auto Reconnect Active - Power District 1 bool autorct2 = true; // Auto Reconnect Active - Power District 2 bool killpwr1 = false; // Break via soft command - Power District 1 (Can only resume via soft command or by Manual Reset) bool killpwr2 = false; // Break via soft command - Power District 2 bool autorvsr = false; // AutoReverser Mode not enabled int dpb_addr = 0; int t; // temp int v; // temp int i; byte busy_dcc = 0; // Set = 1 when any DCC command(s) being executed byte arev_act = 0; // Set = 0 when AutoReverser not active, otherwise = 1 or 2 for active Power District typedef struct { int cv_adr; byte cv_val; } cv_pair; byte cv_value; int SET_CV_Address = 31; // This Address is for setting CV'S like a Loco using Ops Mode - for this // .. application it is made the same as the Power Breaker Address in CV 41 & CV42 int Accessory_Address = 1; // This Address is the default Accessory Address - not used for Power Breaker byte CV_DECODER_MASTER_RESET = 120; // This is the CV Address for Full Reset - load a value of 120 // .. to this location and then press the Nano Reset button // .. Return to default CV values can also be achieved by loading any value // .. other than 0xAD (173) to CV50 and then pressing the Nano Reset button byte CV_To_Store_SET_CV_Address = 121; // The address used to change CVs using Ops Mode (set as 31 above) is // .. stored in this location, and in the following CV if greater than 256 byte CV_Accessory_Address = CV_ACCESSORY_DECODER_ADDRESS_LSB; // CV01 - the Board Address byte accadrlo = lowByte(Accessory_Address); byte accadrhi = highByte(Accessory_Address) & 0x07; byte scvadrlo = lowByte(SET_CV_Address); byte scvadrhi = highByte(SET_CV_Address) & 0x3F; cv_pair FactoryDefaultCVs [] = { // These two CVs define the Long Accessory Address //{CV_ACCESSORY_DECODER_ADDRESS_LSB, Accessory_Address&0xFF}, //{CV_ACCESSORY_DECODER_ADDRESS_MSB, (Accessory_Address>>8)&0x07}, {CV_ACCESSORY_DECODER_ADDRESS_LSB, accadrlo}, {CV_ACCESSORY_DECODER_ADDRESS_MSB, accadrhi}, {CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB, 0}, {CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB, 0}, // Accessory Decoder Short Address // {CV_29_CONFIG,CV29_ACCESSORY_DECODER|CV29_OUTPUT_ADDRESS_MODE|CV29_F0_LOCATION}, // Accessory Decoder Long Address {CV_29_CONFIG, CV29_ACCESSORY_DECODER|CV29_OUTPUT_ADDRESS_MODE|CV29_EXT_ADDRESSING | CV29_F0_LOCATION}, {CV_DECODER_MASTER_RESET, 0}, {CV_To_Store_SET_CV_Address, scvadrlo}, // LSB Set CV Address {CV_To_Store_SET_CV_Address+1,scvadrlo}, // MSB Set CV Address {30, 0}, // Not Used {31, 0}, // Not Used {32, 0}, // Not Used {33, 0}, // Not Used {34, 0}, // Not Used {35, 0}, // Not Used {36, 0}, // Not Used {37, 0}, // Not Used {38, 0}, // Not Used {39, 0}, // Not Used {40, 0}, // Not Used {41, 31}, // Power Breaker Address LSB (31) {42, 0}, // Power Breaker Address MSB {43, 6}, // Current Sensor 1 Limit - Index (+1) into trip_slct[] array - set at 1.5A {44, 6}, // Current Sensor 2 Limit - Index (+1) into trip_slct[] array - set at 1.5A {45, 25}, // Allowed duration of overcurrent before breaking Power District 1 (msec) - set at 25ms {46, 25}, // Allowed duration of overcurrent before breaking Power District 2 (msec) - set at 25ms {47, 12}, // Time before attempting reconnection after break Power District 1 (250msec steps) - set at 3sec {48, 12}, // Time before attempting reconnection after break Power District 2 (250msec steps) - set at 3sec {49, 0}, // Use as AutoReverser if = 0x5A {50, 0}, // Reset to default CVs if CV50 not equal to 173 (0xAD = "All Default") {51, 0}, // Not Used {52, 10}, // Manual-Auto Reset 1 - 0x0A (10) = Auto (default) - any other value for Manual {53, 10}, // Manual-Auto Reset 2 - 0x0A (10) = Auto (default) - any other value for Manual {54, 0}, // Output current values to serial monitor if not zero {55, 0}, // Set debug = true if not zero {56, 0}, // Not Used {57, 0}, // Not Used {58, 0}, // Not Used {59, 0}, // Not Used {60, 0}, // Not Used {109, 68}, // "D" - Extended Decoder Version {110, 80}, // "P" {111, 66}, // "B" {112, dpb_vern}, // Software Version }; uint8_t FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(cv_pair); //void notifyCVResetFactoryDefault() //{ // Make FactoryDefaultCVIndex non-zero and equal to number of CV's to be reset // to flag to the loop() function that a reset to Factory Defaults needs to be done // FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(cv_pair); //}; void(* resetFunc) (void) = 0; // Declare reset function at address 0 //********************************************************************************************************************** void setup() { Serial.begin(38400); // Used either to display debug messages via the serial Monitor, or to link to PC COM port // Setup which External Interrupt to use for the DCC input, and the associated Pin (2) - only used for programming CVs DCC.pin(0, 2, 0); // Call the main DCC Init function to enable the DCC Receiver DCC.init( MAN_ID_DIY, dpb_vern, FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER, CV_To_Store_SET_CV_Address); // delay(100); pinMode(adr_prog, INPUT_PULLUP); // Set as Address Programming enable - when connected to GND effectively // .. enables NmraDCC and allows the Address (Accessory and Program on Main) // .. of the DualPowerBreaker to be set by issuing a DCC Accessory command // .. to that address digitalWrite(adr_prog, HIGH); // Probably unnecessary - belt and braces ! // Initialize the control and external indicator digital pins as outputs set LOW pinMode(en_pout1, OUTPUT); digitalWrite(en_pout1, LOW); // Enable Power District 1 output - set Off initially pinMode(en_pout2, OUTPUT); digitalWrite(en_pout2, LOW); // Enable Power District 2 output - set Off initially pinMode(man_rst1, INPUT_PULLUP); // Manual reset District 1 via pushbutton (normally-open) pinMode(alm_out1, OUTPUT); digitalWrite(alm_out1, LOW); // Alarm output (District 1 over-current) to piezo sounder pinMode(alm_led1, OUTPUT); digitalWrite(alm_led1, LOW); // Alarm output (District 1 over-current) to external Red LED pinMode(man_rst2, INPUT_PULLUP); // Manual reset District 2 via pushbutton (normally-open) pinMode(alm_out2, OUTPUT); digitalWrite(alm_out2, LOW); // Alarm output (District 2 over-current) to piezo sounder pinMode(alm_led2, OUTPUT); digitalWrite(alm_led2, LOW); // Alarm output (District 2 over-current) to external Red LED if ((DCC.getCV(CV_DECODER_MASTER_RESET)== CV_DECODER_MASTER_RESET) || (DCC.getCV(50) != 0xAD)) { // Reset all defined CVs to default values if value of CV120 = 120 or CV50 is not equal to 173 (0xAD = "All Default") for (int j=0; j < sizeof(FactoryDefaultCVs)/sizeof(cv_pair); j++ ) DCC.setCV( FactoryDefaultCVs[j].cv_adr, FactoryDefaultCVs[j].cv_val); digitalWrite(alm_out1, HIGH); delay (1000); digitalWrite(alm_out1, LOW); // Flash Alarm Out 1 Red LED when CVs loaded DCC.setCV(50, 0xAD); if (debug) { Serial.print("Reset to Default CVs, CV50 = "); Serial.println(DCC.getCV(50), DEC) ; } } // Check that current Software Version is stored in CV 112 in EEPROM if (dpb_vern != byte (DCC.getCV(112))) { DCC.setCV(112, dpb_vern); // Only save if Software Version has been updated delay(5); } // Set all operating parameters in accordance with values stored in EEPROM cv_value = byte (DCC.getCV(43)); sen1trip = trip_slct[cv_value - 1]; cv_value = byte (DCC.getCV(44)); sen2trip = trip_slct[cv_value - 1]; ovrtime1 = byte (DCC.getCV(45)); ovrtime2 = byte (DCC.getCV(46)); trycnct1 = 250 * DCC.getCV(47); trycnct2 = 250 * DCC.getCV(48); cv_value = byte (DCC.getCV(49)); if (cv_value == 0x5A) { autorvsr = true; // AutoReverser Mode enabled } else { autorvsr = false; // AutoReverser Mode not enabled } dpb_addr = DCC.getCV(41) + (256 * DCC.getCV(42)); t = DCC.getCV(121) + (256 * DCC.getCV(122)); if (t != dpb_addr) { cv_value = byte (DCC.getCV(41)); DCC.setCV(121, cv_value); // Update Program Main Address if Accessory Address has been updated delay(5); cv_value = byte (DCC.getCV(42)); DCC.setCV(122, cv_value); delay(5); } cv_value = byte (DCC.getCV(52)); if ((cv_value == 10) || autorvsr) { autorct1 = true; } else { autorct1 = false; } cv_value = byte (DCC.getCV(53)); if ((cv_value == 10) || autorvsr) { autorct2 = true; } else { autorct2 = false; } cv_value = byte (DCC.getCV(55)); if (cv_value != 0) debug = true; cv_value = byte (DCC.getCV(54)); if (cv_value != 0) { mon_cur = true; nxtadcop = millis(); // Initialise timer for output of ADC current values } tmautosw = 0; // Initialise timer for Auto Reverser switching if (debug) { cv_value = dpb_vern; cv_value = cv_value >> 4; Serial.print("DualPowerBreaker Version = "); Serial.print(cv_value); // Major = MS Nibble Serial.print("."); cv_value = dpb_vern; cv_value = cv_value & 0x0F; Serial.println(cv_value); // Minor = LS Nibble Serial.print("DualPowerBreaker Address = "); Serial.println(dpb_addr, DEC) ; t = DCC.getCV(121) + (256 * DCC.getCV(122)); Serial.print("Program-On-Main Address = "); Serial.println(t, DEC) ; Serial.print("Trip 1 Level = "); Serial.println(sen1trip, DEC) ; Serial.print("Trip 2 Level = "); Serial.println(sen2trip, DEC) ; Serial.print("Trip 1 Time = "); Serial.println(ovrtime1, DEC) ; Serial.print("Trip 2 Time = "); Serial.println(ovrtime2, DEC) ; Serial.print("Reconnect 1 Delay = "); Serial.println(trycnct1, DEC) ; Serial.print("Reconnect 2 Delay = "); Serial.println(trycnct2, DEC) ; if (!autorvsr) { if (autorct1) { Serial.println(F("Manual-Auto Reset 1 = Auto")); // Note use of F macro to move text out of dynamic memory space } else { Serial.println(F("Manual-Auto Reset 1 = Manual")); } if (autorct2) { Serial.println(F("Manual-Auto Reset 2 = Auto")); } else { Serial.println(F("Manual-Auto Reset 2 = Manual")); } Serial.println(F("Normal Breaker Mode Enabled")); } else { Serial.println(F("AutoReverser Mode Enabled")); } } if ((debug) && (mon_cur)) { Serial.println(F("Outputting current values to Serial Monitor")); } if (!autorvsr) { digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; digitalWrite(alm_led2, LOW); arev_act = 0; // Not in AutoReverser mode } else { digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led2, HIGH); delay(2); // Power Districts 1 & 2 outputs are linked together in anti-phase digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); arev_act = 1; // Power District 1 active in AutoReverser mode } } //********************************************************************************************************************** void loop() { //The NmraDcc.process() method MUST be called frequently from this loop() //function for correct library operation and to process all DCC packets DCC.process(); delay(2); // Sets normal execution time of loop() if no configuration operations in progress // ========== Check Serial (USB) port for received configuration and status commands ============ if (Serial.available() > 0) { // Read the incoming byte: in_cmnd = Serial.read(); if (in_cmnd == 0x4E || in_cmnd == 0x6E) { // "N" - NOP command debug = false; // Ensure neither debug nor mon_cur flags are set to avoid USB conflicts with terminal protocol mon_cur = false; DCC.setCV(55, 0); delay(5); DCC.setCV(54, 0); delay(5); Serial.println(F("N >> OK")); // .. send ACK response } else if (in_cmnd == 0x53 || in_cmnd == 0x73) { // "S" - Return values of all 16 CVs holding Power Breaker parameters - CV41 to CV56 Serial.print(F("S >> CVs - ")); // Output copy of command for(i = 41; i < 57; i++){ cv_value = DCC.getCV(i); // Fetch CV if (i != 56) { Serial.print(cv_value, DEC); Serial.print(F(", ")); // Output as decimal values, comma-separated } else { Serial.println(cv_value, DEC); } } } else if (in_cmnd == 0x41 || in_cmnd == 0x61) { // "A" - Return PowerBreaker Address command ser_prm1 = DCC.getCV(42); // Address MSB ser_prm2 = DCC.getCV(41); // Address LSB t = (ser_prm1 * 256) + ser_prm2; Serial.print(F("A >> Address = ")); Serial.println(t, DEC); // Output as decimal value } else if (in_cmnd == 0x56 || in_cmnd == 0x76) { // "V" - Return Version command Serial.print(F("V >> ")); cv_value = dpb_vern; cv_value = cv_value >> 4; Serial.print(cv_value, DEC); // Major = MS Nibble Serial.print("."); // Major = MS Nibble cv_value = dpb_vern; cv_value = cv_value & 0x0F; Serial.println(cv_value, DEC); // Minor = LS Nibble } if (in_cmnd == 0x47 || in_cmnd == 0x67) { // "G" - Enable Debug Messages command debug = true; DCC.setCV(55, 0x0F); // Set debug CV to non-zero value delay(5); Serial.println(F("G >> OK")); // .. send ACK response } if (in_cmnd == 0x4B || in_cmnd == 0x6B) { // "K" - Enable ADC Display command mon_cur = true; DCC.setCV(54, 0x0F); // Set current monitor CV to non-zero value delay(5); nxtadcop = millis(); // Initialise timer for output of ADC current values Serial.println(F("K >> OK")); // .. send ACK response } else if (in_cmnd == 0x54 || in_cmnd == 0x74) { // "T" - Set Power Breaker Address command - add new address - up to 4 numeric characters ser_prmstr = Serial.readString(); // Get rest of input characters t = ser_prmstr.length(); sub_prmstr = ser_prmstr.substring(0,(t - 1)); Serial.print(F("T")); Serial.print(sub_prmstr); // Output copy of command Serial.print(F(" >> ")); v = ser_prmstr.toInt(); if (v == 0 || v > 2043) { Serial.println(F("Invalid Address - not in range 1 - 2043")); } else { t = DCC.getCV(41) + (256 * DCC.getCV(42)); if (t != v) { ser_prm1 = highByte(v); ser_prm2 = lowByte(v); DCC.setCV(41, ser_prm2); // Update Accessory Address delay(5); // .. if not equal to current stored value DCC.setCV(42, ser_prm1); delay(5); DCC.setCV(121, ser_prm2); // Update Program Main Address to match delay(5); DCC.setCV(122, ser_prm1); delay(5); } Serial.print(F("Address = ")); Serial.println(v, DEC); // .. send ACK response } } else if (in_cmnd == 0x43 || in_cmnd == 0x63) { // "C" - Set Trip Current command - add index (1-20) - up to 2 numeric characters delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District Select parameter delayMicroseconds(300); ser_prmstr = Serial.readString(); // Get rest of input characters t = ser_prmstr.length(); sub_prmstr = ser_prmstr.substring(0,(t - 1)); // Remove Newline character if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("C")); Serial.print((char)ser_prm1); Serial.print(sub_prmstr); // Output copy of command Serial.print(F(" >> ")); v = ser_prmstr.toInt(); if (v == 0 || v > 20) { Serial.println(F("Invalid Index - not in range 1 - 20")); } else { cv_value = lowByte(v); // Extract index value to fit byte if (ser_prm1 == 0x31) { // Check CV43 value - District 1 Trip Index if (cv_value != DCC.getCV(43)) { sen1trip = trip_slct[cv_value]; // Update Trip Current value only if value has changed DCC.setCV(43, cv_value); // .. and load to CV memory delay(5); } Serial.print(F("Dist 1 Trip Current = ")); Serial.print(cv_value*0.25, 2); Serial.println(F("A")); // .. send ACK response = Trip Current } else if (ser_prm1 == 0x32) { // Check CV44 value - District 2 Trip Index if (cv_value != DCC.getCV(44)) { sen2trip = trip_slct[cv_value]; // Update Trip Current value only if value DCC.setCV(44, cv_value); // .. has changed, and load to CV memory delay(5); } Serial.print(F("Dist 2 Trip Current = ")); Serial.print(cv_value*0.25, 2); Serial.println(F("A")); // .. send ACK response = Trip Current } else { Serial.println(F("Invalid District - must be 1 or 2")); } } } } else if (in_cmnd == 0x44 || in_cmnd == 0x64) { // "D" - Set Trip Delay command - add value 5 - 255msecs delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District Select parameter delayMicroseconds(300); ser_prmstr = Serial.readString(); // Get rest of input characters t = ser_prmstr.length(); sub_prmstr = ser_prmstr.substring(0,(t - 1)); // Remove Newline character if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("D")); Serial.print((char)ser_prm1); Serial.print(sub_prmstr); // Output copy of command Serial.print(F(" >> ")); v = ser_prmstr.toInt(); if (v < 5 || v > 255) { Serial.println(F("Invalid Delay - not in range 5 - 255")); } else { cv_value = lowByte(v); // Adjust value to fit byte if (ser_prm1 == 0x31) { // Check CV45 value - District 1 Trip Delay if (cv_value != DCC.getCV(45)) { ovrtime1 = cv_value; // Update Trip Delay value only if value DCC.setCV(45, cv_value); // .. has changed and load to CV memory delay(5); } Serial.print(F("Dist 1 Trip Delay = ")); Serial.print(cv_value, DEC); Serial.println(F("msec")); // .. send ACK response = Trip Delay } else if (ser_prm1 == 0x32) { // Check CV46 value - District 2 Trip Delay if (cv_value != DCC.getCV(46)) { ovrtime1 = cv_value; // Update Trip Delay value only if value DCC.setCV(46, cv_value); // .. has changed, and load to CV memory delay(5); } Serial.print(F("Dist 2 Trip Delay = ")); Serial.print(cv_value, DEC); Serial.println(F("msec")); // .. send ACK response = Trip Delay } else { Serial.println(F("Invalid District - must be 1 or 2")); } } } } else if (in_cmnd == 0x52 || in_cmnd == 0x72) { // "R" - Set Reconnect Delay command - add index (1-240) - units of 0.25sec delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District Select parameter delayMicroseconds(300); ser_prmstr = Serial.readString(); // Get rest of input characters t = ser_prmstr.length(); sub_prmstr = ser_prmstr.substring(0,(t - 1)); // Remove Newline character if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("R")); Serial.print((char)ser_prm1); Serial.print(sub_prmstr); // Output copy of command Serial.print(F(" >> ")); v = ser_prmstr.toInt(); if (v == 0 || v > 240) { Serial.println(F("Invalid Index - not in range 1 - 240")); } else { cv_value = lowByte(v); // Adjust value to fit byte if (ser_prm1 == 0x31) { // Check CV47 value - District 1 Reconnect Delay Index if (cv_value != DCC.getCV(47)) { trycnct1 = 250 * cv_value; // Update Reconnect Delay index only if value DCC.setCV(47, cv_value); // .. has changed and load to CV memory delay(5); } Serial.print(F("Dist 1 Reconnect Delay = ")); Serial.print(cv_value*0.25, 2); Serial.println(F("sec")); // .. send ACK response = Reconnect Delay } else if (ser_prm1 == 0x32) { // Check CV48 value - District 2 Reconnect Delay Index if (cv_value != DCC.getCV(48)) { trycnct2 = 250 * cv_value; // Update Reconnect Delay value only if value DCC.setCV(48, cv_value); // .. has changed, and load to CV memory delay(5); } Serial.print(F("Dist 2 Reconnect Delay = ")); Serial.print(cv_value*0.25, 2); Serial.println(F("sec")); // .. send ACK response = Reconnect Delay } else { Serial.println(F("Invalid District - must be 1 or 2")); } } } } else if (in_cmnd == 0x4D || in_cmnd == 0x6D) { // "M" - Enable/Disable Auto Reconnect command - add 1 or 2 to select District // .. then A to enable Auto, M to enable Manual delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District Select parameter delayMicroseconds(300); ser_prmstr = Serial.readString(); // Get rest of input characters if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' t = ser_prmstr.length(); if (t > 1) { ser_prm2 = ser_prmstr.charAt(0); } else { ser_prm2 = 0x2D; } Serial.print(F("M")); Serial.print((char)ser_prm1); Serial.print((char)ser_prm2); // Output copy of command Serial.print(F(" >> ")); cv_value = 0x00; if (ser_prm1 != 0x31 && ser_prm1 != 0x32) { Serial.println(F("Invalid District - must be 1 or 2")); cv_value = 0xFF; // Prepare to exit command } else { if (ser_prm2 == 0x4D || ser_prm2 == 0x6D) { cv_value = 0x00; // Prepare to set Manual Reconnect } else if (ser_prm2 == 0x41 || ser_prm2 == 0x61) { cv_value = 0x0A; // Prepare to set Auto Reconnect } else { Serial.println(F("Invalid Parameter - must be A or M")); cv_value = 0xFF; // Prepare to exit command } } if (cv_value != 0xFF) { if (ser_prm1 == 0x31) { if (cv_value != DCC.getCV(52)) { // Check CV52 value - Manual-Auto Reconnect District 1 if (cv_value == 0x0A) { autorct1 = true; // Auto } else { autorct1 = false; // Manual } DCC.setCV(52, cv_value); // Load to CV memory only if value has changed delay(5); } Serial.print(F("Dist 1 Reconnect = ")); if (autorct1) { Serial.println(F("Automatic")); } else { Serial.println(F("Manual")); } } else if (ser_prm1 == 0x32) { if (cv_value != DCC.getCV(53)) { // Check CV53 value - Manual-Auto Reconnect District 2 if (cv_value == 0x0A) { autorct2 = true; // Auto } else { autorct2 = false; // Manual } DCC.setCV(53, cv_value); // Load to CV memory only if value has changed delay(5); } Serial.print(F("Dist 2 Reconnect = ")); if (autorct2) { Serial.println(F("Automatic")); } else { Serial.println(F("Manual")); } } } } } else if (in_cmnd == 0x46 || in_cmnd == 0x66) { // "F" - Reset PowerBreaker to Default CV Values command DCC.setCV(50, 0); // Set CV50 = 0 delay(5); Serial.println("F >> CVs Reset"); // .. send ACK response delay(5); resetFunc(); // .. then call system reset to load default CVs on restart } else if (in_cmnd == 0x42 || in_cmnd == 0x62) { // "B" - Break Power command - add 1 = Dist 1, or 2 = Dist 2, or B = Both Districts delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District selection if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("B")); Serial.print((char)ser_prm1); // Output copy of command Serial.print(F(" >> ")); if (autorvsr) { Serial.println(F("Remove AutoReverser connections and Disable AutoReverser (UD)")); } else if (ser_prm1 == 0x31) { pwbreak1 = true; ovrcurr1 = true; killpwr1 = true; digitalWrite(alm_led1, HIGH); // Switch on Alarm LED 1 digitalWrite(en_pout1, LOW); // Switch off Power District 1 enblout1 = false; Serial.println(F("Dist 1 Switched Off")); // ACK response } else if (ser_prm1 == 0x32) { pwbreak2 = true; ovrcurr2 = true; killpwr2 = true; digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; Serial.println(F("Dist 2 Switched Off")); // ACK response } else if (ser_prm1 == 0x42 || ser_prm1 == 0x62) { pwbreak1 = true; pwbreak2 = true; ovrcurr1 = true; ovrcurr2 = true; killpwr1 = true; killpwr2 = true; digitalWrite(alm_led1, HIGH); // Switch on Alarm LED 1 digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 digitalWrite(en_pout1, LOW); // Switch off Power District 1 digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout1 = false; enblout2 = false; Serial.println(F("Dists 1 & 2 Switched Off")); // ACK response } else { Serial.println(F("Invalid District - must be 1, 2 or B")); } } } else if (in_cmnd == 0x50 || in_cmnd == 0x70) { // "P" - Resume Power command - add 1 = Dist 1, or 2 = Dist 2, or B = Both Districts delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get District selection if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("P")); Serial.print((char)ser_prm1); // Output copy of command Serial.print(F(" >> ")); if (autorvsr) { Serial.println(F("Remove AutoReverser connections and Disable AutoReverser (UD)")); } else if (ser_prm1 == 0x31) { pwbreak1 = false; ovrcurr1 = false; killpwr1 = false; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; Serial.println(F("Dist 1 Switched On")); // ACK response } else if (ser_prm1 == 0x32) { pwbreak2 = false; ovrcurr2 = false; killpwr2 = false; digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; Serial.println(F("Dist 2 Switched On")); // ACK response } else if (ser_prm1 == 0x42 || ser_prm1 == 0x62) { pwbreak1 = false; pwbreak2 = false; ovrcurr1 = false; ovrcurr2 = false; killpwr1 = false; killpwr2 = false; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 digitalWrite(en_pout1, HIGH); // Switch on Power District 1 digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout1 = true; enblout2 = true; Serial.println(F("Dists 1 & 2 Switched On")); // ACK response } else { Serial.println(F("Invalid District - must be 1, 2 or B")); } } } else if (in_cmnd == 0x55 || in_cmnd == 0x75) { // "U" - Enable/Disable Auto Reverser command - add E to enable, D to disable delayMicroseconds(300); if (Serial.available() > 0) { ser_prm1 = Serial.read(); // Get Enable/Disable parameter if (ser_prm1 == 0x0A) ser_prm1 = 0x2D; // Replace Newline with '-' Serial.print(F("U")); Serial.print((char)ser_prm1); // Output copy of command Serial.print(F(" >> ")); if (ser_prm1 == 0x45 || ser_prm1 == 0x65) { DCC.setCV(49, 0x5A); // Set CV49 = 90 delay(5); digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 delay(2); // Power Districts 1 & 2 outputs are linked together in anti-phase digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 autorvsr = true; // AutoReverser Mode enabled arev_act = 1; // Power District 1 active in AutoReverser mode Serial.println(F("AutoReverser Mode Enabled")); // ACK response } else if (ser_prm1 == 0x44 || ser_prm1 == 0x64) { DCC.setCV(49, 0x00); // Set CV49 = 0 delay(5); digitalWrite(en_pout1, LOW); // Switch off Power District 1 enblout1 = false; digitalWrite(alm_led1, HIGH); // Switch on Alarm LED 1 digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 autorvsr = false; arev_act = 0; // AutoReverser Mode disabled Serial.println(F("AutoReverser Mode Disabled - Both Districts Switched Off")); // ACK response } else { Serial.println(F("Invalid Parameter - must be E to Enable or D to Disable")); } } } } // ========== Check for Address Programming Link fitted ============ if ((digitalRead(adr_prog) == LOW) && (busy_dcc != 1)) { if (prog_done == 0) { delay(100); if (digitalRead(adr_prog) == LOW) { // Address Programming is active digitalWrite(en_pout1, LOW); // Switch off Power District 1 enblout1 = false; digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led1, HIGH); // Switch Alarm 1 Red LED On prog_actv = 1; busy_dcc = 1; Serial.println(F("Address Programming Active")); } } } else if ((digitalRead(adr_prog) == LOW) && (busy_dcc == 1)) { if (prog_done == 1) { flash = flash + 1; //Flash Alarm 1 Red LED as reminder to remove Programming Link if (flash == 100){ digitalWrite(alm_led1, LOW); // Switch off Alarm 1 Red LED } else{ if (flash == 200){ digitalWrite(alm_led1, HIGH); // Switch on Alarm 1 Red LED flash = 0; } } } } else if ((digitalRead(adr_prog) == HIGH) && (prog_actv == 1)) { delay(20); // Programming Link removed - Address Programming will be abandoned set_adr = 0; // Clear any entered address data prog_actv = 0; // Disable Address Programming prog_done = 0; busy_dcc = 0; // Allow normal switch actions Serial.println(F("Address Programming Inactive")); digitalWrite(alm_led1, LOW); // Switch off Alarm 1 Red LED delay(200); resetFunc(); // Call system reset - execution stops here then returns to start } // ========== Start Current Monitoring ============ if (mon_cur) { if (nxtadcop <= (millis() - 200)) { // Print District Current Values read from Nano ADCs t = analogRead(sens_in1); // Read District 1 Current Serial.print(F("Dist 1 ADC = ")); Serial.print(t, DEC); t = analogRead(sens_in2); // Read District 2 Current Serial.print(F(" - Dist 2 ADC = ")); Serial.println(t, DEC); nxtadcop = millis(); // Reset timer for next output of ADC current values } } if (!autorvsr) { // ==== Normal Power Breaker Operation ============================================================= if ((digitalRead(adr_prog) == HIGH) && (busy_dcc != 1) && (prog_actv != 1)) { // Check Power District 1 if (pwbreak1 && autorct1 && !killpwr1) { elapsed1 = millis() - tmstart1; // Check if time to try reconnect after break if (elapsed1 > trycnct1) { // Attempt reconnect if Auto Reconnect active and Not Soft Break if (debug) { Serial.print(F("OverCurrent Period Elapsed")); Serial.println(F(" - Reconnect Power District 1")); } digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 digitalWrite(alm_out1, LOW); // Switch off Alarm Out 1 (Sounder) pwbreak1 = false; ovrcurr1=false; } } else { sen1read = analogRead(sens_in1); // Read District 1 Current if (sen1read > sen1trip) { if (ovrcurr1) { elapsed1 = millis() - tmstart1; // Over-current already detected - check if still true for set time if (elapsed1 > ovrtime1) { // Initiate break - set Alarm 1 digitalWrite(en_pout1, LOW); // Switch off Power District 1 enblout1 = false; digitalWrite(alm_led1, HIGH); // Switch on Alarm LED 1 digitalWrite(alm_out1, HIGH); // Switch on Alarm Out 1 (Sounder) if (debug) { Serial.println(F("Break Power District 1")); } pwbreak1 = true; tmstart1 = millis(); } } else { ovrcurr1 = true; // Detected load > set trip current - set flag and start timer tmstart1 = millis(); } } else { ovrcurr1 = false; // Current has fallen below trip level - start monitoring again elapsed1 = 0; sen1read = 0; // Clear last read District 1 current } } // Check Power District 2 if (pwbreak2 && autorct2 && !killpwr2) { elapsed2 = millis() - tmstart2; // Check if time to try reconnect after break if (elapsed2 > trycnct2) { // Attempt reconnect if Auto Reconnect active and Not Soft Break if (debug) { Serial.print(F("OverCurrent Period Elapsed")); Serial.println(F(" - Reconnect Power District 2")); } digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 digitalWrite(alm_out2, LOW); // Switch off Alarm Out 2 (Sounder) pwbreak2 = false; ovrcurr2=false; } } else { sen2read = analogRead(sens_in2); // Read District 2 Current if (sen2read > sen2trip) { if (ovrcurr2) { elapsed2 = millis() - tmstart2; // Over-current already detected - check if still true for set time if (elapsed2 > ovrtime2) { // Initiate break - set Alarm 2 digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 digitalWrite(alm_out2, HIGH); // Switch on Alarm Out 2 (Sounder) if (debug) { Serial.println(F("Break Power District 2")); } pwbreak2 = true; tmstart2 = millis(); } } else { ovrcurr2 = true; // Detected load > set trip current - set flag and start timer tmstart2 = millis(); } } else { ovrcurr2 = false; // Current has fallen below trip level - start monitoring again elapsed2 = 0; sen2read = 0; // Clear last read District 2 current } } } } else { // ==== Operation as Auto Reverser ================================================================ if ((digitalRead(adr_prog) == HIGH) && (busy_dcc != 1) && (prog_actv != 1) && (arev_act == 1)) { // Check Power District 1 if (pwbreak1) { elapsed1 = millis() - tmstart1; // Check if time to try reconnect after break if (elapsed1 > trycnct1) { // Attempt reconnect if Auto Reconnect active and Not Soft Break if (debug) { Serial.print(F("OverCurrent Period Elapsed")); Serial.println(F(" - Reconnect Power District 1")); } digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 digitalWrite(alm_out1, LOW); // Switch off Alarm Out 1 (Sounder) pwbreak1 = false; ovrcurr1=false; } } else { sen1read = analogRead(sens_in1); // Read District 1 Current if (sen1read > sen1trip) { if (ovrcurr1) { elapsed1 = millis() - tmstart1; // Over-current already detected - check if still true for set time if (elapsed1 > ovrtime1) { // Initiate AutoReverser switch to District 2 - disable District 1 output and set Alarm 1 digitalWrite(en_pout1, LOW); // Switch off Power District 1 enblout1 = false; digitalWrite(alm_led1, HIGH); // Switch on Alarm LED 1 (probably not used when in AutoReverser mode) digitalWrite(alm_out1, HIGH); // Switch on Alarm Out 1 (Sounder, " ) if (tmautosw == 0) {tmautosw = millis() - (6 * ovrtime1);} // Set tmautosw for first time through main loop chkautsw = millis() - tmautosw; // Check if District switched again within (4 * ovrtime1) after previous switch if (chkautsw < (4 * ovrtime1)) { pwbreak1 = true; // Prepare to retry switching Power District 1 on after delay tmstart1 = millis(); if (debug) { Serial.print(F("Break Power Districts 1 & 2")); // Power District 2 is already off Serial.println(F(" - In Power District 1 - Both to reconnect")); } } else { delay(2); digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 (probably not used when in AutoReverser mode) digitalWrite(alm_out2, LOW); // Switch off Alarm Out 2 (Sounder, " ) tmautosw = millis(); // Start check period for continuing overload on newly active Power District arev_act = 2; // Set power District 2 as active if (debug) { Serial.println(F("Break Power District 1 - District 2 Active")); } } } } else { ovrcurr1 = true; // Detected load > set trip current - set flag and start timer tmstart1 = millis(); if (debug) { Serial.println(F("Overcurrent Detected District 1")); } } } else { ovrcurr1 = false; // Current has fallen below trip level - start monitoring again elapsed1 = 0; sen1read = 0; // Clear last read District 1 current } } } if ((digitalRead(adr_prog) == HIGH) && (busy_dcc != 1) && (prog_actv != 1) && (arev_act == 2)) { // Check Power District 2 if (pwbreak2) { elapsed2 = millis() - tmstart2; // Check if time to try reconnect after break if (elapsed2 > trycnct2) { // Attempt reconnect if Auto Reconnect active and Not Soft Break if (debug) { Serial.print(F("OverCurrent Period Elapsed")); Serial.println(F(" - Reconnect Power District 2")); } digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 digitalWrite(alm_out2, LOW); // Switch off Alarm Out 2 (Sounder) pwbreak2 = false; ovrcurr2=false; } } else { sen2read = analogRead(sens_in2); // Read District 2 Current if (sen2read > sen2trip) { if (ovrcurr2) { elapsed2 = millis() - tmstart2; // Over-current already detected - check if still true for set time if (elapsed2 > ovrtime2) { // Initiate AutoReverser switch to District 2 - disable District 1 output and set Alarm 1 digitalWrite(en_pout2, LOW); // Switch off Power District 2 enblout2 = false; digitalWrite(alm_led2, HIGH); // Switch on Alarm LED 2 (probably not used when in AutoReverser mode) digitalWrite(alm_out2, HIGH); // Switch on Alarm Out 2 (Sounder, " ) if (tmautosw == 0) {tmautosw = millis() - (6 * ovrtime2);} // Set tmautosw for first time through main loop chkautsw = millis() - tmautosw; // Check if District switched again within (4 * ovrtime1) after previous switch if (chkautsw < (4 * ovrtime2)) { pwbreak2 = true; // Prepare to retry switching Power District 2 on after delay tmstart2 = millis(); if (debug) { Serial.print(F("Break Power Districts 1 & 2")); // Power District 1 is already off Serial.println(F(" - In Power District 2 - Both to reconnect")); } } else { delay(2); digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 (probably not used when in AutoReverser mode) digitalWrite(alm_out1, LOW); // Switch off Alarm Out 1 (Sounder, " ) tmautosw = millis(); // Start check period for continuing overload on newly active Power District arev_act = 1; // Set power District 1 as active if (debug) { Serial.println(F("Break Power District 2 - District 1 Active")); } } } } else { ovrcurr2 = true; // Detected load > set trip current - set flag and start timer tmstart2 = millis(); if (debug) { Serial.println(F("Overcurrent Detected District 1")); } } } else { ovrcurr2 = false; // Current has fallen below trip level - start monitoring again elapsed2 = 0; sen2read = 0; // Clear last read District 2 current } } } } // ========== Check for Manual Reset inputs active to reconnect Power Districts ============ if ((digitalRead(man_rst1) == LOW) && (pwbreak1 || killpwr1)) { delay(30); // Only acted upon if District 1 power break is currently active if (digitalRead(man_rst1) == LOW) { while (digitalRead(man_rst1) == LOW){ // Reset switch 1 pressed } // Wait for Reset switch 1 to be released // Reset Power District 1 - irrespective of whether Auto Reset is active or not pwbreak1 = false; ovrcurr1 = false; killpwr1 = false; digitalWrite(alm_led1, LOW); // Switch off Alarm LED 1 digitalWrite(alm_out1, LOW); // Switch off Alarm Out 1 (Sounder) digitalWrite(en_pout1, HIGH); // Switch on Power District 1 enblout1 = true; } } if ((digitalRead(man_rst2) == LOW) && (pwbreak2 || killpwr2)) { delay(30); // Only acted upon if District 2 power break is currently active if (digitalRead(man_rst2) == LOW) { while (digitalRead(man_rst2) == LOW){ // Reset switch 2 pressed } // Wait for Reset switch 2 to be released // Reset Power District 2 pwbreak2 = false; ovrcurr2 = false; killpwr2 = false; digitalWrite(alm_led2, LOW); // Switch off Alarm LED 2 digitalWrite(alm_out2, LOW); // Switch off Alarm Out 2 (Sounder) digitalWrite(en_pout2, HIGH); // Switch on Power District 2 enblout2 = true; } } } // End main loop() //********************************************************************************************************************** // This function is called whenever a normal DCC Turnout Packet is received in Output Addressing Mode extern void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) { byte adr_lsb; byte adr_msb; if (prog_actv == 1 && prog_done == 0) { // Address Programming is active - acceptable addresses, 0001 to 2043 // Received Address is accepted as Output Address for the Dual Power Breaker set_adr = Addr; if ((set_adr > 0) && (set_adr < 2044)) { Serial.print("Address = "); Serial.print(Addr, DEC); Serial.println(" Entered"); } else { set_adr = 0; Serial.println("Entered Address must be between 1 and 2043"); } // Transfer entered Output Address (if any) to CV41, 42 and CV121, 122 if (set_adr != 0) { adr_lsb = lowByte(set_adr); adr_msb = highByte(set_adr); DCC.setCV(41, adr_lsb); delay(5); DCC.setCV(42, adr_msb); delay(5); Serial.print("Accessory Address Stored = "); Serial.println(set_adr, DEC); DCC.setCV(121, adr_lsb); delay(5); DCC.setCV(122, adr_msb); delay(5); Serial.print("Program-on-Main Address Stored = "); Serial.println(set_adr, DEC); } set_adr = 0; // Clear any entered address data prog_done = 1; // Prevent re-entry or setup operations until programming link removed flash = 0; // Prepare to blink Red LED Serial.println("Address Programming Complete - Remove Programming Link"); } }