1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-01-11 21:31:02 +01:00
This commit is contained in:
Asbelos 2025-01-10 11:27:47 +00:00
parent 6f0ff49945
commit e20935848f
12 changed files with 273 additions and 146 deletions

View File

@ -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("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
void CommandDistributor::broadcastLoco(DCC::LOCO* sp) {
if (!sp) {
broadcastReply(COMMAND_TYPE,F("<l 0 -1 128 0>\n"));
return;
}
broadcastReply(COMMAND_TYPE, F("<l %d 0 %d %l>\n"),
sp->loco,sp->targetSpeed,sp->functions);
#ifdef SABERTOOTH
if (Serial2 && sp->loco == SABERTOOTH) {
static uint8_t rampingmode = 0;

View File

@ -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);

310
DCC.cpp
View File

@ -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("<iDCC-EX V-%S / %S / %S G-%S>\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;
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
// retain speed for loco reminders
updateLocoReminder(cab, speedCode );
}
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<<functionNumber);
if (on) {
speedTable[reg].functions |= funcmask;
slot->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<<functionNumber);
speedTable[reg].functions ^= funcmask;
slot->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<<functionNumber);
return (speedTable[reg].functions & funcmask)? 1 : 0;
return (slot->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<MAX_LOCOS;i++) {
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
speedTable[i].loco=0;
speedTable[i].loco=0; // no longer used and looks like end
}
}
@ -764,25 +798,66 @@ void DCC::issueReminders() {
// if the main track transmitter still has a pending packet, skip this time around.
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
// Move to next loco slot. If occupied, send a reminder.
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;
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);
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 (current<target) { // accelerate
current+=ticks;
if (current>target) current=target;
}
else { // slow
current-=ticks;
if (current<target) current=target;
}
sc=dccalize(current);
//DIAG(F("c=%d newsc=%d"),current,sc);
slot->speedCode=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)
@ -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) {
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;
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;
}
// 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);
}

24
DCC.h
View File

@ -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;

View File

@ -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,11 +317,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
int16_t direction;
if (params==1) { // <t cab> 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("<l %d -1 128 0>\n"),p[0]);
if (p[0]<=0) break;
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false));
return;
}
@ -490,6 +488,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
#endif
case 'm': // <m cabid momentum [braking]>
// <m LINEAR|POWER>
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 <M REG X1 ... X9>
#ifndef DISABLE_PROG
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
@ -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

View File

@ -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.
@ -586,6 +586,10 @@ void RMFT2::loop2() {
driveLoco(operand);
break;
case OPCODE_MOMENTUM:
DCC::setMomentum(loco,operand,getOperand(1));
break;
case OPCODE_FORGET:
if (loco!=0) {
DCC::forgetLoco(loco);
@ -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;

View File

@ -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,

View File

@ -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)

View File

@ -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: // </ PAUSE>
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;

View File

@ -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)),\

View File

@ -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]));
}

View File

@ -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()