Ok, I’ve removed all distracting code fragments and unnecessary comments and the error is still there -
D:\arduino sketches\DCC_Multi_Breaker\DCC_Multi_Breaker.ino: In function 'void untrip_handler()':
D:\arduino sketches\DCC_Multi_Breaker\DCC_Multi_Breaker.ino:158:25: error: expected ';' before numeric constant
#define led_off 0B10000000 // this is Breaker tripped or selected Off. 'power is On' LED is connected direct from +5V
^
D:\arduino sketches\DCC_Multi_Breaker\DCC_Multi_Breaker.ino:282:35: note: in expansion of macro 'led_off'
system_states[i][user_shadow] led_off ) = ( system_states[i][user_shadow] ^= 1 << button_breaker ) ? 1 : 0;
^~~~~~~
In file included from D:\arduino sketches\DCC_Multi_Breaker\DCC_Multi_Breaker.ino:48:0:
C:\Users\bruce\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\libraries\Wire\src/Wire.h: In function 'void I2C_read(uint8_t, uint8_t)':
C:\Users\bruce\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\libraries\Wire\src/Wire.h:69:13: note: candidate 1: uint8_t TwoWire::requestFrom(int, int, int)
uint8_t requestFrom(int, int, int);
^~~~~~~~~~~
C:\Users\bruce\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\libraries\Wire\src/Wire.h:66:13: note: candidate 2: uint8_t TwoWire::requestFrom(uint8_t, uint8_t, uint8_t)
uint8_t requestFrom(uint8_t, uint8_t, uint8_t);
^~~~~~~~~~~
exit status 1
Compilation error: expected ';' before numeric constant
Ignore the TwoWire warning about ambiguous data type.
And here is the current code -
/*
Title - DCC Multi Breaker
bruceg Aug 2025
*/
/*
Model Train Digital Command/Control Solid State Circuit Breaker
Original design & program by Terry Chamberlain 2025.
The Breaker sub circuit used is a slightly modified version of the 2002 MERG BCO1 using 4 N-channel Mosfets to fully isolate
both tracks.
Terry Chamberlain designed an all-in-one PCB holding a Nano, power, 2 sense circuits & 2 Breakers with user config via DCC
variables inputed to a DCC Command Station and displayed via the Arduino Serial Monitor.
I prefer a more modular approach with the microcontroller and power at one location, then the DCC config via DIP switches
next to individual Sensor/Breakers at the middle of each Power District with user display/interfaces for each Power District
nearby where they can be easily reached.
Hardware (my variant) -
1 Arduino Nano microcontroller. 30k prog, 2k data, 1k EEPROM, 14 (20) I/Os + on-board ADC mux, comparitor, timers, comms
1 Power. +9, -9V, +5 and Gnd is derived from the Rail DCC, or a +12V DC source with 1 A voltage regulators and a charge pump.
An on-board optocoupler also outputs a +5V single sided copy of the bi-polar DCC signal.
2 to 6 DCC Sense & Switch modules. Direct operating connections between microcontroller & modules.
Sense - a current transformer into a voltage via a resistor to an opamp precision rectifier, then to the ADC on the Nano
Range 1 is 0.25 to 7.75A/300 turns via 180R = 0.6V per A = 124ADC counts
or Range 2, double Range 1 is 15.5A/1000 turns via 165R = 0.3V per A = 62ADC counts.
Switch - opto isolated mosfet isolators for both DCC rail currents. Switch in opto LED trace for manual Off over ride
Polarity Reverser - Dual SPDT relays. (or 4 cheap solid state SPST relays)
2 to 6 user interface/display modules using PCF8574 8 I/O Expanders. Indicator LEDs, slide switch & push buttons.
P82B715 I2C Extenders allows ~30m twisted pair cables.
2 to 6 sets of PCF8575 I2C 16 I/O Expanders with DIP switches for Breaker configuration settings.
will need a TCA9548A I2C mux to be able to re-use the same 6 I2C addresses as the PCF8574 8 I/O Expanders
I/O Extender & I2C mux modules are ~$1 each these days.
I2C addresses assigned are fixed. To simplify the code a module count is performed in setup() then that value
is used in loops to process each Breaker in turn. Ensure that there are no gaps in the module numbers used;
if you only have 3 Breakers they must be connected to the pin numbers assigned to Breakers 1, 2 & 3 with their
I2C expanders set to the correct addresses defined below.
*/
//#include <NmraDcc.h>
#include <Wire.h>
#define version "0.0.6"
/*
#define processor_volts 5 // volts used by the microprocessor
#define ADC_volts_per_amp 0.6 // measured input
#define ADC_max_counts 1024 // Arduino Nano internal ADC is 10 bits
#define current_quarter_counts ( ADC_max_counts * ( ADC_volts_per_amp / 4 / processor_volts ) ) // ADC counts for 0.25 Amps
*/
// These values are implememtation dependant -
#define quarter_amp 31 // ADC counts **** check that your current transformers output this value ****
#define amps_default 2
#define trip_default 12 // ms
#define reset_default 16 // ms between reset checks
#define time_between_checks 1 // ms = 1,000us = 16,000 clocks
#define count_trip_times 4 // for 'settled' evaluation
#define count_rev_times 3 // must be more than half of trip for counting logic to prevent re-reversing
#define relay_time 5 // ms for mechanical changeover to start/end
#define attention 256 // ms buzzer duration
#define modules_max 7 // Nano + 6 Breakers
#define I2C_release true // Wire.endTransmission() & Wire.requestFrom() hold on to control of the I2C bus when I2C_release = 0
#define I2C_expander_bytes 1 // Each Breaker's setting switches will be read in setup(), the User Display/Control will be read/written as required
// PCF8574 are 1 byte = 8 I/O + interrupt. PCF8575 are 2 bytes = 16 I/O + interrupt.
// PCF857x 0x20 to 0x27
#define I2C_mux 0x70 // The I2C Gateway control is at 0x70 to 0x77. Write a single 8 bit byte for selecting any combination of the 8 buses
#define I2C_user 1 // I2C_user buttons & LEDs
#define I2C_config 2 // I2C_config is 16 DIP switches
#define I2C_display 0x20 // reserved for possible future LCD
#define I2C_expander1 0x21 // PCF8574 7 bit I2C addresses (address bit 0 sets data direction - 0B01010AAAD ->D extracted) PCF8575s are set to 4 higher here
#define I2C_expander2 0x22 // 1mA source by default, set lo can sink 25mA. All I/Os max total 100mA. Use reverse logic to sink devices ON
#define I2C_expander3 0x23 // interrupt method requires microcontroller pin to be pulled up
#define I2C_expander4 0x24 // I2C spec limits the Data & Clock lines to 3mA, BUT PCF857xs are built compatible to microcontroller port output specs, so can take more
#define I2C_expander5 0x25
#define I2C_expander6 0x26 // max using Nano ADC. Could add more using ADS7128 8 channel, 12 bit ADC modules at 0x10 to 0x17 (address set with resistor values to single pin)
//#define spare 0x27
// Nano pins assigned -
#define ADC_pin 0 // array position where each module's pin numbers are stored
#define analog1 A0 // pin number for module 1. Modules are numbered from USB end of Nano
#define analog2 A1
#define analog3 A2
#define analog4 A3 // I2C uses A4, A5 pins. A6, A7 are analog in only, no digital I/O
#define analog5 A6
#define analog6 A7
#define BREAK_pin 1 // array position where each module's pin numbers are stored
#define power1 9 // pin number for module 1
#define power2 8
#define power3 7
#define power4 6
#define power5 5
#define power6 4
#define district_reverse 3 // bit/array position where each module's pin positions are stored
// District settings are made with 16 DIP switches on I2C expanders, read in setup()
// byte 1 Bits 0 to 4 are Amp bits x 0.25 Amps; so 0=0.25, 1=0.5, 2=1 ... , bit 5 is high range, bit 6 reverser mode on/off, bit 7 auto reset on/off
// byte 2 is 2 packed nibbles, nibble 1 is trip time, nibble 2 is reset time;
#define amps_bits 0B00011111 // 5 bits x 0.25A = 0.25 to 7.75A OR x 0.5A to 15.5A
#define amps_hi 5 // 1:1000 current transformer doubles range & reduces hi_quarter_amp value /2
#define reverser_ok 6 // bit to test for reverse is allowed
#define reset_ok 7 // bit to test for reset manual/auto
#define trip_bits 0B00001111 // 4 bits x 4ms = 4 to 60ms
#define reset_bits 0B11110000 // reset times are 16x bigger than trip times; 64 to 960ms
// data layout for arrays -
// system-state bytes
#define breaker_state 0 // see further defines below
#define user_shadow 1 // state of user Display/Inputs
#define rev_count 2
#define trip_count 3
#define trip_value 4 // ADC value stored on Expander as 4x desired 0.25A default when left at 0 = 2A
#define trip_time 5 // low nibble 0-8. max 15, x4 = 4-60ms default when left at 0 = 16ms
#define recheck_time 6 // hi nibble 16-128, max 240, x4 = 64-960ms default when left at 0 = 512ms ~0.5s
#define states_size 7 // bytes for second array dimension (to store above values)
#define module_off 0 // module breaker_state bit values/flags
#define reverse_en 1
#define module_on 2 // only need to test this for on & on_rev
#define module_on_rev 3
#define reset_en 4
//#define range_hi 8 // not used. may be needed if status is shown on an LCD
#define module_ck 2 // <2 = Off
// User I/O shadow registers bit assignments, make bit changes in system_states[n][x] then write whole byte to expanders
// Expanders default to all Hi (max 1mA). Hi = Off
// I2C bit positions in user_shadow for quick bitwise manipulation by names, Uses inverted logic Hi = Off
// Arduino C code is 'little endian', so hi bit is at the beginning (left) and Lo bit is at the 'end' (right)
// Inputs
#define juicer 0B00000001 // not implemented yet ...
#define buzz_off 0B00000010 // Silence this buzzer
#define button_breaker 0B00000100 // request On/Off. an inline switch can allow local manual Distric Off too
// Outputs
#define reverse 0B00001000 // Output to switch relays
#define buzzer 0B00010000
#define led_juicer 0B00100000 // very fast acting mode (not implemented ... yet)
#define led_reverser 0B01000000 // show that this District will change polarity in faster time than a full trip
#define led_off 0B10000000 // this is Breaker tripped or selected Off. 'power is On' LED is connected direct from +5V
// global variables
//volatile bool buttonPressed = false; // flag for activity on an I2C PCF857x I/O expander. Set in an Interrupt Service Routine, read in loop()
// volatile tells compiler to NOT optimize this storage location so it can be found by an ISR when interrupts are Off
uint8_t temp, i, j, modules; // counters that will be used repeatedly
uint32_t time_last_check = millis();
// 5 arrays. Position 0 is used for flags, then 1:1 (human style) numbering for data positions
uint8_t I2C_data[3]; // max 2 bytes to/from Expanders
uint8_t I2C_addresses[modules_max] = { 0, I2C_expander1, I2C_expander2, I2C_expander3, I2C_expander4, I2C_expander5, I2C_expander6 };
uint8_t module_control_pins[modules_max][3] =
{
{ 0, 0, 0 },
{ analog1, power1, 0 }, // Input pin / Output pins for identical external modules
{ analog2, power2, 0 }, // analog & power are directly to/from Nano I/Os, button is on expanders
{ analog3, power3, 0 },
{ analog4, power4, 0 },
{ analog5, power5, 0 },
{ analog6, power6, 0 },
};
uint8_t system_states[modules_max][states_size]; // Off/On/RevOk/RevOn/Juice, userState, rev_count, trip_count, trip_value, trip_time, recheck_time
uint16_t module_ADC_counts[modules_max]; // current ADC counts (10 bit)
////////////////////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin( 115200 ); // default speed for Arduino IDE 2 Serial monitor
while ( !Serial ); // wait for slow hardware setup on fast devices
Wire.begin(); // use default pins - for Nano Clock = A4, Data = A5. buffers default to 32 bytes each
// Wire.setClock( 400000 ); // 400kHz, go faster than default 100000. probably too fast for long cables
Wire.setWireTimeout( 2500, true ); // defaults to 2500us = 0.0025s = 250 bit times at 100kHz. Ignores missing devices, but sets a flag & error codes
for( i = 1; i < modules_max; i++ ) // module expanders are at 0x21 - 0x27, only 7 addresses are available,
{ // but need 12+ for 6 Breakers, so will use an I2C mux to re-use addresses on multiple buses
I2C_write1( I2C_mux, I2C_user ); // TCA9548A mux boots to no output buses selected, switch mux to read/write user control modules,
// this actually makes a 9th 'master' bus available
I2C_read( I2C_addresses[i], 1 ); // who's here ? A proper response has error = 0; error 5 = timeout, no module found at address !
if( Wire.getWireTimeoutFlag() ) break; // flag is bool. exit for( loop ) at first reply failure
modules++; // count of modules that reply
system_states[i][user_shadow] = I2C_data[1]; // 1 byte returned is from the user control module, make a master copy (module & master reconciled in loop)
I2C_write1( I2C_addresses[i], ( system_states[i][user_shadow] & 1 << buzzer ) ); // set lo with bitwise AND (don't update shadow)
delay( attention ); // short buzz from each board at startup
I2C_write1( I2C_addresses[i], ( system_states[i][user_shadow] | 1 << buzzer ) ); // back to hi with bitwise OR (don't update shadow) Bitwise XOR (^) toggles a current value
// TO DO: button_breaker pressed at startup = debug mode
I2C_write1( I2C_mux, I2C_config ); // select correct I2C bus for reading config switches
// I2C Expanders startup with all bits set Hi. They use inverted logic to 'turn on' devices
I2C_read( I2C_addresses[i], 2 ); // Config Switches PCF8575 16 bit Expanders return 2 bytes
// store values read in, if zero then apply defaults. Byte 1 is Reverser, Reset & Amps, byte 2 is trip & reset time (2 nibbles)
if( I2C_data[1] & reverser_ok ) system_states[i][user_shadow] |= 1 << led_reverser; // test bit for reversing allowed, set flag bit in shadow
if( I2C_data[1] & reset_ok ) system_states[i][breaker_state] |= 1 << reset_en; // test bit for auto/manual reset and store result
system_states[i][trip_value] = ( I2C_data[1] & amps_bits ) * ( ( I2C_data[1] & amps_hi ) ? ( quarter_amp + 1 ) / 2 : quarter_amp ); // convert Amps value to ADC counts
system_states[i][trip_time] = ( I2C_data[2] & trip_bits ) ? ( I2C_data[2] & trip_bits ) : trip_default; // low nibble ms gets multiplied by 4 trip tests
system_states[i][recheck_time] = ( I2C_data[2] & reset_bits ) ? ( I2C_data[2] & reset_bits ) : reset_default; // high nibble ms is 16x bigger than low, then also requires 4 continuous under tests, so 64x
pinMode( module_control_pins[i][BREAK_pin], OUTPUT); // enable output pin to this Breaker
if( system_states[i][user_shadow] & button_breaker ) digitalWrite( module_control_pins[i][BREAK_pin], HIGH ); // if not user isolated, switch the Breaker On
}
I2C_write1( I2C_mux, I2C_user ); // finished reading configs, only need bus 1 from now on
// Wire.clearTimeout();
}
////////////////////////////////////////////////////////////////////////////////////
void loop()
{
if( ( millis() - time_last_check ) > time_between_checks ) // default 1ms = 1,000us = 16,000 Nano clocks
{
time_last_check = millis();
for( i = 1; i == modules; i++ )
{ // calls trip or untrip handlers to update trip counts to turn Breakers Off/On
analogRead( module_control_pins[i][ADC_pin] ); // switch the input pin, ignore first reading after switching to allow proper settling time
( ( module_ADC_counts[i] = analogRead( module_control_pins[i][ADC_pin] ) ) >= system_states[i][trip_value] ) ? trip_handler() : untrip_handler();
}
user_reconcile();
// TO DO: update possible display or serial monitor
}
}
////////////////////////////////////////////////////////////////////////////////////
void trip_handler()
{ // i was set in calling function ADC_read()
if( ( system_states[i][breaker_state] & reverser_ok ) && ( module_control_pins[i][rev_count] <= count_rev_times ) ) system_states[i][rev_count]++; // do NOT increment more than 1 above count_rev_times
if( ( system_states[i][rev_count] == count_rev_times ) ) reverse_phase(); // only counts up when reverse_ok
if( system_states[i][trip_count] <= count_trip_times ) system_states[i][trip_count]++; // do NOT increment more than 1 above count_trip_times
if( ( system_states[i][trip_count] == count_trip_times ) && ( module_control_pins[i][BREAK_pin] & module_on ) ) // only switch Off if currently On
{
digitalWrite( module_control_pins[i][BREAK_pin], LOW ); // switch OFF module_pin
system_states[i][user_shadow] & led_off;
}
}
////////////////////////////////////////////////////////////////////////////////////
void untrip_handler()
{ // i was set in calling function ADC_read()
if( ( system_states[i][breaker_state] & reverser_ok ) && ( module_control_pins[i][trip_count] > 0 ) ) system_states[i][rev_count]--; // check reverser first, do NOT decrement below 0
if( system_states[i][trip_count] > 0 ) system_states[i][trip_count]--; // do NOT decrement below 0
if( module_control_pins[i][button_breaker] )
{
I2C_write1( I2C_addresses[i], ( system_states[i][user_shadow] &= 1 << buzzer ) ); // set lo with bitwise AND,
delay( attention ); // buzz
I2C_write1( I2C_addresses[i], ( system_states[i][user_shadow] |= 1 << buzzer ) ); // back hi with bitwise OR. Bitwise XOR (^) toggles a current value
delay( 500 );
system_states[i][user_shadow] led_off ) = ( system_states[i][user_shadow] ^= 1 << button_breaker ) ? 1 : 0;
}
if( ( system_states[i][breaker_state] & reset_ok ) || ( system_states[i][user_shadow] &= 1 << button_breaker ) )
{
if( ( system_states[i][trip_count] == 0 ) && ( module_control_pins[i][BREAK_pin] |= 1 << module_on ) ) // only switch On if allowed & currently Off
{
digitalWrite( module_control_pins[i][BREAK_pin], HIGH ); // switch On module_pin
system_states[i][user_shadow] & led_off;
}
}
}
////////////////////////////////////////////////////////////////////////////////////
void reverse_phase()
{
digitalWrite( module_control_pins[i][BREAK_pin], 0 ); // blind, quick switch Off
delay( relay_time ); // debounce & power off time
I2C_write1( I2C_addresses[i], ( system_states[i][user_shadow] ^= 1 << reverse ) ); // toggle reverse; update shadow & user module
delay( relay_time ); // time for relays to change over too
if( system_states[i][BREAK_pin] & module_on ) digitalWrite( module_control_pins[i][BREAK_pin], 1 );
system_states[i][rev_count] = 0;
}
////////////////////////////////////////////////////////////////////////////////////
void user_reconcile()
{
for( i = 1; i == modules; i++ )
{
I2C_read( I2C_addresses[i], I2C_expander_bytes ); // fetch & copy to begin status alignment/reconciliation
// update system_states with any switch changes made on Breaker
if( I2C_data[1] & juicer ) system_states[i][user_shadow] |= 1 << juicer; // over write with same does nothing,
else system_states[i][user_shadow] &= 1 << juicer; // so just do it quickly anyway
if( I2C_data[1] & buzz_off ) system_states[i][user_shadow] |= 1 << buzzer;
else system_states[i][user_shadow] &= 1 << buzzer;
if( !( I2C_data[1] & button_breaker ) ) button_press_handle();
I2C_write1( I2C_addresses[i], system_states[i][user_shadow] ); // write any changes made back to the Breaker to take effect
}
}
////////////////////////////////////////////////////////////////////////////////////
void button_press_handle()
{
I2C_write1( i, user_shadow & buzzer ); // acknowledge button press
while( !( I2C_data[1] && button_breaker ) ) I2C_read( I2C_addresses[i], 1 ); // wait for release
I2C_write1( i, user_shadow | buzzer );
digitalWrite( module_control_pins[i][module_off], !digitalRead( module_control_pins[i][module_off] ) ); // toggle states
system_states[i][user_shadow] ^= 1 << led_off;
delay( 1 );
}
////////////////////////////////////////////////////////////////////////////////////
void I2C_write1( uint8_t address, uint8_t data ) // all Expanders used here accept 1 byte at a time, PCF8574 just over writes its 8 bits
{ // PCF8575 over writes its 2 bytes sequentially, 1, 2, 1, 2, ...
Wire.beginTransmission( address ); // set target address
Wire.write( data ); // load Tx buffer (single bytes in this prog, can use pointers for strings or arrays)
Wire.endTransmission( I2C_release ); // send the data queued up (default max 32 bytes), release control of the bus after sending,
} // return value is error report, may be ignored, but useful for auto-discovery via timeout
////////////////////////////////////////////////////////////////////////////////////
void I2C_read( uint8_t address, uint8_t size ) // all Expanders used here output all their contents when requested,
{ // the size value is ignored, but should be sent as a place holder
Wire.requestFrom( address, size, I2C_release ); // buffer size bytes from target and release control of the bus after receipt
if( Wire.getWireTimeoutFlag() ) return;
j = 0;
while( Wire.available() ) I2C_data[j++] = Wire.read(); // copy bytes from I2C buffer (not strictly necessary, but makes for more modular code)
}
////////////////////////////////////////////////////////////////////////////////////
Any clues as to the error’s cause ?