1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-28 18:03:45 +02:00

Squashed commit of the following:

commit 3ac2fff70d
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Jul 23 15:40:36 2024 +0100

    Create momentum.md

commit a08195332f
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Jul 22 21:57:47 2024 +0100

    Cleanup of DCC Class reminders

commit 002ec5f176
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Jul 22 12:42:43 2024 +0100

    Cleaning access to speedByte

commit 854ddb0c6c
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Jul 21 10:15:07 2024 +0100

    Fix momentum algorithm

commit 916d3baf63
Merge: ab72a75 27dc805
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Jul 19 10:14:06 2024 +0100

    Merge branch 'devel' into devel_momentum

commit ab72a75d8f
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Jul 19 08:33:50 2024 +0100

    EXRAIL MOMENTUM

commit 8a623aa1cb
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Jul 18 20:31:58 2024 +0100

     Momentum
This commit is contained in:
Asbelos
2024-07-23 15:42:35 +01:00
parent 27dc8059d7
commit 4e57a80265
12 changed files with 237 additions and 134 deletions

289
DCC.cpp
View File

@@ -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,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<<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 +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<<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 +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<<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 +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<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,26 +778,68 @@ 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);
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 (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)
#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);
}