diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 5b6986b..787651c 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -2,7 +2,7 @@ * © 2021 Neil McKechnie * © 2021-2023 Harald Barth * © 2020-2023 Chris Harlow - * © 2022 Colin Murdoch + * © 2022-2023 Colin Murdoch * All rights reserved. * * This file is part of CommandStation-EX @@ -94,6 +94,8 @@ LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onChangeLookup=NULL; LookList * RMFT2::onClockLookup=NULL; +//CHM +LookList * RMFT2::onOverloadLookup=NULL; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -175,6 +177,8 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onGreenLookup=LookListLoader(OPCODE_ONGREEN); onChangeLookup=LookListLoader(OPCODE_ONCHANGE); onClockLookup=LookListLoader(OPCODE_ONTIME); +//CHM + onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD); // Second pass startup, define any turnouts or servos, set signals red @@ -266,12 +270,12 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) { char RMFT2::getRouteType(int16_t id) { for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(routeIdList,i); - if (rid==0) break; + if (rid==INT16_MAX) break; if (rid==id) return 'R'; } for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(automationIdList,i); - if (rid==0) break; + if (rid==INT16_MAX) break; if (rid==id) return 'A'; } return 'X'; @@ -610,6 +614,7 @@ void RMFT2::loop2() { break; case OPCODE_SPEED: + forward=DCC::getThrottleDirection(loco)^invert; driveLoco(operand); break; @@ -704,11 +709,11 @@ void RMFT2::loop2() { DCC::setThrottle(0,1,true); // pause all locos on the track pausingTask=this; break; - + case OPCODE_POM: if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1)); break; - + case OPCODE_POWEROFF: TrackManager::setPower(POWERMODE::OFF); TrackManager::setJoin(false); @@ -883,23 +888,18 @@ void RMFT2::loop2() { while(loopTask) loopTask->kill(F("KILLALL")); return; +#ifndef DISABLE_PROG case OPCODE_JOIN: TrackManager::setPower(POWERMODE::ON); TrackManager::setJoin(true); CommandDistributor::broadcastPower(); break; - - case OPCODE_POWERON: - TrackManager::setMainPower(POWERMODE::ON); - TrackManager::setJoin(false); - CommandDistributor::broadcastPower(); - break; - + case OPCODE_UNJOIN: TrackManager::setJoin(false); CommandDistributor::broadcastPower(); break; - + case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet DCC::getLocoId(readLocoCallback); @@ -920,6 +920,13 @@ void RMFT2::loop2() { forward=true; invert=false; break; +#endif + + case OPCODE_POWERON: + TrackManager::setMainPower(POWERMODE::ON); + TrackManager::setJoin(false); + CommandDistributor::broadcastPower(); + break; case OPCODE_START: { @@ -983,7 +990,9 @@ void RMFT2::loop2() { case OPCODE_ONGREEN: case OPCODE_ONCHANGE: case OPCODE_ONTIME: - + //CHM + case OPCODE_ONOVERLOAD: + break; default: @@ -1136,6 +1145,15 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) { handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60); } } +//CHM +void RMFT2::powerEvent(char track, bool overload) { + // Hunt for an ONOVERLOAD for this item + if (Diag::CMD) + DIAG(F("Looking for Power event on track : %c"), track); + if (overload) { + handleEvent(F("POWER"),onOverloadLookup,track); + } +} void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); @@ -1242,7 +1260,10 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL); break; case thrunge_broadcast: - // TODO CommandDistributor::broadcastText(buffer->getString()); + CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString()); + break; + case thrunge_withrottle: + CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString()); break; case thrunge_lcd: LCD(id,F("%s"),buffer->getString()); diff --git a/EXRAIL2.h b/EXRAIL2.h index e3d9007..a3f10e7 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -1,7 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow - * © 2022 Colin Murdoch + * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * All rights reserved. * @@ -45,7 +45,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE, OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, - OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM, +#ifndef DISABLE_PROG + OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2, +#endif + OPCODE_POM, OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET, OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, @@ -59,6 +62,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ONCHANGE, OPCODE_ONCLOCKTIME, OPCODE_ONTIME, + OPCODE_ONOVERLOAD, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -77,7 +81,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, // Ensure thrunge_lcd is put last as there may be more than one display, // sequentially numbered from thrunge_lcd. enum thrunger: byte { - thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, + thrunge_print, thrunge_broadcast, thrunge_withrottle, + thrunge_serial,thrunge_parse, thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial4, thrunge_serial5, thrunge_serial6, thrunge_lcn, @@ -126,6 +131,7 @@ class LookList { static void activateEvent(int16_t addr, bool active); static void changeEvent(int16_t id, bool change); static void clockEvent(int16_t clocktime, bool change); + static void powerEvent(char track, bool overload); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -184,6 +190,9 @@ private: static LookList * onGreenLookup; static LookList * onChangeLookup; static LookList * onClockLookup; + //CHM + static LookList * onOverloadLookup; + // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 5c047da..b8c4ce5 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -1,6 +1,6 @@ /* * © 2020-2022 Chris Harlow. All rights reserved. - * © 2022 Colin Murdoch + * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * * This file is part of CommandStation-EX @@ -93,6 +93,8 @@ #undef ONTIME #undef ONCLOCKTIME #undef ONCLOCKMINS +//CHM +#undef ONOVERLOAD #undef ONGREEN #undef ONRED #undef ONTHROW @@ -101,7 +103,9 @@ #undef PAUSE #undef PIN_TURNOUT #undef PRINT +#ifndef DISABLE_PROG #undef POM +#endif #undef POWEROFF #undef POWERON #undef READ_LOCO @@ -142,6 +146,7 @@ #undef VIRTUAL_SIGNAL #undef VIRTUAL_TURNOUT #undef WAITFOR +#undef WITHROTTLE #undef XFOFF #undef XFON @@ -214,6 +219,8 @@ #define ONCLOCKMINS(mins) #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) +//CHM +#define ONOVERLOAD(track_id) #define ONCLOSE(turnout_id) #define ONGREEN(signal_id) #define ONRED(signal_id) @@ -223,7 +230,9 @@ #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) #define PARSE(msg) +#ifndef DISABLE_PROG #define POM(cv,value) +#endif #define POWEROFF #define POWERON #define READ_LOCO @@ -264,6 +273,7 @@ #define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) #define WAITFOR(pin) +#define WITHROTTLE(msg) #define XFOFF(cab,func) #define XFON(cab,func) #endif diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 633ce5a..8e01b4a 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -1,7 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow - * © 2022 Colin Murdoch + * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * All rights reserved. * @@ -81,14 +81,14 @@ void exrailHalSetup() { #define ROUTE(id, description) id, const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" - 0}; + INT16_MAX}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" - 0}; + INT16_MAX}; // Pass 3 Create route descriptions: #undef ROUTE @@ -153,6 +153,8 @@ const int StringMacroTracker1=__COUNTER__; lcdid=id;\ break;\ } +#undef WITHROTTLE +#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle) void RMFT2::printMessage(uint16_t id) { thrunger tmode; @@ -188,7 +190,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) { // Pass 6: Roster IDs (count) #include "EXRAIL2MacroReset.h" #undef ROSTER -#define ROSTER(cabid,name,funcmap...) +1 +#define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1) const byte RMFT2::rosterNameCount=0 #include "myAutomation.h" ; @@ -199,7 +201,7 @@ const byte RMFT2::rosterNameCount=0 #define ROSTER(cabid,name,funcmap...) cabid, const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" - 0}; + INT16_MAX}; // Pass 7: Roster names getter #include "EXRAIL2MacroReset.h" @@ -221,7 +223,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #include "myAutomation.h" default: break; } - return F(""); + return NULL; } // Pass 8 Signal definitions @@ -320,13 +322,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins) #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), +#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(track_id), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), #define ONRED(signal_id) OPCODE_ONRED,V(signal_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, -#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), +#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), +#ifndef DISABLE_PROG #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), +#endif #define POWEROFF OPCODE_POWEROFF,0,0, #define POWERON OPCODE_POWERON,0,0, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), @@ -368,6 +373,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), #define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0), +#define WITHROTTLE(msg) PRINT(msg) #define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), #define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func), diff --git a/MotorDriver.cpp b/MotorDriver.cpp index b8068f3..1ca4f04 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -4,6 +4,7 @@ * © 2021 Fred Decker * © 2020-2023 Harald Barth * © 2020-2021 Chris Harlow + * © 2023 Colin Murdoch * All rights reserved. * * This file is part of CommandStation-EX @@ -26,16 +27,18 @@ #include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" +#include "EXRAIL2.h" -bool MotorDriver::commonFaultPin=false; +unsigned long MotorDriver::globalOverloadStart = 0; volatile portreg_t shadowPORTA; volatile portreg_t shadowPORTB; volatile portreg_t shadowPORTC; -MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float sense_factor, unsigned int trip_milliamps, int8_t fault_pin) { - powerPin=power_pin; +MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin, + byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) { + const FSH * warnString = F("** WARNING **"); + invertPower=power_pin < 0; if (invertPower) { powerPin = 0-power_pin; @@ -91,35 +94,54 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } else dualSignal=false; - brakePin=brake_pin; if (brake_pin!=UNUSED_PIN){ invertBrake=brake_pin < 0; - brakePin=invertBrake ? 0-brake_pin : brake_pin; + if (invertBrake) + brake_pin = 0-brake_pin; + if (brake_pin > MAX_PIN) + DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN); + brakePin=(byte)brake_pin; getFastPin(F("BRAKE"),brakePin,fastBrakePin); // if brake is used for railcom cutout we need to do PORTX register trick here as well pinMode(brakePin, OUTPUT); setBrake(true); // start with brake on in case we hace DC stuff going on + } else { + brakePin=UNUSED_PIN; } - else brakePin=UNUSED_PIN; currentPin=current_pin; - if (currentPin!=UNUSED_PIN) ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) { + int ret = ADCee::init(currentPin); + if (ret < -1010) { // XXX give value a name later + DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin); + currentPin = UNUSED_PIN; + } + } senseOffset=0; // value can not be obtained until waveform is activated - faultPin=fault_pin; - if (faultPin != UNUSED_PIN) { + if (fault_pin != UNUSED_PIN) { invertFault=fault_pin < 0; - faultPin=invertFault ? 0-fault_pin : fault_pin; + if (invertFault) + fault_pin = 0-fault_pin; + if (fault_pin > MAX_PIN) + DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN); + faultPin=(byte)fault_pin; DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault); getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); pinMode(faultPin, INPUT); + } else { + faultPin=UNUSED_PIN; } // This conversion performed at compile time so the remainder of the code never needs // float calculations or libraray code. senseFactorInternal=sense_factor * senseScale; tripMilliamps=trip_milliamps; - rawCurrentTripValue=mA2raw(trip_milliamps); +#ifdef MAX_CURRENT + if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps) + tripMilliamps = MAX_CURRENT; +#endif + rawCurrentTripValue=mA2raw(tripMilliamps); if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) { // This would mean that the values obtained from the ADC never @@ -134,20 +156,16 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } if (currentPin==UNUSED_PIN) - DIAG(F("** WARNING ** No current or short detection")); + DIAG(F("%S No current or short detection"), warnString); else { - DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue); + DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue); // self testing diagnostic for the non-float converters... may be removed when happy // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), // senseFactorInternal, raw2mA(1000),mA2raw(1000)); } - // prepare values for current detection - sampleDelay = 0; - lastSampleTaken = millis(); progTripValue = mA2raw(TRIP_CURRENT_PROG); - } bool MotorDriver::isPWMCapable() { @@ -156,7 +174,12 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { - bool on=mode==POWERMODE::ON; + if (powerMode == mode) return; + //DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode); + lastPowerChange[(int)mode] = micros(); + if (mode == POWERMODE::OVERLOAD) + globalOverloadStart = lastPowerChange[(int)mode]; + bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT); if (on) { // when switching a track On, we need to check the crrentOffset with the pin OFF if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) { @@ -196,8 +219,8 @@ bool MotorDriver::canMeasureCurrent() { return currentPin!=UNUSED_PIN; } /* - * Return the current reading as pin reading 0 to 1023. If the fault - * pin is activated return a negative current to show active fault pin. + * Return the current reading as pin reading 0 to max resolution (1024 or 4096). + * If the fault pin is activated return a negative current to show active fault pin. * As there is no -0, cheat a little and return -1 in that case. * * senseOffset handles the case where a shield returns values above or below @@ -214,14 +237,12 @@ int MotorDriver::getCurrentRaw(bool fromISR) { // if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current); current = current-senseOffset; // adjust with offset if (current<0) current=0-current; - if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) { - if (invertFault && isLOW(fastFaultPin)) - return (current == 0 ? -1 : -current); - if (!invertFault && !isLOW(fastFaultPin)) + // current >= 0 here, we use negative current as fault pin flag + if ((faultPin != UNUSED_PIN) && powerPin) { + if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin)) return (current == 0 ? -1 : -current); } return current; - } #ifdef ANALOG_READ_INTERRUPT @@ -259,6 +280,7 @@ void MotorDriver::startCurrentFromHW() { #endif //ANALOG_READ_INTERRUPT #if defined(ARDUINO_ARCH_ESP32) +#ifdef VARIABLE_TONES uint16_t taurustones[28] = { 165, 175, 196, 220, 247, 262, 294, 330, 349, 392, 440, 494, @@ -267,16 +289,43 @@ uint16_t taurustones[28] = { 165, 175, 196, 220, 330, 284, 262, 247, 220, 196, 175, 165 }; #endif +#endif void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; + switch(brakePin) { #if defined(ARDUINO_AVR_UNO) - TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz + // Not worth doin something here as: + // If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source. + // If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc. + // We are most likely not on pin 3 or 11 as no known motor shield has that as brake. #endif #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz - TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs + case 9: + case 10: + // Timer2 (is differnet) + TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM + TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz + //DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B); + break; + case 6: + case 7: + case 8: + // Timer4 + TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit + TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz + break; + case 46: + case 45: + case 44: + // Timer5 + TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit + TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz + break; #endif + default: + break; + } // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 byte tDir=speedcode & 0x80; @@ -284,11 +333,13 @@ void MotorDriver::setDCSignal(byte speedcode) { #if defined(ARDUINO_ARCH_ESP32) { int f = 131; +#ifdef VARIABLE_TONES if (tSpeed > 2) { if (tSpeed <= 58) { f = taurustones[ (tSpeed-2)/2 ] ; } } +#endif DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup } #endif @@ -327,7 +378,60 @@ void MotorDriver::setDCSignal(byte speedcode) { interrupts(); } } - +void MotorDriver::throttleInrush(bool on) { + if (brakePin == UNUSED_PIN) + return; + if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT))) + return; + byte duty = on ? 208 : 0; + if (invertBrake) + duty = 255-duty; +#if defined(ARDUINO_ARCH_ESP32) + if(on) { + DCCTimer::DCCEXanalogWrite(brakePin,duty); + DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500); + } else { + ledcDetachPin(brakePin); + } +#else + if(on){ + switch(brakePin) { +#if defined(ARDUINO_AVR_UNO) + // Not worth doin something here as: + // If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source. + // If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc. + // We are most likely not on pin 3 or 11 as no known motor shield has that as brake. +#endif +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + case 9: + case 10: + // Timer2 (is different) + TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM + TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max) + DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B); + break; + case 6: + case 7: + case 8: + // Timer4 + TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit + TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max) + break; + case 46: + case 45: + case 44: + // Timer5 + TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit + TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max) + break; +#endif + default: + break; + } + } + analogWrite(brakePin,duty); +#endif +} unsigned int MotorDriver::raw2mA( int raw) { //DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale); return (int32_t)raw * senseFactorInternal / senseScale; @@ -356,64 +460,174 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } +/////////////////////////////////////////////////////////////////////////////////////////// +// checkPowerOverload(useProgLimit, trackno) +// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode +// byte trackno: trackmanager knows it's number (could be skipped?) +// +// Short ciruit handling strategy: +// +// There are the following power states: ON ALERT OVERLOAD OFF +// OFF state is only changed to/from manually. Power is on +// during ON and ALERT. Power is off during OVERLOAD and OFF. +// The overload mechanism changes between the other states like +// +// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON +// or +// ON -1-> ALERT -4-> ON +// +// Times are in class MotorDriver (MotorDriver.h). +// +// 1. ON to ALERT: +// Transition on fault pin condition or current overload +// +// 2. ALERT to OVERLOAD: +// Transition happens if different timeouts have elapsed. +// If only the fault pin is active, timeout is +// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms) +// If only overcurrent is detected, timeout is +// POWER_SAMPLE_IGNORE_CURRENT (100ms) +// If fault pin and overcurrent are active, timeout is +// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms) +// Transition to OVERLOAD turns off power to the affected +// output (unless fault pins are shared) +// If the transition conditions are not fullfilled, +// transition according to 4 is tested. +// +// 3. OVERLOAD to ALERT +// Transiton happens when timeout has elapsed, timeout +// is named power_sample_overload_wait. It is started +// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry +// to OVERLOAD and then increased by a factor of 2 +// at further entries to the OVERLOAD condition. This +// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached. +// power_sample_overload_wait is reset by a poweroff or +// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON. +// After timeout power is turned on again and state +// goes back to ALERT. +// +// 4. ALERT to ON +// Transition happens by watching the current and fault pin +// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If +// values have been good during that time, transition is +// made back to ON. Note that even if state is back to ON, +// the power_sample_overload_wait time is first reset +// later (see above). +// +// The time keeping is handled by timestamps lastPowerChange[] +// which are set by each power change and by lastBadSample which +// keeps track if conditions during ALERT have been good enough +// to go back to ON. The time differences are calculated by +// microsSinceLastPowerChange(). +// + void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { - if (millis() - lastSampleTaken < sampleDelay) return; - lastSampleTaken = millis(); - int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); - - // Trackname for diag messages later + switch (powerMode) { - case POWERMODE::OFF: - sampleDelay = POWER_SAMPLE_OFF_WAIT; - break; - case POWERMODE::ON: - // Check current - lastCurrent=getCurrentRaw(); - if (lastCurrent < 0) { - // We have a fault pin condition to take care of - lastCurrent = -lastCurrent; - setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - if (commonFaultPin) { - if (lastCurrent < tripValue) { - setPower(POWERMODE::ON); // maybe other track - } - // Write this after the fact as we want to turn on as fast as possible - // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); - } else { - DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A'); - if (lastCurrent < tripValue) { - lastCurrent = tripValue; // exaggerate - } - } - } - if (lastCurrent < tripValue) { - sampleDelay = POWER_SAMPLE_ON_WAIT; - if(power_good_counter<100) - power_good_counter++; - else - if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; + + case POWERMODE::OFF: { + lastPowerMode = POWERMODE::OFF; + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + break; + } + + case POWERMODE::ON: { + lastPowerMode = POWERMODE::ON; + bool cF = checkFault(); + bool cC = checkCurrent(useProgLimit); + if(cF || cC ) { + if (cC) { + unsigned int mA=raw2mA(lastCurrent); + DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A', + cF ? "FAULT" : "", + mA); } else { - setPower(POWERMODE::OVERLOAD); - unsigned int mA=raw2mA(lastCurrent); - unsigned int maxmA=raw2mA(tripValue); - power_good_counter=0; - sampleDelay = power_sample_overload_wait; - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay); - if (power_sample_overload_wait >= 10000) - power_sample_overload_wait = 10000; - else - power_sample_overload_wait *= 2; + DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } + setPower(POWERMODE::ALERT); break; - case POWERMODE::OVERLOAD: - // Try setting it back on after the OVERLOAD_WAIT + } + // all well + if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) { + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + } + break; + } + + case POWERMODE::ALERT: { + // set local flags that handle how much is output to diag (do not output duplicates) + bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD); + bool powerModeChange = (powerMode != lastPowerMode); + unsigned long now = micros(); + if (powerModeChange) + lastBadSample = now; + lastPowerMode = POWERMODE::ALERT; + // check how long we have been in this state + unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT); + if(checkFault()) { + throttleInrush(true); + lastBadSample = now; + unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW; + if ( mslpc < timeout) { + if (powerModeChange) + DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout); + break; + } + DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait); + throttleInrush(false); + setPower(POWERMODE::OVERLOAD); + //CHM + RMFT2::powerEvent(trackno + 'A', true); // Tell EXRAIL we have an overload + break; + } + if (checkCurrent(useProgLimit)) { + lastBadSample = now; + if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) { + if (powerModeChange) { + unsigned int mA=raw2mA(lastCurrent); + DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA); + } + break; + } + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), + trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait); + throttleInrush(false); + setPower(POWERMODE::OVERLOAD); + //CHM + RMFT2::powerEvent(trackno + 'A', true); // Tell EXRAIL we have an overload + break; + } + // all well + unsigned long goodtime = micros() - lastBadSample; + if (goodtime > POWER_SAMPLE_ALERT_GOOD) { + if (true || notFromOverload) { // we did a RESTORE message XXX + unsigned int mA=raw2mA(lastCurrent); + DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA); + } + throttleInrush(false); setPower(POWERMODE::ON); - sampleDelay = POWER_SAMPLE_ON_WAIT; - // Debug code.... - DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay); - break; - default: - sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. + } + break; + } + + case POWERMODE::OVERLOAD: { + lastPowerMode = POWERMODE::OVERLOAD; + unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD)); + if (mslpc > power_sample_overload_wait) { + // adjust next wait time + power_sample_overload_wait *= 2; + if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) + power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; + // power on test + DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc); + setPower(POWERMODE::ALERT); + } + break; + } + + default: + break; } }