From e20935848fae45e1d387360833fd21c708964682 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Jan 2025 11:27:47 +0000 Subject: [PATCH] Momentum --- CommandDistributor.cpp | 12 +- CommandDistributor.h | 5 +- DCC.cpp | 320 ++++++++++++++++++++++++++--------------- DCC.h | 24 +++- DCCEXParser.cpp | 29 ++-- EXRAIL2.cpp | 8 +- EXRAIL2.h | 3 +- EXRAIL2MacroReset.h | 4 +- EXRAIL2Parser.cpp | 4 +- EXRAILMacros.h | 3 +- TrackManager.cpp | 4 +- version.h | 3 +- 12 files changed, 273 insertions(+), 146 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index e889f62..0e0a82f 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -1,6 +1,6 @@ /* * © 2022 Harald Barth - * © 2020-2021 Chris Harlow + * © 2020-2025 Chris Harlow * © 2020 Gregor Baues * © 2022 Colin Murdoch * All rights reserved. @@ -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..be66348 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -1,6 +1,6 @@ /* * © 2022 Harald Barth - * © 2020-2021 Chris Harlow + * © 2020-2025 Chris Harlow * © 2020 Gregor Baues * © 2022 Colin Murdoch * @@ -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..d2d8164 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -5,7 +5,7 @@ * © 2021 Herb Morton * © 2020-2022 Harald Barth * © 2020-2021 M Steve Todd - * © 2020-2021 Chris Harlow + * © 2020-2025 Chris Harlow * All rights reserved. * * This file is part of DCC-EX @@ -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,49 @@ void DCC::begin() { #endif } +byte DCC::defaultMomentumA=0; +byte DCC::defaultMomentumD=0; +bool DCC::linearAcceleration=false; + +byte DCC::getMomentum(LOCO * slot) { + auto target=slot->targetSpeed & 0x7f; + auto current=slot->speedCode & 0x7f; + if (target > current) { + // accelerating + auto momentum=slot->momentumA==MOMENTUM_USE_DEFAULT ? defaultMomentumA : slot->momentumA; + // if nonlinear acceleration, momentum is reduced according to + // gap between throttle and speed. + // ie. Loco takes accelerates faster if high throttle + if (momentum==0 || linearAcceleration) return momentum; + auto powerDifference= (target-current)/8; + if (momentum-powerDifference <0) return 0; + return momentum-powerDifference; + } + return slot->momentumD==MOMENTUM_USE_DEFAULT ? defaultMomentumD : slot->momentumD; +} void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { + if (tSpeed==1) { + if (cab==0) { + 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; + byte momentum=getMomentum(slot); + if (momentum && 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 +177,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 +201,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 +213,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 +246,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 +268,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 +282,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 +302,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 +773,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 +783,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=MOMENTUM_FACTOR * (int16_t)getMomentum(slot); + // 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 +919,85 @@ 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->momentumA=MOMENTUM_USE_DEFAULT; + firstEmpty->momentumD=MOMENTUM_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 accelerating, int16_t decelerating) { + if (locoId<0) return false; + if (locoId==0) { + if (accelerating<0 || decelerating<0) return false; + defaultMomentumA=accelerating/MOMENTUM_FACTOR; + defaultMomentumD=decelerating/MOMENTUM_FACTOR; + return true; } + // -1 is ok and means this loco should use the default. + if (accelerating<-1 || decelerating<-1) return false; + if (accelerating/MOMENTUM_FACTOR >= MOMENTUM_USE_DEFAULT || + decelerating/MOMENTUM_FACTOR >= MOMENTUM_USE_DEFAULT) return false; + + // Values stored are 255=MOMENTUM_USE_DEFAULT, or millis/MOMENTUM_FACTOR. + // This is to keep the values in a byte rather than int16 + // thus saving 2 bytes RAM per loco slot. + LOCO* slot=lookupSpeedTable(locoId,true); + slot->momentumA=(accelerating<0)? MOMENTUM_USE_DEFAULT: (accelerating/MOMENTUM_FACTOR); + slot->momentumD=(decelerating<0)? MOMENTUM_USE_DEFAULT: (decelerating/MOMENTUM_FACTOR); + 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/%d\n"), + slot->loco, slot->speedCode, slot->targetSpeed, + slot->momentumA, slot->momentumD); } } - StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS); - + StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d/%d *>\n"), + used,MAX_LOCOS, DCC::defaultMomentumA,DCC::defaultMomentumD); } diff --git a/DCC.h b/DCC.h index 38c1952..82e03e0 100644 --- a/DCC.h +++ b/DCC.h @@ -3,7 +3,7 @@ * © 2021 Fred Decker * © 2021 Herb Morton * © 2020-2021 Harald Barth - * © 2020-2021 Chris Harlow + * © 2020-2025 Chris Harlow * All rights reserved. * * This file is part of Asbelos DCC API @@ -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,30 @@ public: byte speedCode; byte groupFlags; uint32_t functions; + // Momentum management variables + uint32_t momentum_base; // millis() when speed modified under momentum + byte momentumA, momentumD; + byte targetSpeed; // speed set by throttle }; + static const int16_t MOMENTUM_FACTOR=7; + static const byte MOMENTUM_USE_DEFAULT=255; + static bool linearAcceleration; + static byte getMomentum(LOCO * slot); + 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 accelerating, int16_t decelerating); private: static byte loopStatus; + static byte defaultMomentumA; // Accelerating + static byte defaultMomentumD; // Accelerating 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 ad577ff..4c38e98 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -6,7 +6,7 @@ * © 2020-2023 Harald Barth * © 2020-2021 M Steve Todd * © 2020-2021 Fred Decker - * © 2020-2021 Chris Harlow + * © 2020-2025 Chris Harlow * © 2022 Colin Murdoch * All rights reserved. * @@ -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 @@ -316,12 +317,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) @@ -489,6 +487,19 @@ 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==1) { + if (p[0]=="LINEAR"_hk) DCC::linearAcceleration=true; + else if (p[0]=="POWER"_hk) DCC::linearAcceleration=false; + else break; + return; + } + if (params<2 || params>3) break; + if (params==2) p[2]=p[1]; + if (DCC::setMomentum(p[0],p[1],p[2])) return; + break; case 'M': // WRITE TRANSPARENT DCC PACKET MAIN #ifndef DISABLE_PROG @@ -638,7 +649,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 7e92edf..241417b 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -2,7 +2,7 @@ * © 2024 Paul M. Antoine * © 2021 Neil McKechnie * © 2021-2023 Harald Barth - * © 2020-2023 Chris Harlow + * © 2020-2025 Chris Harlow * © 2022-2023 Colin Murdoch * © 2025 Morten Nielsen * All rights reserved. @@ -585,6 +585,10 @@ void RMFT2::loop2() { forward=DCC::getThrottleDirection(loco)^invert; driveLoco(operand); break; + + case OPCODE_MOMENTUM: + DCC::setMomentum(loco,operand,getOperand(1)); + break; case OPCODE_FORGET: if (loco!=0) { @@ -699,7 +703,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 5b95f8e..954e6af 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -1,6 +1,6 @@ /* * © 2021 Neil McKechnie - * © 2020-2022 Chris Harlow + * © 2020-2025 Chris Harlow * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * © 2025 Morten Nielsen @@ -36,6 +36,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 6b6db29..1986ef1 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -1,5 +1,5 @@ /* - * © 2020-2022 Chris Harlow. All rights reserved. + * © 2020-2025 Chris Harlow. All rights reserved. * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * © 2025 Morten Nielsen @@ -99,6 +99,7 @@ #undef LCC #undef LCCX #undef LCN +#undef MOMENTUM #undef MOVETT #undef NEOPIXEL #undef NEOPIXEL_OFF @@ -271,6 +272,7 @@ #define LCC(eventid) #define LCCX(senderid,eventid) #define LCD(row,msg) +#define MOMENTUM(accel,decel...) #define SCREEN(display,row,msg) #define LCN(msg) #define MESSAGE(msg) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index 63bace0..3bea9b9 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -1,7 +1,7 @@ /* * © 2021 Neil McKechnie * © 2021-2023 Harald Barth - * © 2020-2023 Chris Harlow + * © 2020-2025 Chris Harlow * © 2022-2023 Colin Murdoch * All rights reserved. * @@ -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 d0f640a..6de320a 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -1,6 +1,6 @@ /* * © 2021 Neil McKechnie - * © 2020-2022 Chris Harlow + * © 2020-2025 Chris Harlow * © 2022-2023 Colin Murdoch * © 2023 Harald Barth * © 2025 Morten Nielsen @@ -565,6 +565,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define STEALTH_GLOBAL(code...) #define LCN(msg) PRINT(msg) #define MESSAGE(msg) PRINT(msg) +#define MOMENTUM(accel,decel...) OPCODE_MOMENTUM,V(accel),OPCODE_PAD,V(#decel[0]?decel+0:accel), #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define NEOPIXEL(id,r,g,b,count...) OPCODE_NEOPIXEL,V(id),\ OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\ diff --git a/TrackManager.cpp b/TrackManager.cpp index 72e8fad..8d183dc 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -1,5 +1,5 @@ /* - * © 2022 Chris Harlow + * © 2022-2025 Chris Harlow * © 2022-2024 Harald Barth * © 2023-2024 Paul M. Antoine * © 2024 Herb Morton @@ -379,7 +379,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])); } diff --git a/version.h b/version.h index d302659..4cf4337 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.5.0" +#define VERSION "5.5.1" +// 5.5.1 - Momentum // 5.5.0 - New version on devel // 5.4.0 - New version on master // 5.2.96 - EXRAIL additions XFWD() and XREV()