diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index e7087ad..be23577 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -37,7 +37,7 @@ int16_t lastclocktime; int8_t lastclockrate; -#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) +#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) || defined(SERIAL4_COMMANDS) || defined(SERIAL5_COMMANDS) || defined(SERIAL6_COMMANDS) // use a buffer to allow broadcast StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer(); template void CommandDistributor::broadcastReply(clientType type, Targs... msg){ @@ -248,6 +248,10 @@ void CommandDistributor::broadcastLoco(byte slot) { #endif } +void CommandDistributor::broadcastForgetLoco(int16_t loco) { + broadcastReply(COMMAND_TYPE, F("\n<- %d>\n"), loco,loco); +} + void CommandDistributor::broadcastPower() { char pstr[] = "? x"; for(byte t=0; t3) return; + auto reg=lookupSpeedTable(cab,true); + // drop and replace F29,30,31 (top 3 bits) + auto newFunctions=speedTable[reg].functions & 0x1FFFFFFFUL; + if (freq==1) newFunctions |= (1UL<<29); // F29 + else if (freq==2) newFunctions |= (1UL<<30); // F30 + else if (freq==3) newFunctions |= (1UL<<31); // F31 + if (newFunctions==speedTable[reg].functions) return; // no change + speedTable[reg].functions=newFunctions; + CommandDistributor::broadcastLoco(reg); +} + void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) { // onoff is tristate: // 0 => send off packet @@ -728,11 +742,15 @@ void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco if (reg>=0) { speedTable[reg].loco=0; setThrottle2(cab,1); // ESTOP if this loco still on track + CommandDistributor::broadcastForgetLoco(cab); } } void DCC::forgetAllLocos() { // removes all speed reminders setThrottle2(0,1); // ESTOP all locos still on track - for (int i=0;igetCurrentRaw(); ackThreshold= baseline + progDriver->mA2raw(ackLimitmA); - if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), + if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %lus and %lus"), baseline,progDriver->raw2mA(baseline), ackThreshold,progDriver->raw2mA(ackThreshold), minAckPulseDuration, maxAckPulseDuration); @@ -146,7 +146,7 @@ void DCCACK::setAckPending() { byte DCCACK::getAck() { if (ackPending) return (2); // still waiting - if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, + if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%luS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); if (ackDetected) return (1); // Yes we had an ack return(0); // pending set off but not detected means no ACK. diff --git a/DCCACK.h b/DCCACK.h index fa03387..c50dbbd 100644 --- a/DCCACK.h +++ b/DCCACK.h @@ -79,10 +79,10 @@ class DCCACK { static inline void setAckLimit(int mA) { ackLimitmA = mA; } - static inline void setMinAckPulseDuration(unsigned int i) { + static inline void setMinAckPulseDuration(unsigned long i) { minAckPulseDuration = i; } - static inline void setMaxAckPulseDuration(unsigned int i) { + static inline void setMaxAckPulseDuration(unsigned long i) { maxAckPulseDuration = i; } @@ -126,11 +126,11 @@ class DCCACK { static unsigned long ackCheckStart; // millis static unsigned int ackCheckDuration; // millis - static unsigned int ackPulseDuration; // micros + static unsigned long ackPulseDuration; // micros static unsigned long ackPulseStart; // micros - static unsigned int minAckPulseDuration ; // micros - static unsigned int maxAckPulseDuration ; // micros + static unsigned long minAckPulseDuration ; // micros + static unsigned long maxAckPulseDuration ; // micros static MotorDriver* progDriver; static volatile uint8_t numAckGaps; static volatile uint8_t numAckSamples; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cdbd71a..08e4097 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -2,7 +2,7 @@ * © 2022 Paul M Antoine * © 2021 Neil McKechnie * © 2021 Mike S - * © 2021 Herb Morton + * © 2021-2024 Herb Morton * © 2020-2023 Harald Barth * © 2020-2021 M Steve Todd * © 2020-2021 Fred Decker @@ -564,6 +564,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } #ifndef DISABLE_PROG else if (p[0]=="PROG"_hk) { // <0 PROG> + TrackManager::setJoin(false); TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF); } @@ -642,6 +643,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'F': // New command to call the new Loco Function API if(params!=3) break; + + if (p[1]=="DCFREQ"_hk) { // + if (p[2]<0 || p[2]>3) break; + DCC::setDCFreq(p[0],p[2]); + return; + } + if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); if (DCC::setFn(p[0], p[1], p[2] == 1)) return; @@ -1078,15 +1086,24 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { #ifndef DISABLE_PROG case "ACK"_hk: // if (params >= 3) { + long duration; if (p[1] == "LIMIT"_hk) { DCCACK::setAckLimit(p[2]); - LCD(1, F("Ack Limit=%dmA"), p[2]); // + LCD(1, F("Ack Limit=%dmA"), p[2]); // } else if (p[1] == "MIN"_hk) { - DCCACK::setMinAckPulseDuration(p[2]); - LCD(0, F("Ack Min=%uus"), p[2]); // + if (params == 4 && p[3] == "MS"_hk) + duration = p[2] * 1000L; + else + duration = p[2]; + DCCACK::setMinAckPulseDuration(duration); + LCD(0, F("Ack Min=%lus"), duration); // } else if (p[1] == "MAX"_hk) { - DCCACK::setMaxAckPulseDuration(p[2]); - LCD(0, F("Ack Max=%uus"), p[2]); // + if (params == 4 && p[3] == "MS"_hk) // + duration = p[2] * 1000L; + else + duration = p[2]; + DCCACK::setMaxAckPulseDuration(duration); + LCD(0, F("Ack Max=%lus"), duration); // } else if (p[1] == "RETRY"_hk) { if (p[2] >255) p[2]=3; LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index 93711aa..39824a5 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -76,8 +76,13 @@ int DCCTimer::freeMemory() { #endif //////////////////////////////////////////////////////////////////////// - #ifdef ARDUINO_ARCH_ESP32 + +#include "esp_idf_version.h" +#if ESP_IDF_VERSION_MAJOR > 4 +#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDE version 4. Arduino ESP32 library 3.0.0 is too new. Downgrade to one of 2.0.9 to 2.0.17" +#endif + #include "DIAG.h" #include #include @@ -292,7 +297,12 @@ void DCCTimer::DCCEXInrushControlOn(uint8_t pin, int duty, bool inverted) { int ADCee::init(uint8_t pin) { pinMode(pin, ANALOG); adc1_config_width(ADC_WIDTH_BIT_12); +// Espressif deprecated ADC_ATTEN_DB_11 somewhere between 2.0.9 and 2.0.17 +#ifdef ADC_ATTEN_11db + adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_11db); +#else adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11); +#endif return adc1_get_raw(pinToADC1Channel(pin)); } int16_t ADCee::ADCmax() { diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 6980f03..f228f1f 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -228,7 +228,6 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_AT: case OPCODE_ATTIMEOUT2: case OPCODE_AFTER: - case OPCODE_AFTEROVERLOAD: case OPCODE_IF: case OPCODE_IFNOT: { int16_t pin = (int16_t)operand; @@ -479,10 +478,15 @@ bool RMFT2::skipIfBlock() { /* static */ void RMFT2::readLocoCallback(int16_t cv) { + if (cv <= 0) { + DIAG(F("CV read error")); + progtrackLocoId = -1; + return; + } if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr - DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR); + DIAG(F("Long addr %d <= %d unsupported"), progtrackLocoId, HIGHEST_SHORT_ADDR); progtrackLocoId = -1; } } else { @@ -629,14 +633,16 @@ void RMFT2::loop2() { skipIf=blinkState!=at_timeout; break; - case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation) + case OPCODE_AFTER: // waits for sensor to hit and then remain off for x mS. + // Note, this must come after an AT operation, which is + // automatically inserted by the AFTER macro. if (readSensor(operand)) { - // reset timer to half a second and keep waiting + // reset timer and keep waiting waitAfter=millis(); delayMe(50); return; } - if (millis()-waitAfter < 500 ) return; + if (millis()-waitAfter < getOperand(1) ) return; break; case OPCODE_AFTEROVERLOAD: // waits for the power to be turned back on - either by power routine or button @@ -717,41 +723,7 @@ void RMFT2::loop2() { case OPCODE_SETFREQ: // Frequency is default 0, or 1, 2,3 - //if (loco) DCC::setFn(loco,operand,true); - switch (operand) { - case 0: // default - all F-s off - if (loco) { - DCC::setFn(loco,29,false); - DCC::setFn(loco,30,false); - DCC::setFn(loco,31,false); - } - break; - case 1: - if (loco) { - DCC::setFn(loco,29,true); - DCC::setFn(loco,30,false); - DCC::setFn(loco,31,false); - } - break; - case 2: - if (loco) { - DCC::setFn(loco,29,false); - DCC::setFn(loco,30,true); - DCC::setFn(loco,31,false); - } - break; - case 3: - if (loco) { - DCC::setFn(loco,29,false); - DCC::setFn(loco,30,false); - DCC::setFn(loco,31,true); - } - break; - default: - ; // do nothing - break; - } - + DCC::setDCFreq(loco,operand); break; case OPCODE_RESUME: @@ -954,11 +926,10 @@ void RMFT2::loop2() { delayMe(100); return; // still waiting for callback } - if (progtrackLocoId<0) { - kill(F("No Loco Found"),progtrackLocoId); - return; // still waiting for callback - } + // At failed read will result in loco == -1 + // which is intended so it can be checked + // from within EXRAIL loco=progtrackLocoId; speedo=0; forward=true; @@ -1001,6 +972,14 @@ void RMFT2::loop2() { if ((compileFeatures & FEATURE_LCC) && LCCSerial) StringFormatter::send(LCCSerial,F(""),(uint16_t)operand); break; + + case OPCODE_ACON: // MERG adapter + case OPCODE_ACOF: + if ((compileFeatures & FEATURE_LCC) && LCCSerial) + StringFormatter::send(LCCSerial,F(""), + opcode==OPCODE_ACON?'0':'1', + (uint16_t)operand,getOperand(progCounter,1)); + break; case OPCODE_LCCX: // long form LCC if ((compileFeatures & FEATURE_LCC) && LCCSerial) @@ -1089,6 +1068,8 @@ void RMFT2::loop2() { case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime case OPCODE_ONCLOSE: // Turnout event catchers ignored here case OPCODE_ONLCC: // LCC event catchers ignored here + case OPCODE_ONACON: // MERG event catchers ignored here + case OPCODE_ONACOF: // MERG event catchers ignored here case OPCODE_ONTHROW: case OPCODE_ONACTIVATE: // Activate event catchers ignored here case OPCODE_ONDEACTIVATE: diff --git a/EXRAIL2.h b/EXRAIL2.h index 8750e41..9271bba 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -69,6 +69,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT, OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE, OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT, OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC, + OPCODE_ACON, OPCODE_ACOF, + OPCODE_ONACON, OPCODE_ONACOF, OPCODE_ONOVERLOAD, OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, OPCODE_ROUTE_DISABLED, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index c799ddf..8f2845c 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -99,6 +99,10 @@ #undef LCCX #undef LCN #undef MOVETT +#undef ACON +#undef ACOF +#undef ONACON +#undef ONACOF #undef MESSAGE #undef ONACTIVATE #undef ONACTIVATEL @@ -191,7 +195,7 @@ #ifndef RMFT2_UNDEF_ONLY #define ACTIVATE(addr,subaddr) #define ACTIVATEL(addr) -#define AFTER(sensor_id) +#define AFTER(sensor_id,timer...) #define AFTEROVERLOAD(track_id) #define ALIAS(name,value...) #define AMBER(signal_id) @@ -265,6 +269,10 @@ #define LCN(msg) #define MESSAGE(msg) #define MOVETT(id,steps,activity) +#define ACON(eventid) +#define ACOF(eventid) +#define ONACON(eventid) +#define ONACOF(eventid) #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) @@ -326,7 +334,7 @@ #define SET_TRACK(track,mode) #define SET_POWER(track,onoff) #define SETLOCO(loco) -#define SETFREQ(loco,freq) +#define SETFREQ(freq) #define SIGNAL(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index 4023633..8c498ab 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -61,47 +61,85 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 case 'L': // This entire code block is compiled out if LLC macros not used if (!(compileFeatures & FEATURE_LCC)) return; - + static int lccProgCounter=0; + static int lccEventIndex=0; + if (paramCount==0) { // LCC adapter introducing self LCCSerial=stream; // now we know where to send events we raise + opcode=0; // flag command as intercepted - // loop through all possible sent events - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); - if (opcode==OPCODE_LCCX) { // long form LCC - StringFormatter::send(stream,F("\n"), + // loop through all possible sent/waited events + for (int progCounter=lccProgCounter;; SKIPOP) { + byte exrailOpcode=GET_OPCODE; + switch (exrailOpcode) { + case OPCODE_ENDEXRAIL: + stream->print(F("\n")); // ready to roll + lccProgCounter=0; // allow a second pass + lccEventIndex=0; + return; + + case OPCODE_LCC: + StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); + SKIPOP; + lccProgCounter=progCounter; + return; + + case OPCODE_LCCX: // long form LCC + StringFormatter::send(stream,F("\n"), getOperand(progCounter,1), getOperand(progCounter,2), getOperand(progCounter,3), getOperand(progCounter,0) - ); - }} + ); + SKIPOP;SKIPOP;SKIPOP;SKIPOP; + lccProgCounter=progCounter; + return; + + case OPCODE_ACON: // CBUS ACON + case OPCODE_ACOF: // CBUS ACOF + StringFormatter::send(stream,F("\n"), + exrailOpcode==OPCODE_ACOF?'1':'0', + getOperand(progCounter,0),getOperand(progCounter,1)); + SKIPOP;SKIPOP; + lccProgCounter=progCounter; + return; // we stream the hex events we wish to listen to // and at the same time build the event index looku. - - int eventIndex=0; - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_ONLCC) { - onLCCLookup[eventIndex]=progCounter; // TODO skip... + case OPCODE_ONLCC: StringFormatter::send(stream,F("\n"), - eventIndex, + lccEventIndex, getOperand(progCounter,1), getOperand(progCounter,2), getOperand(progCounter,3), getOperand(progCounter,0) ); - eventIndex++; - } + SKIPOP;SKIPOP;SKIPOP;SKIPOP; + // start on handler at next + onLCCLookup[lccEventIndex]=progCounter; + lccEventIndex++; + lccProgCounter=progCounter; + return; + + case OPCODE_ONACON: + case OPCODE_ONACOF: + StringFormatter::send(stream,F("\n"), + lccEventIndex, + exrailOpcode==OPCODE_ONACOF?'1':'0', + getOperand(progCounter,0),getOperand(progCounter,1) + ); + SKIPOP;SKIPOP; + // start on handler at next + onLCCLookup[lccEventIndex]=progCounter; + lccEventIndex++; + lccProgCounter=progCounter; + return; + + default: + break; + } } - StringFormatter::send(stream,F("\n")); // Ready to rumble - opcode=0; - break; } if (paramCount==1) { // LCC event arrived from adapter int16_t eventid=p[0]; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 827c1d2..edc1088 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -189,6 +189,14 @@ bool exrailHalSetup() { #define LCCX(senderid,eventid) | FEATURE_LCC #undef ONLCC #define ONLCC(senderid,eventid) | FEATURE_LCC +#undef ACON +#define ACON(eventid) | FEATURE_LCC +#undef ACOF +#define ACOF(eventid) | FEATURE_LCC +#undef ONACON +#define ONACON(eventid) | FEATURE_LCC +#undef ONACOF +#define ONACOF(eventid) | FEATURE_LCC #undef ROUTE_ACTIVE #define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE #undef ROUTE_INACTIVE @@ -429,10 +437,14 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; -// Pass 9 ONLCC counter and lookup array +// Pass 9 ONLCC/ ONMERG counter and lookup array #include "EXRAIL2MacroReset.h" #undef ONLCC #define ONLCC(sender,event) +1 +#undef ONACON +#define ONACON(event) +1 +#undef ONACOF +#define ONACOF(event) +1 const int RMFT2::countLCCLookup=0 #include "myAutomation.h" @@ -451,7 +463,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1), #define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1), -#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), +#define AFTER(sensor_id,timer...) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),OPCODE_PAD,V(#timer[0]?timer+0:500), #define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id), #define ALIAS(name,value...) #define AMBER(signal_id) OPCODE_AMBER,V(signal_id), @@ -529,6 +541,10 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\ OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\ OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF), +#define ACON(eventid) OPCODE_ACON,V(((uint32_t)eventid >>16) & 0xFFFF),OPCODE_PAD,V(eventid & 0xFFFF), +#define ACOF(eventid) OPCODE_ACOF,V(((uint32_t)eventid >>16) & 0xFFFF),OPCODE_PAD,V(eventid & 0xFFFF), +#define ONACON(eventid) OPCODE_ONACON,V((uint32_t)(eventid) >>16),OPCODE_PAD,V(eventid & 0xFFFF), +#define ONACOF(eventid) OPCODE_ONACOF,V((uint32_t)(eventid) >>16),OPCODE_PAD,V(eventid & 0xFFFF), #define LCD(id,msg) PRINT(msg) #define SCREEN(display,id,msg) PRINT(msg) #define STEALTH(code...) PRINT(dummy) @@ -604,7 +620,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track), #define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track), #define SETLOCO(loco) OPCODE_SETLOCO,V(loco), -#define SETFREQ(loco,freq) OPCODE_SETLOCO,V(loco), OPCODE_SETFREQ,V(freq), +#define SETFREQ(freq) OPCODE_SETFREQ,V(freq), #define SIGNAL(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) OPCODE_SPEED,V(speed), diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8fd0549..839dbb0 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202404091507Z" +#define GITHUB_SHA "devel-202409040713Z" diff --git a/IODevice.h b/IODevice.h index 2ba0355..cd60bcc 100644 --- a/IODevice.h +++ b/IODevice.h @@ -547,7 +547,7 @@ protected: #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" #include "IO_trainbrains.h" +#include "IO_EncoderThrottle.h" #include "IO_EXSensorCAM.h" - #endif // iodevice_h diff --git a/IO_EncoderThrottle.cpp b/IO_EncoderThrottle.cpp new file mode 100644 index 0000000..76a5f85 --- /dev/null +++ b/IO_EncoderThrottle.cpp @@ -0,0 +1,144 @@ +/* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins +* to drive a loco. +* Loco id is selected by writeAnalog. +*/ + +#include "IODevice.h" +#include "DIAG.h" +#include "DCC.h" + +const byte _DIR_CW = 0x10; // Clockwise step +const byte _DIR_CCW = 0x20; // Counter-clockwise step + +const byte transition_table[5][4]= { + {0,1,3,0}, // 0: 00 + {1,1,1,2 | _DIR_CW}, // 1: 00->01 + {2,2,0,2}, // 2: 00->01->11 + {3,3,3,4 | _DIR_CCW}, // 3: 00->10 + {4,0,4,4} // 4: 00->10->11 +}; + +const byte _STATE_MASK = 0x07; +const byte _DIR_MASK = 0x30; + + + + void EncoderThrottle::create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch) { + if (checkNoOverlap(firstVpin)) new EncoderThrottle(firstVpin, dtPin,clkPin,clickPin,notch); + } + + + // Constructor + EncoderThrottle::EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch){ + _firstVpin = firstVpin; + _nPins = 1; + _I2CAddress = 0; + _dtPin=dtPin; + _clkPin=clkPin; + _clickPin=clickPin; + _notch=notch; + _locoid=0; + _stopState=xrSTOP; + _rocoState=0; + _prevpinstate=4; // not 01..11 + IODevice::configureInput(dtPin,true); + IODevice::configureInput(clkPin,true); + IODevice::configureInput(clickPin,true); + addDevice(this); + _display(); + } + + + + void EncoderThrottle::_loop(unsigned long currentMicros) { + if (_locoid==0) return; // not in use + + // Clicking down on the roco, stops the loco and sets the direction as unknown. + if (IODevice::read(_clickPin)) { + if (_stopState==xrSTOP) return; // debounced multiple stops + DCC::setThrottle(_locoid,1,DCC::getThrottleDirection(_locoid)); + _stopState=xrSTOP; + DIAG(F("DRIVE %d STOP"),_locoid); + return; + } + + // read roco pins and detect state change + byte pinstate = (IODevice::read(_dtPin) << 1) | IODevice::read(_clkPin); + if (pinstate==_prevpinstate) return; + _prevpinstate=pinstate; + + _rocoState = transition_table[_rocoState & _STATE_MASK][pinstate]; + if ((_rocoState & _DIR_MASK) == 0) return; // no value change + + int change=(_rocoState & _DIR_CW)?+1:-1; + // handle roco change -1 or +1 (clockwise) + + if (_stopState==xrSTOP) { + // first move after button press sets the direction. (clockwise=fwd) + _stopState=change>0?xrFWD:xrREV; + } + + // when going fwd, clockwise increases speed. + // but when reversing, anticlockwise increases speed. + // This is similar to a center-zero pot control but with + // the added safety that you cant panic-spin into the other + // direction. + if (_stopState==xrREV) change=-change; + // manage limits + int oldspeed=DCC::getThrottleSpeed(_locoid); + if (oldspeed==1)oldspeed=0; // break out of estop + int newspeed=change>0 ? (min((oldspeed+_notch),126)) : (max(0,(oldspeed-_notch))); + if (newspeed==1) newspeed=0; // normal decelereated stop. + if (oldspeed!=newspeed) { + DIAG(F("DRIVE %d notch %S %d %S"),_locoid, + change>0?F("UP"):F("DOWN"),_notch, + _stopState==xrFWD?F("FWD"):F("REV")); + DCC::setThrottle(_locoid,newspeed,_stopState==xrFWD); + } +} + + // Selocoid as analog value to start drive + // use + void EncoderThrottle::_writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { + (void) param2; + _locoid=value; + if (param1>0) _notch=param1; + _rocoState=0; + + // If loco is moving, we inherit direction from it. + _stopState=xrSTOP; + if (_locoid>0) { + auto speedbyte=DCC::getThrottleSpeedByte(_locoid); + if ((speedbyte & 0x7f) >1) { + // loco is moving + _stopState= (speedbyte & 0x80)?xrFWD:xrREV; + } + } + _display(); + } + + + void EncoderThrottle::_display() { + DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch); + } + diff --git a/IO_EncoderThrottle.h b/IO_EncoderThrottle.h new file mode 100644 index 0000000..05ce2eb --- /dev/null +++ b/IO_EncoderThrottle.h @@ -0,0 +1,53 @@ +/* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins +* to drive a loco. +* Loco id is selected by writeAnalog. +*/ + +#ifndef IO_EncoderThrottle_H +#define IO_EncoderThrottle_H +#include "IODevice.h" + +class EncoderThrottle : public IODevice { +public: + + static void create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch=10); + +private: + int _dtPin,_clkPin,_clickPin, _locoid, _notch,_prevpinstate; + enum {xrSTOP,xrFWD,xrREV} _stopState; + byte _rocoState; + + // Constructor + EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch); + + void _loop(unsigned long currentMicros) override ; + + // Selocoid as analog value to start drive + // use + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override; + + void _display() override ; + + }; + +#endif diff --git a/KeywordHasher.h b/KeywordHasher.h index d232bd2..4b1e0fb 100644 --- a/KeywordHasher.h +++ b/KeywordHasher.h @@ -26,7 +26,7 @@ Thus "MAIN"_hk generates exactly the same run time vakue as const int16_t HASH_KEYWORD_MAIN=11339 */ -#ifndef KeywordHAsher_h +#ifndef KeywordHasher_h #define KeywordHasher_h #include diff --git a/MotorDriver.cpp b/MotorDriver.cpp index da9d3ee..749c425 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -1,5 +1,6 @@ /* * © 2022-2024 Paul M Antoine + * © 2024 Herb Morton * © 2021 Mike S * © 2021 Fred Decker * © 2020-2023 Harald Barth @@ -98,7 +99,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i if (HAVE_PORTH(fastSignalPin.inout == &PORTH)) { DIAG(F("Found PORTH pin %d"),signalPin); fastSignalPin.shadowinout = fastSignalPin.inout; - fastSignalPin.inout = &shadowPORTF; + fastSignalPin.inout = &shadowPORTH; } signalPin2=signal_pin2; @@ -638,6 +639,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { } throttleInrush(false); setPower(POWERMODE::ON); + break; + } + if (goodtime > POWER_SAMPLE_ALERT_GOOD/2) { + throttleInrush(false); } break; } diff --git a/MotorDrivers.h b/MotorDrivers.h index 9e5f85b..d51ab16 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -97,6 +97,18 @@ new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \ new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/) +// EX-CSB1 with integrated motor driver definition +#define EXCSB1 F("EXCSB1"),\ + new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 2.23, 5000, 19), \ + new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 2.23, 5000, 23) + +// EX-CSB1 with EX-8874 stacked on top for 4 outputs +#define EXCSB1_WITH_EX8874 F("EXCSB1_WITH_EX8874"),\ + new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 2.23, 5000, 19), \ + new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 2.23, 5000, 23), \ + new MotorDriver(26, 5, UNUSED_PIN, 13, 36, 1.52, 5000, 18), \ + new MotorDriver(16, 4, UNUSED_PIN, 12, 39, 1.52, 5000, 17) + #else // STANDARD shield on any Arduino Uno or Mega compatible with the original specification. #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ diff --git a/SerialManager.cpp b/SerialManager.cpp index 88bc7cd..fa7ad38 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -68,7 +68,11 @@ void SerialManager::init() { new SerialManager(&Serial3); #endif #ifdef SERIAL2_COMMANDS +#ifdef ARDUINO_ARCH_ESP32 + Serial2.begin(115200, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32 +#else // not ESP32 Serial2.begin(115200); +#endif // ESP32 new SerialManager(&Serial2); #endif #ifdef SERIAL1_COMMANDS @@ -88,7 +92,11 @@ void SerialManager::init() { } #endif #ifdef SABERTOOTH +#ifdef ARDUINO_ARCH_ESP32 Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32 +#else + Serial2.begin(9600); +#endif #endif } diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 9c69877..192e1cc 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -139,6 +139,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; + case 'L': stream->print(va_arg(args, unsigned long), DEC); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break; diff --git a/TrackManager.cpp b/TrackManager.cpp index 512452d..ba9776b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -1,6 +1,8 @@ /* * © 2022 Chris Harlow * © 2022-2024 Harald Barth + * © 2023-2024 Paul M. Antoine + * © 2024 Herb Morton * © 2023 Colin Murdoch * All rights reserved. * @@ -149,6 +151,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -156,6 +160,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); } // setPROGSignal(), called from interrupt context @@ -167,6 +173,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -174,6 +182,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); } // setDCSignal(), called from normal context @@ -631,23 +641,25 @@ void TrackManager::setJoinRelayPin(byte joinRelayPin) { void TrackManager::setJoin(bool joined) { #ifdef ARDUINO_ARCH_ESP32 - if (joined) { + if (joined) { // if we go into joined mode (PROG acts as MAIN) FOR_EACH_TRACK(t) { - if (track[t]->getMode() & TRACK_MODE_PROG) { - tempProgTrack = t; + if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track + tempProgTrack = t; // remember PROG track setTrackMode(t, TRACK_MODE_MAIN); - break; + track[t]->setPower(POWERMODE::ON); // if joined, always on + break; // there is only one prog track, done } } } else { if (tempProgTrack != MAX_TRACKS+1) { - // as setTrackMode with TRACK_MODE_PROG defaults to - // power off, we will take the current power state - // of our track and then preserve that state. - POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track - setTrackMode(tempProgTrack, TRACK_MODE_PROG); - track[tempProgTrack]->setPower(tPTmode); //set track status as it was before + // setTrackMode defaults to power off, so we + // need to preserve that state. + POWERMODE tPTmode = track[tempProgTrack]->getPower(); // get current power status of this track + setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog + track[tempProgTrack]->setPower(tPTmode); // set power status as it was before tempProgTrack = MAX_TRACKS+1; + } else { + DIAG(F("Unjoin but no remembered prog track")); } } #endif diff --git a/WifiESP32.cpp b/WifiESP32.cpp index e45d0e8..d44672a 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -147,6 +147,12 @@ bool WifiESP::setup(const char *SSid, // enableCoreWDT(1); // disableCoreWDT(0); +#ifdef WIFI_LED + // Turn off Wifi LED + pinMode(WIFI_LED, OUTPUT); + digitalWrite(WIFI_LED, 0); +#endif + // clean start WiFi.mode(WIFI_STA); WiFi.disconnect(true); @@ -247,12 +253,19 @@ bool WifiESP::setup(const char *SSid, // no idea to go on return false; } +#ifdef WIFI_LED + else{ + // Turn on Wifi connected LED + digitalWrite(WIFI_LED, 1); + } +#endif + // Now Wifi is up, register the mDNS service if(!MDNS.begin(hostname)) { DIAG(F("Wifi setup failed to start mDNS")); } - if(!MDNS.addService("withrottle", "tcp", 2560)) { + if(!MDNS.addService("withrottle", "tcp", port)) { DIAG(F("Wifi setup failed to add withrottle service to mDNS")); } diff --git a/config.example.h b/config.example.h index 8d63583..26b8cf4 100644 --- a/config.example.h +++ b/config.example.h @@ -1,295 +1,338 @@ -/* - * © 2022 Paul M. Antoine - * © 2021 Neil McKechnie - * © 2020-2023 Harald Barth - * © 2020-2021 Fred Decker - * © 2020-2021 Chris Harlow - * © 2023 Nathan Kellenicki - * - * This file is part of CommandStation-EX - * - * This is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * It is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -/********************************************************************** - -The configuration file for DCC-EX Command Station - -**********************************************************************/ - -///////////////////////////////////////////////////////////////////////////////////// -// If you want to add your own motor driver definition(s), add them here -// For example MY_SHIELD with display name "MINE": -// (remove comment start and end marker if you want to edit and use that) -/* -#define MY_SHIELD F("MINE"), \ - new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5) -*/ - -///////////////////////////////////////////////////////////////////////////////////// -// NOTE: Before connecting these boards and selecting one in this software -// check the quick install guides!!! Some of these boards require a voltage -// generating resistor on the current sense pin of the device. Failure to select -// the correct resistor could damage the sense pin on your Arduino or destroy -// the device. -// -// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h -// -// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel -// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) -// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) -// FIREBOX_MK1 : The Firebox MK1 -// FIREBOX_MK1S : The Firebox MK1S -// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN -// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield -// | -// +-----------------------v -// -#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD -// -///////////////////////////////////////////////////////////////////////////////////// -// -// If you want to restrict the maximum current LOWER than what your -// motor shield can provide, you can do that here. For example if you -// have a motor shield that can provide 5A and your power supply can -// only provide 2.5A then you should restict the maximum current to -// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before -// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example -// it would be 2250, adjust the number according to your PS. If your -// PS has a higher rating than your motor shield you do not need this. -// You can use this as well if you are cautious and your trains do not -// need full current. -// #define MAX_CURRENT 2250 -// -///////////////////////////////////////////////////////////////////////////////////// -// -// The IP port to talk to a WIFI or Ethernet shield. -// -#define IP_PORT 2560 - -///////////////////////////////////////////////////////////////////////////////////// -// -// NOTE: Only supported on Arduino Mega -// Set to false if you not even want it on the Arduino Mega -// -#define ENABLE_WIFI false //true - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE WiFi Parameters (only in effect if WIFI is on) -// -// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with -// the <+> commands and this sketch will not change anything over -// AT commands and the other WIFI_* defines below do not have any effect. -//#define DONT_TOUCH_WIFI_CONF -// -// WIFI_SSID is the network name IF you want to use your existing home network. -// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. -// -// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD, -// then the WiFi chip will first try to connect to the previously -// configured network and if that fails fall back to Access Point mode. -// The SSID of the AP will be automatically set to DCCEX_*. -// If you DO set the WIFI_SSID then the WiFi chip will try to connect -// to that (home) network in station (client) mode. If a WIFI_PASSWORD -// is set (recommended), that password will be used for AP mode. -// The AP mode password must be at least 8 characters long. -// -// Your SSID may not contain ``"'' (double quote, ASCII 0x22). -#define WIFI_SSID "Your network name" -// -// WIFI_PASSWORD is the network password for your home network or if -// you want to change the password from default AP mode password -// to the AP password you want. -// Your password may not contain ``"'' (double quote, ASCII 0x22). -#define WIFI_PASSWORD "Your network passwd" -// -// WIFI_HOSTNAME: You probably don't need to change this -#define WIFI_HOSTNAME "dccex" -// -// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented, -// WiFi will be enabled (Mega only). The default channel is set to "1" whether -// this line exists or not. If you need to use an alternate channel (we recommend -// using only 1,6, or 11) you may change it here. -#define WIFI_CHANNEL 1 -// -// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this -// true. Otherwise it is assumed that you'd like to connect to an existing network -// with that SSID. -#define WIFI_FORCE_AP false - -///////////////////////////////////////////////////////////////////////////////////// -// -// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This -// is not for Wifi. You will then need the Arduino Ethernet library as well -// -//#define ENABLE_ETHERNET true - - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP -// -//#define IP_ADDRESS { 192, 168, 1, 200 } - - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE LCD SCREEN USAGE BY THE BASE STATION -// -// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780 -// controller and a commonly available PCF8574 based I2C 'backpack'. -// To enable, uncomment one of the #define lines below - -// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows -// #define LCD_DRIVER 0x27,16,2 - -//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied) -// 128x32 or 128x64 I2C SSD1306-based devices are supported. -// Use 132,64 for a SH1106-based I2C device with a 128x64 display. -// #define OLED_DRIVER 0x3c,128,32 - -// Define scroll mode as 0, 1 or 2 -// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), -// * #define SCROLLMODE 1 is by page (alternate between pages), -// * #define SCROLLMODE 2 is by row (move up 1 row at a time). -#define SCROLLMODE 1 - -///////////////////////////////////////////////////////////////////////////////////// -// DISABLE EEPROM -// -// If you do not need the EEPROM at all, you can disable all the code that saves -// data in the EEPROM. You might want to do that if you are in a Arduino UNO -// and want to use the EXRAIL automation. Otherwise you do not have enough RAM -// to do that. Of course, then none of the EEPROM related commands work. -// -// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled, -// at least until it works. -// -// #define DISABLE_EEPROM - -///////////////////////////////////////////////////////////////////////////////////// -// DISABLE PROG -// -// If you do not need programming capability, you can disable all programming related -// commands. You might want to do that if you are using an Arduino UNO and still want -// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both. -// -// Note this disables all programming functionality, including EXRAIL. -// -// #define DISABLE_PROG - -///////////////////////////////////////////////////////////////////////////////////// -// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address -// is 127 and the first long address is 128. There are manufacturers which have -// another view. Lenz CS for example have considered addresses long from 100. If -// you want to change to that mode, do -//#define HIGHEST_SHORT_ADDR 99 -// If you want to run all your locos addressed long format, you could even do a -//#define HIGHEST_SHORT_ADDR 0 -// We do not support to use the same address, for example 100(long) and 100(short) -// at the same time, there must be a border. - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213 -// -// According to norm RCN-213 a DCC packet with a 1 is closed/straight -// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous -// versions of DCC++EX, a turnout throw command was implemented in the packet as -// '1' and a close command as '0'. The #define below makes the states -// match with the norm. But we don't want to cause havoc on existent layouts, -// so we define this only for new installations. If you don't want this, -// don't add it to your config.h. -//#define DCC_TURNOUTS_RCN_213 - -// By default, the driver which defines a DCC accessory decoder -// does send out the same state change on the DCC packet as it -// receives. This means a VPIN state=1 sends D=1 (close turnout -// or signal green) in the DCC packet. This can be reversed if -// necessary. -//#define HAL_ACCESSORY_COMMAND_REVERSE - -// If you have issues with that the direction of the accessory commands is -// reversed (for example when converting from another CS to DCC-EX) then -// you can use this to reverse the sense of all accessory commmands sent -// over DCC++. This #define likewise inverts the behaviour of the command -// for triggering DCC Accessory Decoders, so that generates a -// DCC packet with D=1 (close turnout) and generates D=0 -// (throw turnout). -//#define DCC_ACCESSORY_RCN_213 -// -// HANDLING MULTIPLE SERIAL THROTTLES -// The command station always operates with the default Serial port. -// Diagnostics are only emitted on the default serial port and not broadcast. -// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4, -// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3, -// SAMD/SAMC and STM32 have up to 6.) -// To monitor a throttle on one or more serial ports, uncomment the defines below. -// NOTE: do not define here the WiFi shield serial port or your wifi will not work. -// -//#define SERIAL1_COMMANDS -//#define SERIAL2_COMMANDS -//#define SERIAL3_COMMANDS -//#define SERIAL4_COMMANDS -//#define SERIAL5_COMMANDS -//#define SERIAL6_COMMANDS -// -// BLUETOOTH SERIAL ON ESP32 -// On ESP32 you have the possibility to use the builtin BT serial to connect to -// the CS. -// -// The CS shows up as a pairable BT Clasic device. Name is "DCCEX-hexnumber". -// BT is as an additional serial port, debug messages are still sent over USB, -// not BT serial. -// -// If you enable this there are some implications: -// 1. WiFi will sleep more (as WiFi and BT share the radio. So WiFi performance -// may suffer -// 2. The app will be bigger that 1.2MB, so the default partition scheme will not -// work any more. You need to choose a partition scheme with 2MB (or bigger). -// For example "NO OTA (2MB APP, 2MB SPIFFS)" in the Arduino IDE. -// 3. There is no securuity (PIN) implemented. Everyone in radio range can pair -// with your CS. -// -//#define SERIAL_BT_COMMANDS - -// SABERTOOTH -// -// This is a very special option and only useful if you happen to have a -// sabertooth motor controller from dimension engineering configured to -// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX) -// and GPIO16 (RX, currently unused). -// The number defined is the DCC address for which speed controls are sent -// to the sabertooth controller _as_well_. Default: Undefined. -// -//#define SABERTOOTH 1 -// -///////////////////////////////////////////////////////////////////////////////////// -// -// SENSORCAM -// ESP32-CAM based video sensors require #define to use appropriate base vpin number. -#define SENSORCAM_VPIN 700 -// For shortcut to vPin number, define CAM for ex-rail use e.g. AT(CAM 012) for S12 etc. -#define CAM SENSORCAM_VPIN+ - -//#define SENSORCAM2_VPIN 600 //define other CAM's if installed. -//#define CAM2 SENSORCAM2_VPIN+ //for EX-RAIL commands e.g. IFLT(CAM2 020,1)1 -// -// For smoother power-up, define a STARTUP_DELAY to allow CAM to initialise ref images -#define STARTUP_DELAY 5000 // up to 20sec. CS delay - - -///////////////////////////////////////////////////////////////////////////////////// +/* + * © 2022 Paul M. Antoine + * © 2021 Neil McKechnie + * © 2020-2023 Harald Barth + * © 2020-2021 Fred Decker + * © 2020-2021 Chris Harlow + * © 2023 Nathan Kellenicki + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/********************************************************************** + +The configuration file for DCC-EX Command Station + +**********************************************************************/ + +///////////////////////////////////////////////////////////////////////////////////// +// If you want to add your own motor driver definition(s), add them here +// For example MY_SHIELD with display name "MINE": +// (remove comment start and end marker if you want to edit and use that) +/* +#define MY_SHIELD F("MINE"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5) +*/ + +///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Before connecting these boards and selecting one in this software +// check the quick install guides!!! Some of these boards require a voltage +// generating resistor on the current sense pin of the device. Failure to select +// the correct resistor could damage the sense pin on your Arduino or destroy +// the device. +// +// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h +// +// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel +// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) +// FIREBOX_MK1 : The Firebox MK1 +// FIREBOX_MK1S : The Firebox MK1S +// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN +// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield +// | +// +-----------------------v +// +#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD +// +///////////////////////////////////////////////////////////////////////////////////// +// +// If you want to restrict the maximum current LOWER than what your +// motor shield can provide, you can do that here. For example if you +// have a motor shield that can provide 5A and your power supply can +// only provide 2.5A then you should restict the maximum current to +// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before +// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example +// it would be 2250, adjust the number according to your PS. If your +// PS has a higher rating than your motor shield you do not need this. +// You can use this as well if you are cautious and your trains do not +// need full current. +// #define MAX_CURRENT 2250 +// +///////////////////////////////////////////////////////////////////////////////////// +// +// The IP port to talk to a WIFI or Ethernet shield. +// +#define IP_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only supported on Arduino Mega +// Set to false if you not even want it on the Arduino Mega +// +#define ENABLE_WIFI true + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE WiFi Parameters (only in effect if WIFI is on) +// +// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with +// the <+> commands and this sketch will not change anything over +// AT commands and the other WIFI_* defines below do not have any effect. +//#define DONT_TOUCH_WIFI_CONF +// +// WIFI_SSID is the network name IF you want to use your existing home network. +// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. +// +// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD, +// then the WiFi chip will first try to connect to the previously +// configured network and if that fails fall back to Access Point mode. +// The SSID of the AP will be automatically set to DCCEX_*. +// If you DO set the WIFI_SSID then the WiFi chip will try to connect +// to that (home) network in station (client) mode. If a WIFI_PASSWORD +// is set (recommended), that password will be used for AP mode. +// The AP mode password must be at least 8 characters long. +// +// Your SSID may not contain ``"'' (double quote, ASCII 0x22). +#define WIFI_SSID "Your network name" +// +// WIFI_PASSWORD is the network password for your home network or if +// you want to change the password from default AP mode password +// to the AP password you want. +// Your password may not contain ``"'' (double quote, ASCII 0x22). +#define WIFI_PASSWORD "Your network passwd" +// +// WIFI_HOSTNAME: You probably don't need to change this +#define WIFI_HOSTNAME "dccex" +// +// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented, +// WiFi will be enabled (Mega only). The default channel is set to "1" whether +// this line exists or not. If you need to use an alternate channel (we recommend +// using only 1,6, or 11) you may change it here. +#define WIFI_CHANNEL 1 +// +// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this +// true. Otherwise it is assumed that you'd like to connect to an existing network +// with that SSID. +#define WIFI_FORCE_AP false + +///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This +// is not for Wifi. You will then need the Arduino Ethernet library as well +// +//#define ENABLE_ETHERNET true + + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// +//#define IP_ADDRESS { 192, 168, 1, 200 } + + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE LCD SCREEN USAGE BY THE BASE STATION +// +// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780 +// controller and a commonly available PCF8574 based I2C 'backpack'. +// To enable, uncomment one of the #define lines below + +// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows +// #define LCD_DRIVER 0x27,16,2 + +//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied) +// 128x32 or 128x64 I2C SSD1306-based devices are supported. +// Use 132,64 for a SH1106-based I2C device with a 128x64 display. +// #define OLED_DRIVER 0x3c,128,32 + +// Define scroll mode as 0, 1 or 2 +// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), +// * #define SCROLLMODE 1 is by page (alternate between pages), +// * #define SCROLLMODE 2 is by row (move up 1 row at a time). +#define SCROLLMODE 1 + +// In order to avoid wasting memory the current scroll buffer is limited +// to 8 lines. Some users wishing to display additional information +// such as TrackManager power states have requested additional rows aware +// of the warning that this will take extra RAM. if you wish to include additional rows +// uncomment the following #define and set the number of lines you need. +//#define MAX_CHARACTER_ROWS 12 + + +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE EEPROM +// +// If you do not need the EEPROM at all, you can disable all the code that saves +// data in the EEPROM. You might want to do that if you are in a Arduino UNO +// and want to use the EXRAIL automation. Otherwise you do not have enough RAM +// to do that. Of course, then none of the EEPROM related commands work. +// +// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled, +// at least until it works. +// +// #define DISABLE_EEPROM + +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE PROG +// +// If you do not need programming capability, you can disable all programming related +// commands. You might want to do that if you are using an Arduino UNO and still want +// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both. +// +// Note this disables all programming functionality, including EXRAIL. +// +// #define DISABLE_PROG + +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE / ENABLE VDPY +// +// The Virtual display "VDPY" feature is by default enabled everywhere +// but on Uno and Nano. If you think you can fit it (for example +// having disabled some of the features above) you can enable it with +// ENABLE_VDPY. You can even disable it on all other CPUs with +// DISABLE_VDPY +// +// #define DISABLE_VDPY +// #define ENABLE_VDPY + +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE / ENABLE DIAG +// +// To diagose different errors, you can turn on differnet messages. This costs +// program memory which we do not have enough on the Uno and Nano, so it is +// by default DISABLED on those. If you think you can fit it (for example +// having disabled some of the features above) you can enable it with +// ENABLE_DIAG. You can even disable it on all other CPUs with +// DISABLE_DIAG +// +// #define DISABLE_DIAG +// #define ENABLE_DIAG + +///////////////////////////////////////////////////////////////////////////////////// +// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address +// is 127 and the first long address is 128. There are manufacturers which have +// another view. Lenz CS for example have considered addresses long from 100. If +// you want to change to that mode, do +//#define HIGHEST_SHORT_ADDR 99 +// If you want to run all your locos addressed long format, you could even do a +//#define HIGHEST_SHORT_ADDR 0 +// We do not support to use the same address, for example 100(long) and 100(short) +// at the same time, there must be a border. + +///////////////////////////////////////////////////////////////////////////////////// +// Some newer 32bit microcontrollers boot very quickly, so powering on I2C and other +// peripheral devices at the same time may result in the CommandStation booting too +// quickly to detect them. +// To work around this, uncomment the STARTUP_DELAY line below and set a value in +// milliseconds that works for your environment, default is 3000 (3 seconds). +// #define STARTUP_DELAY 3000 + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213 +// +// According to norm RCN-213 a DCC packet with a 1 is closed/straight +// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous +// versions of DCC++EX, a turnout throw command was implemented in the packet as +// '1' and a close command as '0'. The #define below makes the states +// match with the norm. But we don't want to cause havoc on existent layouts, +// so we define this only for new installations. If you don't want this, +// don't add it to your config.h. +//#define DCC_TURNOUTS_RCN_213 + +// By default, the driver which defines a DCC accessory decoder +// does send out the same state change on the DCC packet as it +// receives. This means a VPIN state=1 sends D=1 (close turnout +// or signal green) in the DCC packet. This can be reversed if +// necessary. +//#define HAL_ACCESSORY_COMMAND_REVERSE + +// If you have issues with that the direction of the accessory commands is +// reversed (for example when converting from another CS to DCC-EX) then +// you can use this to reverse the sense of all accessory commmands sent +// over DCC++. This #define likewise inverts the behaviour of the command +// for triggering DCC Accessory Decoders, so that generates a +// DCC packet with D=1 (close turnout) and generates D=0 +// (throw turnout). +//#define DCC_ACCESSORY_RCN_213 +// +// HANDLING MULTIPLE SERIAL THROTTLES +// The command station always operates with the default Serial port. +// Diagnostics are only emitted on the default serial port and not broadcast. +// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4, +// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3, +// SAMD/SAMC and STM32 have up to 6.) +// To monitor a throttle on one or more serial ports, uncomment the defines below. +// NOTE: do not define here the WiFi shield serial port or your wifi will not work. +// +//#define SERIAL1_COMMANDS +//#define SERIAL2_COMMANDS +//#define SERIAL3_COMMANDS +//#define SERIAL4_COMMANDS +//#define SERIAL5_COMMANDS +//#define SERIAL6_COMMANDS +// +// BLUETOOTH SERIAL ON ESP32 +// On ESP32 you have the possibility to use the builtin BT serial to connect to +// the CS. +// +// The CS shows up as a pairable BT Clasic device. Name is "DCCEX-hexnumber". +// BT is as an additional serial port, debug messages are still sent over USB, +// not BT serial. +// +// If you enable this there are some implications: +// 1. WiFi will sleep more (as WiFi and BT share the radio. So WiFi performance +// may suffer +// 2. The app will be bigger that 1.2MB, so the default partition scheme will not +// work any more. You need to choose a partition scheme with 2MB (or bigger). +// For example "NO OTA (2MB APP, 2MB SPIFFS)" in the Arduino IDE. +// 3. There is no securuity (PIN) implemented. Everyone in radio range can pair +// with your CS. +// +//#define SERIAL_BT_COMMANDS + +// BOOSTER PIN INPUT ON ESP32 CS +// On ESP32 you have the possibility to define a pin as booster input +// +// Arduino pin D2 is GPIO 26 is Booster Input on ESPDuino32 +//#define BOOSTER_INPUT 26 +// +// GPIO 32 is Booster Input on EX-CSB1 +//#define BOOSTER_INPUT 32 + +// ESP32 LED Wifi Indicator +// GPIO 2 on ESPduino32 +//#define WIFI_LED 2 +// +// GPIO 33 on EX-CSB1 +//#define WIFI_LED 33 + +// SABERTOOTH +// +// This is a very special option and only useful if you happen to have a +// sabertooth motor controller from dimension engineering configured to +// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX) +// and GPIO16 (RX, currently unused). +// The number defined is the DCC address for which speed controls are sent +// to the sabertooth controller _as_well_. Default: Undefined. +// +//#define SABERTOOTH 1 + +///////////////////////////////////////////////////////////////////////////////////// + diff --git a/platformio.ini b/platformio.ini index a03ff61..5cc5008 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,9 +11,9 @@ [platformio] default_envs = mega2560 - uno - unowifiR2 - nano +; uno +; unowifiR2 +; nano samd21-dev-usb samd21-zero-usb ESP32 @@ -164,7 +164,11 @@ monitor_echo = yes build_flags = -mcall-prologues [env:ESP32] -platform = espressif32 +; Lock version to 6.7.0 as that is +; Arduino v2.0.16 (based on IDF v4.4.7) +; which is the latest version based +; on IDF v4. We can not use IDF v5. +platform = espressif32 @ 6.7.0 board = esp32dev framework = arduino lib_deps = ${env.lib_deps} diff --git a/version.h b/version.h index 2079a1e..2220087 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,34 @@ #include "StringFormatter.h" -#define VERSION "5.2.59" +#define VERSION "5.2.76" +// 5.2.76 - Bugfix: EXRAIL: Catch CV read errors in the callback +// 5.2.75 - Bugfix: Serial lines 4 to 6 OK +// 5.2.74 - Bugfix: ESP32 turn on the joined prog (as main) again after a prog operation +// 5.2.73 - Bugfix: STM32 further fixes to shadowPORT entries in TrackManager.cpp for PORTG and PORTH +// 5.2.72 - Bugfix: added shadowPORT entries in TrackManager.cpp for PORTG and PORTH on STM32, fixed typo in MotorDriver.cpp +// 5.2.71 - Broadcasts of loco forgets. +// 5.2.70 - IO_RocoDriver renamed to IO_EncoderThrottle. +// - and included in IODEvice.h (circular dependency removed) +// 5.2.69 - IO_RocoDriver. Direct drive train with rotary encoder hw. +// 5.2.68 - Revert function map to signed (from 5.2.66) to avoid +// incompatibilities with ED etc for F31 frequency flag. +// 5.2.67 - EXRAIL AFTER optional debounce time variable (default 500mS) +// - AFTER(42) == AFTER(42,500) sets time sensor must +// - be continuously off. +// 5.2.66 - +// - EXRAIL SETFREQ drop loco param (breaking since 5.2.28) +// 5.2.65 - Speedup Exrail SETFREQ +// 5.2.64 - Bugfix: <0 PROG> updated to undo JOIN +// 5.2.63 - Implement WIFI_LED for ESP32, ESPduino32 and EX-CSB1, that is turned on when STA mode connects or AP mode is up +// - Add BOOSTER_INPUT definitions for ESPduino32 and EX-CSB1 to config.example.h +// - Add WIFI_LED definitions for ESPduino32 and EX-CSB1 to config.example.h +// 5.2.62 - Allow acks way longer than standard +// 5.2.61 - Merg CBUS ACON/ACOF/ONACON/ONACOF Adapter interface. +// - LCC Adapter interface throttled startup, +// (Breaking change with Adapter base code) +// 5.2.60 - Bugfix: Opcode AFTEROVERLOAD does not have an argument that is a pin and needs to be initialized +// - Remove inrush throttle after half good time so that we go to mode overload if problem persists // 5.2.59 - STM32 bugfix correct Serial1 definition for Nucleo-F401RE // - STM32 add support for ARDUINO_NUCLEO_F4X9ZI type to span F429/F439 in upcoming STM32duino release v2.8 as a result of our PR // 5.2.58 - EXRAIL ALIAS allows named pins