From 4e57a80265c7e4c716574fc7f7a304e122013ada Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 23 Jul 2024 15:42:35 +0100 Subject: [PATCH 1/6] Squashed commit of the following: commit 3ac2fff70d2339992654a7b0da5784dcc9450fcb Author: Asbelos Date: Tue Jul 23 15:40:36 2024 +0100 Create momentum.md commit a08195332f2387d5a9e4ad32bb4128fe0d4ad406 Author: Asbelos Date: Mon Jul 22 21:57:47 2024 +0100 Cleanup of DCC Class reminders commit 002ec5f176645b3a616a387b0357f54d84f99ede Author: Asbelos Date: Mon Jul 22 12:42:43 2024 +0100 Cleaning access to speedByte commit 854ddb0c6c696c808586eb8febaabb52261266bb Author: Asbelos Date: Sun Jul 21 10:15:07 2024 +0100 Fix momentum algorithm commit 916d3baf63eeab5d49682e748ba15a527cd8afa9 Merge: ab72a75 27dc805 Author: Asbelos Date: Fri Jul 19 10:14:06 2024 +0100 Merge branch 'devel' into devel_momentum commit ab72a75d8fe4ac3859655ede97de2f25973a8864 Author: Asbelos Date: Fri Jul 19 08:33:50 2024 +0100 EXRAIL MOMENTUM commit 8a623aa1cbd6616126983e76ecb2fb0c3595b177 Author: Asbelos Date: Thu Jul 18 20:31:58 2024 +0100 Momentum --- CommandDistributor.cpp | 10 +- CommandDistributor.h | 3 +- DCC.cpp | 289 +++++++++++++++++++++++--------------- DCC.h | 16 ++- DCCEXParser.cpp | 19 +-- EXRAIL2.cpp | 6 +- EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 2 + EXRAIL2Parser.cpp | 2 +- EXRAILMacros.h | 1 + Release_Notes/momentum.md | 20 +++ TrackManager.cpp | 2 +- 12 files changed, 237 insertions(+), 134 deletions(-) create mode 100644 Release_Notes/momentum.md diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index d2cf3f9..4c6fa46 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -207,9 +207,13 @@ int16_t CommandDistributor::retClockTime() { return lastclocktime; } -void CommandDistributor::broadcastLoco(byte slot) { - DCC::LOCO * sp=&DCC::speedTable[slot]; - broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); +void CommandDistributor::broadcastLoco(DCC::LOCO* sp) { + if (!sp) { + broadcastReply(COMMAND_TYPE,F("\n")); + return; + } + broadcastReply(COMMAND_TYPE, F("\n"), + sp->loco,sp->targetSpeed,sp->functions); #ifdef SABERTOOTH if (Serial2 && sp->loco == SABERTOOTH) { static uint8_t rampingmode = 0; diff --git a/CommandDistributor.h b/CommandDistributor.h index d86b87f..c2e600d 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -28,6 +28,7 @@ #include "StringBuffer.h" #include "defines.h" #include "EXRAIL2.h" +#include "DCC.h" #if WIFI_ON | ETHERNET_ON // Command Distributor must handle a RingStream of clients @@ -46,7 +47,7 @@ private: #endif public : static void parse(byte clientId,byte* buffer, RingStream * ring); - static void broadcastLoco(byte slot); + static void broadcastLoco(DCC::LOCO * slot); static void broadcastForgetLoco(int16_t loco); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); diff --git a/DCC.cpp b/DCC.cpp index e60cf25..9bbd626 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -60,6 +60,8 @@ const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; byte DCC::globalSpeedsteps=128; +#define SLOTLOOP for (auto slot=&speedTable[0];slot!=&speedTable[MAX_LOCOS];slot++) + void DCC::begin() { StringFormatter::send(&USB_SERIAL,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); #ifndef DISABLE_EEPROM @@ -72,13 +74,29 @@ void DCC::begin() { #endif } +int16_t DCC::defaultMomentum=0; void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { + if (cab==0) { + if (tSpeed==1) estopAll(); // ESTOP broadcast fix + return; + } byte speedCode = (tSpeed & 0x7F) + tDirection * 128; - setThrottle2(cab, speedCode); - TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr - // retain speed for loco reminders - updateLocoReminder(cab, speedCode ); + LOCO * slot=lookupSpeedTable(cab); + if (slot->targetSpeed==speedCode) return; + slot->targetSpeed=speedCode; + auto momentum=slot->millis_per_notch; + if (momentum<0) momentum=defaultMomentum; + if (momentum>0 && tSpeed!=1) { // not ESTOP + // we dont throttle speed, we just let the reminders take it to target + slot->momentum_base=millis(); + } + else { // Momentum not involved, throttle now. + slot->speedCode = speedCode; + setThrottle2(cab, speedCode); + TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr + } + CommandDistributor::broadcastLoco(slot); } void DCC::setThrottle2( uint16_t cab, byte speedCode) { @@ -139,18 +157,22 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) { // returns speed steps 0 to 127 (1 == emergency stop) // or -1 on "loco not found" int8_t DCC::getThrottleSpeed(int cab) { - int reg=lookupSpeedTable(cab); - if (reg<0) return -1; - return speedTable[reg].speedCode & 0x7F; + return getThrottleSpeedByte(cab) & 0x7F; } // returns speed code byte // or 128 (speed 0, dir forward) on "loco not found". +// This is the throttle set speed uint8_t DCC::getThrottleSpeedByte(int cab) { - int reg=lookupSpeedTable(cab); - if (reg<0) - return 128; - return speedTable[reg].speedCode; + LOCO * slot=lookupSpeedTable(cab,false); + return slot?slot->targetSpeed:128; +} +// returns speed code byte for loco. +// This is the most recently send DCC speed packet byte +// or 128 (speed 0, dir forward) on "loco not found". +uint8_t DCC::getLocoSpeedByte(int cab) { + LOCO* slot=lookupSpeedTable(cab,false); + return slot?slot->speedCode:128; } // returns 0 to 7 for frequency @@ -159,12 +181,11 @@ uint8_t DCC::getThrottleFrequency(int cab) { (void)cab; return 0; #else - int reg=lookupSpeedTable(cab); - if (reg<0) - return 0; // use default frequency + LOCO* slot=lookupSpeedTable(cab); + if (!slot) return 0; // use default frequency // shift out first 29 bits so we have the 3 "frequency bits" left - uint8_t res = (uint8_t)(speedTable[reg].functions >>29); - //DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res); + uint8_t res = (uint8_t)(slot->functions >>29); + //DIAG(F("Speed table %d functions %l shifted %d"), reg, slot->functions, res); return res; #endif } @@ -172,9 +193,7 @@ uint8_t DCC::getThrottleFrequency(int cab) { // returns direction on loco // or true/forward on "loco not found" bool DCC::getThrottleDirection(int cab) { - int reg=lookupSpeedTable(cab); - if (reg<0) return true; - return (speedTable[reg].speedCode & 0x80) !=0; + return getThrottleSpeedByte(cab) % 0x80; } // Set function to value on or off @@ -207,22 +226,21 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) { if (functionNumber > 31) return true; - int reg = lookupSpeedTable(cab); - if (reg<0) return false; - + LOCO * slot = lookupSpeedTable(cab); + // Take care of functions: // Set state of function - uint32_t previous=speedTable[reg].functions; + uint32_t previous=slot->functions; uint32_t funcmask = (1UL<functions |= funcmask; } else { - speedTable[reg].functions &= ~funcmask; + slot->functions &= ~funcmask; } - if (speedTable[reg].functions != previous) { + if (slot->functions != previous) { if (functionNumber <= 28) - updateGroupflags(speedTable[reg].groupFlags, functionNumber); - CommandDistributor::broadcastLoco(reg); + updateGroupflags(slot->groupFlags, functionNumber); + CommandDistributor::broadcastLoco(slot); } return true; } @@ -230,14 +248,13 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) { // Flip function state (used from withrottle protocol) void DCC::changeFn( int cab, int16_t functionNumber) { if (cab<=0 || functionNumber>31) return; - int reg = lookupSpeedTable(cab); - if (reg<0) return; + auto slot=lookupSpeedTable(cab); unsigned long funcmask = (1UL<functions ^= funcmask; if (functionNumber <= 28) { - updateGroupflags(speedTable[reg].groupFlags, functionNumber); + updateGroupflags(slot->groupFlags, functionNumber); } - CommandDistributor::broadcastLoco(reg); + CommandDistributor::broadcastLoco(slot); } // Report function state (used from withrottle protocol) @@ -245,12 +262,10 @@ void DCC::changeFn( int cab, int16_t functionNumber) { int8_t DCC::getFn( int cab, int16_t functionNumber) { if (cab<=0 || functionNumber>31) return -1; // unknown - int reg = lookupSpeedTable(cab); - if (reg<0) - return -1; - + auto slot = lookupSpeedTable(cab); + unsigned long funcmask = (1UL<functions & funcmask)? 1 : 0; } // Set the group flag to say we have touched the particular group. @@ -267,22 +282,22 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) { uint32_t DCC::getFunctionMap(int cab) { if (cab<=0) return 0; // unknown pretend all functions off - int reg = lookupSpeedTable(cab); - return (reg<0)?0:speedTable[reg].functions; + auto slot = lookupSpeedTable(cab,false); + return slot?slot->functions:0; } // saves DC frequency (0..3) in spare functions 29,30,31 void DCC::setDCFreq(int cab,byte freq) { if (cab==0 || freq>3) return; - auto reg=lookupSpeedTable(cab,true); + auto slot=lookupSpeedTable(cab,true); // drop and replace F29,30,31 (top 3 bits) - auto newFunctions=speedTable[reg].functions & 0x1FFFFFFFUL; + auto newFunctions=slot->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); + if (newFunctions==slot->functions) return; // no change + slot->functions=newFunctions; + CommandDistributor::broadcastLoco(slot); } void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) { @@ -738,10 +753,9 @@ void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) { void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco setThrottle2(cab,1); // ESTOP this loco if still on track - int reg=lookupSpeedTable(cab, false); - if (reg>=0) { - speedTable[reg].loco=0; - setThrottle2(cab,1); // ESTOP if this loco still on track + auto slot=lookupSpeedTable(cab, false); + if (slot) { + slot->loco=-1; // no longer used but not end of world CommandDistributor::broadcastForgetLoco(cab); } } @@ -749,7 +763,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders setThrottle2(0,1); // ESTOP all locos still on track for (int i=0;i highestUsedReg) reg = 0; // Go to start of table - if (speedTable[reg].loco > 0) { - // have found loco to remind - if (issueReminder(reg)) - lastLocoReminder = reg; - } else - lastLocoReminder = reg; + auto slot = nextLocoReminder; + if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table + if (slot->loco > 0) + if (!issueReminder(slot)) + return; + // a loco=0 is at the end of the list, a loco <0 is deleted + if (slot->loco==0) nextLocoReminder = &speedTable[0]; + else nextLocoReminder=slot+1; } -bool DCC::issueReminder(int reg) { - unsigned long functions=speedTable[reg].functions; - int loco=speedTable[reg].loco; - byte flags=speedTable[reg].groupFlags; +int16_t normalize(byte speed) { + if (speed & 0x80) return speed & 0x7F; + return 0-1-speed; +} +byte dccalize(int16_t speed) { + if (speed>127) return 0xFF; // 127 forward + if (speed<-127) return 0x7F; // 127 reverse + if (speed >=0) return speed | 0x80; + // negative speeds... -1==dcc 0, -2==dcc 1 + return (int16_t)-1 - speed; +} + +bool DCC::issueReminder(LOCO * slot) { + unsigned long functions=slot->functions; + int loco=slot->loco; + byte flags=slot->groupFlags; switch (loopStatus) { - case 0: - // DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode); - setThrottle2(loco, speedTable[reg].speedCode); - break; + case 0: { + // calculate any momentum change going on + auto sc=slot->speedCode; + if (slot->targetSpeed!=sc) { + // calculate new speed code + auto now=millis(); + int16_t delay=now-slot->momentum_base; + auto millisPerNotch=slot->millis_per_notch; + if (millisPerNotch<0) millisPerNotch=defaultMomentum; + // allow for momentum change to 0 while accelerating/slowing + auto ticks=(millisPerNotch>0)?(delay/millisPerNotch):500; + if (ticks>0) { + auto current=normalize(sc); // -128..+127 + auto target=normalize(slot->targetSpeed); + // DIAG(F("Momentum l=%d ti=%d sc=%d c=%d t=%d"),loco,ticks,sc,current,target); + if (currenttarget) current=target; + } + else { // slow + current-=ticks; + if (currentspeedCode=sc; + TrackManager::setDCSignal(loco,sc); // in case this is a dcc track on this addr + slot->momentum_base=now; + } + } + // DIAG(F("Reminder %d speed %d"),loco,slot->speedCode); + setThrottle2(loco, sc); + } + break; case 1: // remind function group 1 (F0-F4) if (flags & FN_GROUP_1) #ifndef DISABLE_FUNCTION_REMINDERS @@ -844,70 +900,75 @@ byte DCC::cv2(int cv) { return lowByte(cv); } -int DCC::lookupSpeedTable(int locoId, bool autoCreate) { +DCC::LOCO * DCC::lookupSpeedTable(int locoId, bool autoCreate) { // determine speed reg for this loco - int firstEmpty = MAX_LOCOS; - int reg; - for (reg = 0; reg < MAX_LOCOS; reg++) { - if (speedTable[reg].loco == locoId) break; - if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg; + LOCO * firstEmpty=nullptr; + SLOTLOOP { + if (firstEmpty==nullptr && slot->loco<=0) firstEmpty=slot; + if (slot->loco == locoId) return slot; + if (slot->loco==0) break; } - - // return -1 if not found and not auto creating - if (reg== MAX_LOCOS && !autoCreate) return -1; - if (reg == MAX_LOCOS) reg = firstEmpty; - if (reg >= MAX_LOCOS) { - DIAG(F("Too many locos")); - return -1; + if (!autoCreate) return nullptr; + if (firstEmpty==nullptr) { + // return last slot if full + DIAG(F("Too many locos, reusing last slot")); + firstEmpty=&speedTable[MAX_LOCOS-1]; } - if (reg==firstEmpty){ - speedTable[reg].loco = locoId; - speedTable[reg].speedCode=128; // default direction forward - speedTable[reg].groupFlags=0; - speedTable[reg].functions=0; - } - if (reg > highestUsedReg) highestUsedReg = reg; - return reg; + // fill first empty slot with new entry + firstEmpty->loco = locoId; + firstEmpty->speedCode=128; // default direction forward + firstEmpty->targetSpeed=128; // default direction forward + firstEmpty->groupFlags=0; + firstEmpty->functions=0; + firstEmpty->millis_per_notch=-1; // use default + return firstEmpty; } -void DCC::updateLocoReminder(int loco, byte speedCode) { - - if (loco==0) { - // broadcast stop/estop but dont change direction - for (int reg = 0; reg <= highestUsedReg; reg++) { - if (speedTable[reg].loco==0) continue; - byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); - if (speedTable[reg].speedCode != newspeed) { - speedTable[reg].speedCode = newspeed; - CommandDistributor::broadcastLoco(reg); - } - } - return; +bool DCC::setMomentum(int locoId,int16_t millis_per_notch) { + if (locoId==0 && millis_per_notch>=0) { + defaultMomentum=millis_per_notch; + return true; } + // millis=-1 is ok and means this loco should use the default. + // We dont copy the default here because it can be changed + // while running and have immediate effect on all locos using -1. + if (locoId<=0 || millis_per_notch<-1) return false; + lookupSpeedTable(locoId,true)->millis_per_notch=millis_per_notch; + return true; +} - // determine speed reg for this loco - int reg=lookupSpeedTable(loco); - if (reg>=0 && speedTable[reg].speedCode!=speedCode) { - speedTable[reg].speedCode = speedCode; - CommandDistributor::broadcastLoco(reg); + +void DCC::estopAll() { + setThrottle2(0,1); // estop all locos + TrackManager::setDCSignal(0,1); + + // remind stop/estop but dont change direction + SLOTLOOP { + if (slot->loco<=0) continue; + byte newspeed=(slot->targetSpeed & 0x80) | 0x01; + slot->speedCode = newspeed; + slot->targetSpeed = newspeed; + CommandDistributor::broadcastLoco(slot); } } + DCC::LOCO DCC::speedTable[MAX_LOCOS]; -int DCC::lastLocoReminder = 0; -int DCC::highestUsedReg = 0; +DCC::LOCO * DCC::nextLocoReminder = &DCC::speedTable[0]; void DCC::displayCabList(Print * stream) { - + StringFormatter::send(stream,F("<*\n")); int used=0; - for (int reg = 0; reg <= highestUsedReg; reg++) { - if (speedTable[reg].loco>0) { + SLOTLOOP { + if (slot->loco==0) break; // no more locos + if (slot->loco>0) { used ++; - StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"), - speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R'); + StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d momentum=%d\n"), + slot->loco, slot->speedCode, slot->targetSpeed, + slot->millis_per_notch); } } - StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS); - + StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d *>\n"), + used,MAX_LOCOS, DCC::defaultMomentum); } diff --git a/DCC.h b/DCC.h index 38c1952..faa43f7 100644 --- a/DCC.h +++ b/DCC.h @@ -59,8 +59,10 @@ public: // Public DCC API functions static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); + static void estopAll(); static int8_t getThrottleSpeed(int cab); static uint8_t getThrottleSpeedByte(int cab); + static uint8_t getLocoSpeedByte(int cab); // may lag throttle static uint8_t getThrottleFrequency(int cab); static bool getThrottleDirection(int cab); static void writeCVByteMain(int cab, int cv, byte bValue); @@ -102,20 +104,24 @@ public: byte speedCode; byte groupFlags; uint32_t functions; + // Momentum management variables + uint32_t momentum_base; // millis() when speed modified under momentum + int16_t millis_per_notch; // 0=no momentum, -1=defaultMomentum + byte targetSpeed; // speed set by throttle }; static LOCO speedTable[MAX_LOCOS]; - static int lookupSpeedTable(int locoId, bool autoCreate=true); + static LOCO * lookupSpeedTable(int locoId, bool autoCreate=true); static byte cv1(byte opcode, int cv); static byte cv2(int cv); + static bool setMomentum(int locoId,int16_t millis_per_notch); private: static byte loopStatus; + static int16_t defaultMomentum; // Millis per speed step static void setThrottle2(uint16_t cab, uint8_t speedCode); - static void updateLocoReminder(int loco, byte speedCode); static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count); - static bool issueReminder(int reg); - static int lastLocoReminder; - static int highestUsedReg; + static bool issueReminder(LOCO * slot); + static LOCO* nextLocoReminder; static FSH *shieldName; static byte globalSpeedsteps; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index fd13a41..27462fe 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -68,7 +68,8 @@ Once a new OPCODE is decided upon, update this list. K, Reserved for future use - Potentially Railcom l, Loco speedbyte/function map broadcast L, Reserved for LCC interface (implemented in EXRAIL) - m, message to throttles broadcast + m, message to throttles (broadcast output) + m, set momentum M, Write DCC packet n, Reserved for SensorCam N, Reserved for Sensorcam @@ -288,12 +289,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) int16_t direction; if (params==1) { // display state - int16_t slot=DCC::lookupSpeedTable(p[0],false); - if (slot>=0) - CommandDistributor::broadcastLoco(slot); - else // send dummy state speed 0 fwd no functions. - StringFormatter::send(stream,F("\n"),p[0]); - return; + if (p[0]<=0) break; + CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false)); + return; } if (params == 4) @@ -431,6 +429,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); return; #endif + + case 'm': // + if (params!=2) break; + if (DCC::setMomentum(p[0],p[1])) return; + break; case 'M': // WRITE TRANSPARENT DCC PACKET MAIN #ifndef DISABLE_PROG @@ -580,7 +583,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } case '!': // ESTOP ALL - DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. + DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1. return; #ifdef HAS_ENOUGH_MEMORY diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 1a7f888..965d0c4 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -566,6 +566,10 @@ void RMFT2::loop2() { forward=DCC::getThrottleDirection(loco)^invert; driveLoco(operand); break; + + case OPCODE_MOMENTUM: + DCC::setMomentum(loco,operand); + break; case OPCODE_FORGET: if (loco!=0) { @@ -679,7 +683,7 @@ void RMFT2::loop2() { break; case OPCODE_PAUSE: - DCC::setThrottle(0,1,true); // pause all locos on the track + DCC::estopAll(); // pause all locos on the track pausingTask=this; break; diff --git a/EXRAIL2.h b/EXRAIL2.h index 9271bba..b0ed324 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -35,6 +35,7 @@ // enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT, OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION, + OPCODE_MOMENTUM, OPCODE_RESERVE,OPCODE_FREE, OPCODE_AT,OPCODE_AFTER, OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 8f2845c..8773787 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -98,6 +98,7 @@ #undef LCC #undef LCCX #undef LCN +#undef MOMENTUM #undef MOVETT #undef ACON #undef ACOF @@ -265,6 +266,7 @@ #define LCC(eventid) #define LCCX(senderid,eventid) #define LCD(row,msg) +#define MOMENTUM(mspertick) #define SCREEN(display,row,msg) #define LCN(msg) #define MESSAGE(msg) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index 8c498ab..780c125 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -276,7 +276,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { switch (p[0]) { case "PAUSE"_hk: // if (paramCount!=1) return false; - DCC::setThrottle(0,1,true); // pause all locos on the track + DCC::estopAll(); // pause all locos on the track pausingTask=(RMFT2 *)1; // Impossible task address return true; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index edc1088..4f7d41a 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -551,6 +551,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define STEALTH_GLOBAL(code...) #define LCN(msg) PRINT(msg) #define MESSAGE(msg) PRINT(msg) +#define MOMENTUM(mspertick) OPCODE_MOMENTUM,V(mspertick), #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), diff --git a/Release_Notes/momentum.md b/Release_Notes/momentum.md new file mode 100644 index 0000000..ac5f280 --- /dev/null +++ b/Release_Notes/momentum.md @@ -0,0 +1,20 @@ +New Momentum feature notes: + +The command station can apply momentum to throttle movements in the same way that a standards compliant DCC decoder can be set to do. This momentum can be defaulted system wide and overridden on individual locos. It does not use or alter the loco CV values and so it also works when driving DC locos. +The momentum is applied regardless of the throttle type used (or even EXRAIL). + +Momentum is specified in mS / throttle_step. + +There is a new command `` + +For example: +`` sets loco 3 to no momentum. +`` sets loco 3 to 21 mS/step. +`` sets the default momentum to 21mS/Step for all current and future locos that have not been specifically set. +`` sets loco 3 to track the default momentum value. + +EXRAIL + A new macro `MOMENTUM(mSecPerStep)` sets the momentum value of the current tasks loco. + +Note: Setting Momentum 7,14,21 etc is similar in effect to setting a decoder CV03 to 1,2,3. At present the same momentum value is used for acceleration and deceleration. The `` command may be extended in future to separate these values. + diff --git a/TrackManager.cpp b/TrackManager.cpp index 512452d..61cf7c8 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -358,7 +358,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } void TrackManager::applyDCSpeed(byte t) { - track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]), + track[t]->setDCSignal(DCC::getLocoSpeedByte(trackDCAddr[t]), DCC::getThrottleFrequency(trackDCAddr[t])); } From f39fd89fbd466f2008d026d322598d0992b0efaf Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 25 Jul 2024 13:58:04 +0800 Subject: [PATCH 2/6] STM32 bugfix for PORTG and PORTH with thanks to Ash --- MotorDriver.cpp | 3 ++- TrackManager.cpp | 10 ++++++++++ version.h | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 28fbfa3..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; diff --git a/TrackManager.cpp b/TrackManager.cpp index 61cf7c8..dcb7c69 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_PORTF(shadowPORTF=PORTG); + HAVE_PORTF(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_PORTF(shadowPORTF=PORTG); + HAVE_PORTF(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_PORTF(shadowPORTF=PORTG); + HAVE_PORTF(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_PORTF(shadowPORTF=PORTG); + HAVE_PORTF(shadowPORTF=PORTH); } // setDCSignal(), called from normal context diff --git a/version.h b/version.h index 52fbbb9..184dad6 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.71" +#define VERSION "5.2.72" +// 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) From 9c263062e44df9da23f5e170cbdaa2a1871c67c2 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 4 Aug 2024 18:08:27 +0800 Subject: [PATCH 3/6] STM32 bugfix PORTG and PORTH shadow ports --- TrackManager.cpp | 16 ++++++++-------- version.h | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index dcb7c69..f67c266 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -151,8 +151,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); - HAVE_PORTF(shadowPORTF=PORTG); - HAVE_PORTF(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -160,8 +160,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTF(shadowPORTF=PORTG); - HAVE_PORTF(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); } // setPROGSignal(), called from interrupt context @@ -173,8 +173,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); - HAVE_PORTF(shadowPORTF=PORTG); - HAVE_PORTF(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -182,8 +182,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTF(shadowPORTF=PORTG); - HAVE_PORTF(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTF=PORTG); + HAVE_PORTH(shadowPORTF=PORTH); } // setDCSignal(), called from normal context diff --git a/version.h b/version.h index 184dad6..191530e 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.72" +#define VERSION "5.2.73" +// 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. From c5c5609fc63e91106ca3ef51f6e61b9050001755 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 6 Aug 2024 07:30:01 +0200 Subject: [PATCH 4/6] ESP32: Turn always on the JOINed PROG track when it acts as MAIN --- TrackManager.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index f67c266..5264166 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -641,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 From 05e77c924e1014562e3eed557e7797eea760ff4c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 8 Aug 2024 10:45:44 +0200 Subject: [PATCH 5/6] Revert momentum additions, squashed commit 4e57a80265c7e4c716574fc7f7a304e122013ada. --- CommandDistributor.cpp | 10 +- CommandDistributor.h | 3 +- DCC.cpp | 289 +++++++++++++++----------------------- DCC.h | 16 +-- DCCEXParser.cpp | 19 ++- EXRAIL2.cpp | 6 +- EXRAIL2.h | 1 - EXRAIL2MacroReset.h | 2 - EXRAIL2Parser.cpp | 2 +- EXRAILMacros.h | 1 - Release_Notes/momentum.md | 20 --- TrackManager.cpp | 2 +- 12 files changed, 134 insertions(+), 237 deletions(-) delete mode 100644 Release_Notes/momentum.md diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4c6fa46..d2cf3f9 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -207,13 +207,9 @@ int16_t CommandDistributor::retClockTime() { return lastclocktime; } -void CommandDistributor::broadcastLoco(DCC::LOCO* sp) { - if (!sp) { - broadcastReply(COMMAND_TYPE,F("\n")); - return; - } - broadcastReply(COMMAND_TYPE, F("\n"), - sp->loco,sp->targetSpeed,sp->functions); +void CommandDistributor::broadcastLoco(byte slot) { + DCC::LOCO * sp=&DCC::speedTable[slot]; + broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); #ifdef SABERTOOTH if (Serial2 && sp->loco == SABERTOOTH) { static uint8_t rampingmode = 0; diff --git a/CommandDistributor.h b/CommandDistributor.h index c2e600d..d86b87f 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -28,7 +28,6 @@ #include "StringBuffer.h" #include "defines.h" #include "EXRAIL2.h" -#include "DCC.h" #if WIFI_ON | ETHERNET_ON // Command Distributor must handle a RingStream of clients @@ -47,7 +46,7 @@ private: #endif public : static void parse(byte clientId,byte* buffer, RingStream * ring); - static void broadcastLoco(DCC::LOCO * slot); + static void broadcastLoco(byte slot); static void broadcastForgetLoco(int16_t loco); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); diff --git a/DCC.cpp b/DCC.cpp index 9bbd626..e60cf25 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -60,8 +60,6 @@ const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; byte DCC::globalSpeedsteps=128; -#define SLOTLOOP for (auto slot=&speedTable[0];slot!=&speedTable[MAX_LOCOS];slot++) - void DCC::begin() { StringFormatter::send(&USB_SERIAL,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); #ifndef DISABLE_EEPROM @@ -74,29 +72,13 @@ void DCC::begin() { #endif } -int16_t DCC::defaultMomentum=0; void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { - if (cab==0) { - if (tSpeed==1) estopAll(); // ESTOP broadcast fix - return; - } byte speedCode = (tSpeed & 0x7F) + tDirection * 128; - LOCO * slot=lookupSpeedTable(cab); - if (slot->targetSpeed==speedCode) return; - slot->targetSpeed=speedCode; - auto momentum=slot->millis_per_notch; - if (momentum<0) momentum=defaultMomentum; - if (momentum>0 && tSpeed!=1) { // not ESTOP - // we dont throttle speed, we just let the reminders take it to target - slot->momentum_base=millis(); - } - else { // Momentum not involved, throttle now. - slot->speedCode = speedCode; - setThrottle2(cab, speedCode); - TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr - } - CommandDistributor::broadcastLoco(slot); + setThrottle2(cab, speedCode); + TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr + // retain speed for loco reminders + updateLocoReminder(cab, speedCode ); } void DCC::setThrottle2( uint16_t cab, byte speedCode) { @@ -157,22 +139,18 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) { // returns speed steps 0 to 127 (1 == emergency stop) // or -1 on "loco not found" int8_t DCC::getThrottleSpeed(int cab) { - return getThrottleSpeedByte(cab) & 0x7F; + int reg=lookupSpeedTable(cab); + if (reg<0) return -1; + return speedTable[reg].speedCode & 0x7F; } // returns speed code byte // or 128 (speed 0, dir forward) on "loco not found". -// This is the throttle set speed uint8_t DCC::getThrottleSpeedByte(int cab) { - LOCO * slot=lookupSpeedTable(cab,false); - return slot?slot->targetSpeed:128; -} -// returns speed code byte for loco. -// This is the most recently send DCC speed packet byte -// or 128 (speed 0, dir forward) on "loco not found". -uint8_t DCC::getLocoSpeedByte(int cab) { - LOCO* slot=lookupSpeedTable(cab,false); - return slot?slot->speedCode:128; + int reg=lookupSpeedTable(cab); + if (reg<0) + return 128; + return speedTable[reg].speedCode; } // returns 0 to 7 for frequency @@ -181,11 +159,12 @@ uint8_t DCC::getThrottleFrequency(int cab) { (void)cab; return 0; #else - LOCO* slot=lookupSpeedTable(cab); - if (!slot) return 0; // use default frequency + int reg=lookupSpeedTable(cab); + if (reg<0) + return 0; // use default frequency // shift out first 29 bits so we have the 3 "frequency bits" left - uint8_t res = (uint8_t)(slot->functions >>29); - //DIAG(F("Speed table %d functions %l shifted %d"), reg, slot->functions, res); + uint8_t res = (uint8_t)(speedTable[reg].functions >>29); + //DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res); return res; #endif } @@ -193,7 +172,9 @@ uint8_t DCC::getThrottleFrequency(int cab) { // returns direction on loco // or true/forward on "loco not found" bool DCC::getThrottleDirection(int cab) { - return getThrottleSpeedByte(cab) % 0x80; + int reg=lookupSpeedTable(cab); + if (reg<0) return true; + return (speedTable[reg].speedCode & 0x80) !=0; } // Set function to value on or off @@ -226,21 +207,22 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) { if (functionNumber > 31) return true; - LOCO * slot = lookupSpeedTable(cab); - + int reg = lookupSpeedTable(cab); + if (reg<0) return false; + // Take care of functions: // Set state of function - uint32_t previous=slot->functions; + uint32_t previous=speedTable[reg].functions; uint32_t funcmask = (1UL<functions |= funcmask; + speedTable[reg].functions |= funcmask; } else { - slot->functions &= ~funcmask; + speedTable[reg].functions &= ~funcmask; } - if (slot->functions != previous) { + if (speedTable[reg].functions != previous) { if (functionNumber <= 28) - updateGroupflags(slot->groupFlags, functionNumber); - CommandDistributor::broadcastLoco(slot); + updateGroupflags(speedTable[reg].groupFlags, functionNumber); + CommandDistributor::broadcastLoco(reg); } return true; } @@ -248,13 +230,14 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) { // Flip function state (used from withrottle protocol) void DCC::changeFn( int cab, int16_t functionNumber) { if (cab<=0 || functionNumber>31) return; - auto slot=lookupSpeedTable(cab); + int reg = lookupSpeedTable(cab); + if (reg<0) return; unsigned long funcmask = (1UL<functions ^= funcmask; + speedTable[reg].functions ^= funcmask; if (functionNumber <= 28) { - updateGroupflags(slot->groupFlags, functionNumber); + updateGroupflags(speedTable[reg].groupFlags, functionNumber); } - CommandDistributor::broadcastLoco(slot); + CommandDistributor::broadcastLoco(reg); } // Report function state (used from withrottle protocol) @@ -262,10 +245,12 @@ void DCC::changeFn( int cab, int16_t functionNumber) { int8_t DCC::getFn( int cab, int16_t functionNumber) { if (cab<=0 || functionNumber>31) return -1; // unknown - auto slot = lookupSpeedTable(cab); - + int reg = lookupSpeedTable(cab); + if (reg<0) + return -1; + unsigned long funcmask = (1UL<functions & funcmask)? 1 : 0; + return (speedTable[reg].functions & funcmask)? 1 : 0; } // Set the group flag to say we have touched the particular group. @@ -282,22 +267,22 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) { uint32_t DCC::getFunctionMap(int cab) { if (cab<=0) return 0; // unknown pretend all functions off - auto slot = lookupSpeedTable(cab,false); - return slot?slot->functions:0; + int reg = lookupSpeedTable(cab); + return (reg<0)?0:speedTable[reg].functions; } // saves DC frequency (0..3) in spare functions 29,30,31 void DCC::setDCFreq(int cab,byte freq) { if (cab==0 || freq>3) return; - auto slot=lookupSpeedTable(cab,true); + auto reg=lookupSpeedTable(cab,true); // drop and replace F29,30,31 (top 3 bits) - auto newFunctions=slot->functions & 0x1FFFFFFFUL; + 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==slot->functions) return; // no change - slot->functions=newFunctions; - CommandDistributor::broadcastLoco(slot); + 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*/) { @@ -753,9 +738,10 @@ void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) { void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco setThrottle2(cab,1); // ESTOP this loco if still on track - auto slot=lookupSpeedTable(cab, false); - if (slot) { - slot->loco=-1; // no longer used but not end of world + int reg=lookupSpeedTable(cab, false); + if (reg>=0) { + speedTable[reg].loco=0; + setThrottle2(cab,1); // ESTOP if this loco still on track CommandDistributor::broadcastForgetLoco(cab); } } @@ -763,7 +749,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders setThrottle2(0,1); // ESTOP all locos still on track for (int i=0;i= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table - if (slot->loco > 0) - if (!issueReminder(slot)) - return; - // a loco=0 is at the end of the list, a loco <0 is deleted - if (slot->loco==0) nextLocoReminder = &speedTable[0]; - else nextLocoReminder=slot+1; + int reg = lastLocoReminder+1; + if (reg > highestUsedReg) reg = 0; // Go to start of table + if (speedTable[reg].loco > 0) { + // have found loco to remind + if (issueReminder(reg)) + lastLocoReminder = reg; + } else + lastLocoReminder = reg; } -int16_t normalize(byte speed) { - if (speed & 0x80) return speed & 0x7F; - return 0-1-speed; -} -byte dccalize(int16_t speed) { - if (speed>127) return 0xFF; // 127 forward - if (speed<-127) return 0x7F; // 127 reverse - if (speed >=0) return speed | 0x80; - // negative speeds... -1==dcc 0, -2==dcc 1 - return (int16_t)-1 - speed; -} - -bool DCC::issueReminder(LOCO * slot) { - unsigned long functions=slot->functions; - int loco=slot->loco; - byte flags=slot->groupFlags; +bool DCC::issueReminder(int reg) { + unsigned long functions=speedTable[reg].functions; + int loco=speedTable[reg].loco; + byte flags=speedTable[reg].groupFlags; switch (loopStatus) { - case 0: { - // calculate any momentum change going on - auto sc=slot->speedCode; - if (slot->targetSpeed!=sc) { - // calculate new speed code - auto now=millis(); - int16_t delay=now-slot->momentum_base; - auto millisPerNotch=slot->millis_per_notch; - if (millisPerNotch<0) millisPerNotch=defaultMomentum; - // allow for momentum change to 0 while accelerating/slowing - auto ticks=(millisPerNotch>0)?(delay/millisPerNotch):500; - if (ticks>0) { - auto current=normalize(sc); // -128..+127 - auto target=normalize(slot->targetSpeed); - // DIAG(F("Momentum l=%d ti=%d sc=%d c=%d t=%d"),loco,ticks,sc,current,target); - if (currenttarget) current=target; - } - else { // slow - current-=ticks; - if (currentspeedCode=sc; - TrackManager::setDCSignal(loco,sc); // in case this is a dcc track on this addr - slot->momentum_base=now; - } - } - // DIAG(F("Reminder %d speed %d"),loco,slot->speedCode); - setThrottle2(loco, sc); - } - break; + case 0: + // DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode); + setThrottle2(loco, speedTable[reg].speedCode); + break; case 1: // remind function group 1 (F0-F4) if (flags & FN_GROUP_1) #ifndef DISABLE_FUNCTION_REMINDERS @@ -900,75 +844,70 @@ byte DCC::cv2(int cv) { return lowByte(cv); } -DCC::LOCO * DCC::lookupSpeedTable(int locoId, bool autoCreate) { +int DCC::lookupSpeedTable(int locoId, bool autoCreate) { // determine speed reg for this loco - LOCO * firstEmpty=nullptr; - SLOTLOOP { - if (firstEmpty==nullptr && slot->loco<=0) firstEmpty=slot; - if (slot->loco == locoId) return slot; - if (slot->loco==0) break; + int firstEmpty = MAX_LOCOS; + int reg; + for (reg = 0; reg < MAX_LOCOS; reg++) { + if (speedTable[reg].loco == locoId) break; + if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg; } - if (!autoCreate) return nullptr; - if (firstEmpty==nullptr) { - // return last slot if full - DIAG(F("Too many locos, reusing last slot")); - firstEmpty=&speedTable[MAX_LOCOS-1]; + + // return -1 if not found and not auto creating + if (reg== MAX_LOCOS && !autoCreate) return -1; + if (reg == MAX_LOCOS) reg = firstEmpty; + if (reg >= MAX_LOCOS) { + DIAG(F("Too many locos")); + return -1; } - // fill first empty slot with new entry - firstEmpty->loco = locoId; - firstEmpty->speedCode=128; // default direction forward - firstEmpty->targetSpeed=128; // default direction forward - firstEmpty->groupFlags=0; - firstEmpty->functions=0; - firstEmpty->millis_per_notch=-1; // use default - return firstEmpty; + if (reg==firstEmpty){ + speedTable[reg].loco = locoId; + speedTable[reg].speedCode=128; // default direction forward + speedTable[reg].groupFlags=0; + speedTable[reg].functions=0; + } + if (reg > highestUsedReg) highestUsedReg = reg; + return reg; } -bool DCC::setMomentum(int locoId,int16_t millis_per_notch) { - if (locoId==0 && millis_per_notch>=0) { - defaultMomentum=millis_per_notch; - return true; +void DCC::updateLocoReminder(int loco, byte speedCode) { + + if (loco==0) { + // broadcast stop/estop but dont change direction + for (int reg = 0; reg <= highestUsedReg; reg++) { + if (speedTable[reg].loco==0) continue; + byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); + if (speedTable[reg].speedCode != newspeed) { + speedTable[reg].speedCode = newspeed; + CommandDistributor::broadcastLoco(reg); + } + } + return; } - // millis=-1 is ok and means this loco should use the default. - // We dont copy the default here because it can be changed - // while running and have immediate effect on all locos using -1. - if (locoId<=0 || millis_per_notch<-1) return false; - lookupSpeedTable(locoId,true)->millis_per_notch=millis_per_notch; - return true; -} - -void DCC::estopAll() { - setThrottle2(0,1); // estop all locos - TrackManager::setDCSignal(0,1); - - // remind stop/estop but dont change direction - SLOTLOOP { - if (slot->loco<=0) continue; - byte newspeed=(slot->targetSpeed & 0x80) | 0x01; - slot->speedCode = newspeed; - slot->targetSpeed = newspeed; - CommandDistributor::broadcastLoco(slot); + // determine speed reg for this loco + int reg=lookupSpeedTable(loco); + if (reg>=0 && speedTable[reg].speedCode!=speedCode) { + speedTable[reg].speedCode = speedCode; + CommandDistributor::broadcastLoco(reg); } } - DCC::LOCO DCC::speedTable[MAX_LOCOS]; -DCC::LOCO * DCC::nextLocoReminder = &DCC::speedTable[0]; +int DCC::lastLocoReminder = 0; +int DCC::highestUsedReg = 0; void DCC::displayCabList(Print * stream) { - StringFormatter::send(stream,F("<*\n")); + int used=0; - SLOTLOOP { - if (slot->loco==0) break; // no more locos - if (slot->loco>0) { + for (int reg = 0; reg <= highestUsedReg; reg++) { + if (speedTable[reg].loco>0) { used ++; - StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d momentum=%d\n"), - slot->loco, slot->speedCode, slot->targetSpeed, - slot->millis_per_notch); + StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"), + speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R'); } } - StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d *>\n"), - used,MAX_LOCOS, DCC::defaultMomentum); + StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS); + } diff --git a/DCC.h b/DCC.h index faa43f7..38c1952 100644 --- a/DCC.h +++ b/DCC.h @@ -59,10 +59,8 @@ public: // Public DCC API functions static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); - static void estopAll(); static int8_t getThrottleSpeed(int cab); static uint8_t getThrottleSpeedByte(int cab); - static uint8_t getLocoSpeedByte(int cab); // may lag throttle static uint8_t getThrottleFrequency(int cab); static bool getThrottleDirection(int cab); static void writeCVByteMain(int cab, int cv, byte bValue); @@ -104,24 +102,20 @@ public: byte speedCode; byte groupFlags; uint32_t functions; - // Momentum management variables - uint32_t momentum_base; // millis() when speed modified under momentum - int16_t millis_per_notch; // 0=no momentum, -1=defaultMomentum - byte targetSpeed; // speed set by throttle }; static LOCO speedTable[MAX_LOCOS]; - static LOCO * lookupSpeedTable(int locoId, bool autoCreate=true); + static int lookupSpeedTable(int locoId, bool autoCreate=true); static byte cv1(byte opcode, int cv); static byte cv2(int cv); - static bool setMomentum(int locoId,int16_t millis_per_notch); private: static byte loopStatus; - static int16_t defaultMomentum; // Millis per speed step static void setThrottle2(uint16_t cab, uint8_t speedCode); + static void updateLocoReminder(int loco, byte speedCode); static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count); - static bool issueReminder(LOCO * slot); - static LOCO* nextLocoReminder; + static bool issueReminder(int reg); + static int lastLocoReminder; + static int highestUsedReg; static FSH *shieldName; static byte globalSpeedsteps; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 27462fe..fd13a41 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -68,8 +68,7 @@ Once a new OPCODE is decided upon, update this list. K, Reserved for future use - Potentially Railcom l, Loco speedbyte/function map broadcast L, Reserved for LCC interface (implemented in EXRAIL) - m, message to throttles (broadcast output) - m, set momentum + m, message to throttles broadcast M, Write DCC packet n, Reserved for SensorCam N, Reserved for Sensorcam @@ -289,9 +288,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) int16_t direction; if (params==1) { // display state - if (p[0]<=0) break; - CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false)); - return; + int16_t slot=DCC::lookupSpeedTable(p[0],false); + if (slot>=0) + CommandDistributor::broadcastLoco(slot); + else // send dummy state speed 0 fwd no functions. + StringFormatter::send(stream,F("\n"),p[0]); + return; } if (params == 4) @@ -429,11 +431,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); return; #endif - - case 'm': // - if (params!=2) break; - if (DCC::setMomentum(p[0],p[1])) return; - break; case 'M': // WRITE TRANSPARENT DCC PACKET MAIN #ifndef DISABLE_PROG @@ -583,7 +580,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } case '!': // ESTOP ALL - DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1. + DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. return; #ifdef HAS_ENOUGH_MEMORY diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 965d0c4..1a7f888 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -566,10 +566,6 @@ void RMFT2::loop2() { forward=DCC::getThrottleDirection(loco)^invert; driveLoco(operand); break; - - case OPCODE_MOMENTUM: - DCC::setMomentum(loco,operand); - break; case OPCODE_FORGET: if (loco!=0) { @@ -683,7 +679,7 @@ void RMFT2::loop2() { break; case OPCODE_PAUSE: - DCC::estopAll(); // pause all locos on the track + DCC::setThrottle(0,1,true); // pause all locos on the track pausingTask=this; break; diff --git a/EXRAIL2.h b/EXRAIL2.h index b0ed324..9271bba 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -35,7 +35,6 @@ // enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT, OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION, - OPCODE_MOMENTUM, OPCODE_RESERVE,OPCODE_FREE, OPCODE_AT,OPCODE_AFTER, OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 8773787..8f2845c 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -98,7 +98,6 @@ #undef LCC #undef LCCX #undef LCN -#undef MOMENTUM #undef MOVETT #undef ACON #undef ACOF @@ -266,7 +265,6 @@ #define LCC(eventid) #define LCCX(senderid,eventid) #define LCD(row,msg) -#define MOMENTUM(mspertick) #define SCREEN(display,row,msg) #define LCN(msg) #define MESSAGE(msg) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index 780c125..8c498ab 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -276,7 +276,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { switch (p[0]) { case "PAUSE"_hk: // if (paramCount!=1) return false; - DCC::estopAll(); // pause all locos on the track + DCC::setThrottle(0,1,true); // pause all locos on the track pausingTask=(RMFT2 *)1; // Impossible task address return true; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 4f7d41a..edc1088 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -551,7 +551,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define STEALTH_GLOBAL(code...) #define LCN(msg) PRINT(msg) #define MESSAGE(msg) PRINT(msg) -#define MOMENTUM(mspertick) OPCODE_MOMENTUM,V(mspertick), #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), diff --git a/Release_Notes/momentum.md b/Release_Notes/momentum.md deleted file mode 100644 index ac5f280..0000000 --- a/Release_Notes/momentum.md +++ /dev/null @@ -1,20 +0,0 @@ -New Momentum feature notes: - -The command station can apply momentum to throttle movements in the same way that a standards compliant DCC decoder can be set to do. This momentum can be defaulted system wide and overridden on individual locos. It does not use or alter the loco CV values and so it also works when driving DC locos. -The momentum is applied regardless of the throttle type used (or even EXRAIL). - -Momentum is specified in mS / throttle_step. - -There is a new command `` - -For example: -`` sets loco 3 to no momentum. -`` sets loco 3 to 21 mS/step. -`` sets the default momentum to 21mS/Step for all current and future locos that have not been specifically set. -`` sets loco 3 to track the default momentum value. - -EXRAIL - A new macro `MOMENTUM(mSecPerStep)` sets the momentum value of the current tasks loco. - -Note: Setting Momentum 7,14,21 etc is similar in effect to setting a decoder CV03 to 1,2,3. At present the same momentum value is used for acceleration and deceleration. The `` command may be extended in future to separate these values. - diff --git a/TrackManager.cpp b/TrackManager.cpp index 5264166..ba9776b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -368,7 +368,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } void TrackManager::applyDCSpeed(byte t) { - track[t]->setDCSignal(DCC::getLocoSpeedByte(trackDCAddr[t]), + track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]), DCC::getThrottleFrequency(trackDCAddr[t])); } From ed853eef1dc60f9c87e2e019baab2a2e86db903c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 8 Aug 2024 10:49:59 +0200 Subject: [PATCH 6/6] version 5.2.74 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 48e2fdf..c1d7c5e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202406182019Z" +#define GITHUB_SHA "devel-202408080849Z" diff --git a/version.h b/version.h index 191530e..4d42e30 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.73" +#define VERSION "5.2.74" +// 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.