mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-26 17:46:14 +01:00
Compare commits
2 Commits
c5c5609fc6
...
ed853eef1d
Author | SHA1 | Date | |
---|---|---|---|
|
ed853eef1d | ||
|
05e77c924e |
|
@ -207,13 +207,9 @@ int16_t CommandDistributor::retClockTime() {
|
||||||
return lastclocktime;
|
return lastclocktime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastLoco(DCC::LOCO* sp) {
|
void CommandDistributor::broadcastLoco(byte slot) {
|
||||||
if (!sp) {
|
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||||
broadcastReply(COMMAND_TYPE,F("<l 0 -1 128 0>\n"));
|
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||||
return;
|
|
||||||
}
|
|
||||||
broadcastReply(COMMAND_TYPE, F("<l %d 0 %d %l>\n"),
|
|
||||||
sp->loco,sp->targetSpeed,sp->functions);
|
|
||||||
#ifdef SABERTOOTH
|
#ifdef SABERTOOTH
|
||||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||||
static uint8_t rampingmode = 0;
|
static uint8_t rampingmode = 0;
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
#include "StringBuffer.h"
|
#include "StringBuffer.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "EXRAIL2.h"
|
#include "EXRAIL2.h"
|
||||||
#include "DCC.h"
|
|
||||||
|
|
||||||
#if WIFI_ON | ETHERNET_ON
|
#if WIFI_ON | ETHERNET_ON
|
||||||
// Command Distributor must handle a RingStream of clients
|
// Command Distributor must handle a RingStream of clients
|
||||||
|
@ -47,7 +46,7 @@ private:
|
||||||
#endif
|
#endif
|
||||||
public :
|
public :
|
||||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
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 broadcastForgetLoco(int16_t loco);
|
||||||
static void broadcastSensor(int16_t id, bool value);
|
static void broadcastSensor(int16_t id, bool value);
|
||||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||||
|
|
289
DCC.cpp
289
DCC.cpp
|
@ -60,8 +60,6 @@ const byte FN_GROUP_5=0x10;
|
||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
byte DCC::globalSpeedsteps=128;
|
byte DCC::globalSpeedsteps=128;
|
||||||
|
|
||||||
#define SLOTLOOP for (auto slot=&speedTable[0];slot!=&speedTable[MAX_LOCOS];slot++)
|
|
||||||
|
|
||||||
void DCC::begin() {
|
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));
|
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
|
#ifndef DISABLE_EEPROM
|
||||||
|
@ -74,29 +72,13 @@ void DCC::begin() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t DCC::defaultMomentum=0;
|
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
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;
|
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||||
LOCO * slot=lookupSpeedTable(cab);
|
setThrottle2(cab, speedCode);
|
||||||
if (slot->targetSpeed==speedCode) return;
|
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
||||||
slot->targetSpeed=speedCode;
|
// retain speed for loco reminders
|
||||||
auto momentum=slot->millis_per_notch;
|
updateLocoReminder(cab, speedCode );
|
||||||
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) {
|
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)
|
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||||
// or -1 on "loco not found"
|
// or -1 on "loco not found"
|
||||||
int8_t DCC::getThrottleSpeed(int cab) {
|
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
|
// returns speed code byte
|
||||||
// or 128 (speed 0, dir forward) on "loco not found".
|
// or 128 (speed 0, dir forward) on "loco not found".
|
||||||
// This is the throttle set speed
|
|
||||||
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||||
LOCO * slot=lookupSpeedTable(cab,false);
|
int reg=lookupSpeedTable(cab);
|
||||||
return slot?slot->targetSpeed:128;
|
if (reg<0)
|
||||||
}
|
return 128;
|
||||||
// returns speed code byte for loco.
|
return speedTable[reg].speedCode;
|
||||||
// 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
|
// returns 0 to 7 for frequency
|
||||||
|
@ -181,11 +159,12 @@ uint8_t DCC::getThrottleFrequency(int cab) {
|
||||||
(void)cab;
|
(void)cab;
|
||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
LOCO* slot=lookupSpeedTable(cab);
|
int reg=lookupSpeedTable(cab);
|
||||||
if (!slot) return 0; // use default frequency
|
if (reg<0)
|
||||||
|
return 0; // use default frequency
|
||||||
// shift out first 29 bits so we have the 3 "frequency bits" left
|
// shift out first 29 bits so we have the 3 "frequency bits" left
|
||||||
uint8_t res = (uint8_t)(slot->functions >>29);
|
uint8_t res = (uint8_t)(speedTable[reg].functions >>29);
|
||||||
//DIAG(F("Speed table %d functions %l shifted %d"), reg, slot->functions, res);
|
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
||||||
return res;
|
return res;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -193,7 +172,9 @@ uint8_t DCC::getThrottleFrequency(int cab) {
|
||||||
// returns direction on loco
|
// returns direction on loco
|
||||||
// or true/forward on "loco not found"
|
// or true/forward on "loco not found"
|
||||||
bool DCC::getThrottleDirection(int cab) {
|
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
|
// Set function to value on or off
|
||||||
|
@ -226,21 +207,22 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||||
if (functionNumber > 31)
|
if (functionNumber > 31)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
LOCO * slot = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return false;
|
||||||
|
|
||||||
// Take care of functions:
|
// Take care of functions:
|
||||||
// Set state of function
|
// Set state of function
|
||||||
uint32_t previous=slot->functions;
|
uint32_t previous=speedTable[reg].functions;
|
||||||
uint32_t funcmask = (1UL<<functionNumber);
|
uint32_t funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
slot->functions |= funcmask;
|
speedTable[reg].functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
slot->functions &= ~funcmask;
|
speedTable[reg].functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
if (slot->functions != previous) {
|
if (speedTable[reg].functions != previous) {
|
||||||
if (functionNumber <= 28)
|
if (functionNumber <= 28)
|
||||||
updateGroupflags(slot->groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
CommandDistributor::broadcastLoco(slot);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -248,13 +230,14 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||||
// Flip function state (used from withrottle protocol)
|
// Flip function state (used from withrottle protocol)
|
||||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>31) return;
|
if (cab<=0 || functionNumber>31) return;
|
||||||
auto slot=lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0) return;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
slot->functions ^= funcmask;
|
speedTable[reg].functions ^= funcmask;
|
||||||
if (functionNumber <= 28) {
|
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)
|
// 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) {
|
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>31)
|
if (cab<=0 || functionNumber>31)
|
||||||
return -1; // unknown
|
return -1; // unknown
|
||||||
auto slot = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
|
if (reg<0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
return (slot->functions & funcmask)? 1 : 0;
|
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the group flag to say we have touched the particular group.
|
// 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) {
|
uint32_t DCC::getFunctionMap(int cab) {
|
||||||
if (cab<=0) return 0; // unknown pretend all functions off
|
if (cab<=0) return 0; // unknown pretend all functions off
|
||||||
auto slot = lookupSpeedTable(cab,false);
|
int reg = lookupSpeedTable(cab);
|
||||||
return slot?slot->functions:0;
|
return (reg<0)?0:speedTable[reg].functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// saves DC frequency (0..3) in spare functions 29,30,31
|
// saves DC frequency (0..3) in spare functions 29,30,31
|
||||||
void DCC::setDCFreq(int cab,byte freq) {
|
void DCC::setDCFreq(int cab,byte freq) {
|
||||||
if (cab==0 || freq>3) return;
|
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)
|
// 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
|
if (freq==1) newFunctions |= (1UL<<29); // F29
|
||||||
else if (freq==2) newFunctions |= (1UL<<30); // F30
|
else if (freq==2) newFunctions |= (1UL<<30); // F30
|
||||||
else if (freq==3) newFunctions |= (1UL<<31); // F31
|
else if (freq==3) newFunctions |= (1UL<<31); // F31
|
||||||
if (newFunctions==slot->functions) return; // no change
|
if (newFunctions==speedTable[reg].functions) return; // no change
|
||||||
slot->functions=newFunctions;
|
speedTable[reg].functions=newFunctions;
|
||||||
CommandDistributor::broadcastLoco(slot);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
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
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||||
auto slot=lookupSpeedTable(cab, false);
|
int reg=lookupSpeedTable(cab, false);
|
||||||
if (slot) {
|
if (reg>=0) {
|
||||||
slot->loco=-1; // no longer used but not end of world
|
speedTable[reg].loco=0;
|
||||||
|
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||||
CommandDistributor::broadcastForgetLoco(cab);
|
CommandDistributor::broadcastForgetLoco(cab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -763,7 +749,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders
|
||||||
setThrottle2(0,1); // ESTOP all locos still on track
|
setThrottle2(0,1); // ESTOP all locos still on track
|
||||||
for (int i=0;i<MAX_LOCOS;i++) {
|
for (int i=0;i<MAX_LOCOS;i++) {
|
||||||
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
|
if (speedTable[i].loco) CommandDistributor::broadcastForgetLoco(speedTable[i].loco);
|
||||||
speedTable[i].loco=0; // no longer used and looks like end
|
speedTable[i].loco=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,68 +764,26 @@ void DCC::issueReminders() {
|
||||||
// if the main track transmitter still has a pending packet, skip this time around.
|
// if the main track transmitter still has a pending packet, skip this time around.
|
||||||
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
||||||
// Move to next loco slot. If occupied, send a reminder.
|
// Move to next loco slot. If occupied, send a reminder.
|
||||||
auto slot = nextLocoReminder;
|
int reg = lastLocoReminder+1;
|
||||||
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||||
if (slot->loco > 0)
|
if (speedTable[reg].loco > 0) {
|
||||||
if (!issueReminder(slot))
|
// have found loco to remind
|
||||||
return;
|
if (issueReminder(reg))
|
||||||
// a loco=0 is at the end of the list, a loco <0 is deleted
|
lastLocoReminder = reg;
|
||||||
if (slot->loco==0) nextLocoReminder = &speedTable[0];
|
} else
|
||||||
else nextLocoReminder=slot+1;
|
lastLocoReminder = reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t normalize(byte speed) {
|
bool DCC::issueReminder(int reg) {
|
||||||
if (speed & 0x80) return speed & 0x7F;
|
unsigned long functions=speedTable[reg].functions;
|
||||||
return 0-1-speed;
|
int loco=speedTable[reg].loco;
|
||||||
}
|
byte flags=speedTable[reg].groupFlags;
|
||||||
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) {
|
switch (loopStatus) {
|
||||||
case 0: {
|
case 0:
|
||||||
// calculate any momentum change going on
|
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||||
auto sc=slot->speedCode;
|
setThrottle2(loco, speedTable[reg].speedCode);
|
||||||
if (slot->targetSpeed!=sc) {
|
break;
|
||||||
// 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)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
if (flags & FN_GROUP_1)
|
||||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||||
|
@ -900,75 +844,70 @@ byte DCC::cv2(int cv) {
|
||||||
return lowByte(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
|
// determine speed reg for this loco
|
||||||
LOCO * firstEmpty=nullptr;
|
int firstEmpty = MAX_LOCOS;
|
||||||
SLOTLOOP {
|
int reg;
|
||||||
if (firstEmpty==nullptr && slot->loco<=0) firstEmpty=slot;
|
for (reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
if (slot->loco == locoId) return slot;
|
if (speedTable[reg].loco == locoId) break;
|
||||||
if (slot->loco==0) break;
|
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||||
}
|
}
|
||||||
if (!autoCreate) return nullptr;
|
|
||||||
if (firstEmpty==nullptr) {
|
// return -1 if not found and not auto creating
|
||||||
// return last slot if full
|
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
||||||
DIAG(F("Too many locos, reusing last slot"));
|
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||||
firstEmpty=&speedTable[MAX_LOCOS-1];
|
if (reg >= MAX_LOCOS) {
|
||||||
|
DIAG(F("Too many locos"));
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
// fill first empty slot with new entry
|
if (reg==firstEmpty){
|
||||||
firstEmpty->loco = locoId;
|
speedTable[reg].loco = locoId;
|
||||||
firstEmpty->speedCode=128; // default direction forward
|
speedTable[reg].speedCode=128; // default direction forward
|
||||||
firstEmpty->targetSpeed=128; // default direction forward
|
speedTable[reg].groupFlags=0;
|
||||||
firstEmpty->groupFlags=0;
|
speedTable[reg].functions=0;
|
||||||
firstEmpty->functions=0;
|
}
|
||||||
firstEmpty->millis_per_notch=-1; // use default
|
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||||
return firstEmpty;
|
return reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::setMomentum(int locoId,int16_t millis_per_notch) {
|
void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||||
if (locoId==0 && millis_per_notch>=0) {
|
|
||||||
defaultMomentum=millis_per_notch;
|
if (loco==0) {
|
||||||
return true;
|
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// determine speed reg for this loco
|
||||||
void DCC::estopAll() {
|
int reg=lookupSpeedTable(loco);
|
||||||
setThrottle2(0,1); // estop all locos
|
if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
|
||||||
TrackManager::setDCSignal(0,1);
|
speedTable[reg].speedCode = speedCode;
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
// 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];
|
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) {
|
void DCC::displayCabList(Print * stream) {
|
||||||
StringFormatter::send(stream,F("<*\n"));
|
|
||||||
int used=0;
|
int used=0;
|
||||||
SLOTLOOP {
|
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||||
if (slot->loco==0) break; // no more locos
|
if (speedTable[reg].loco>0) {
|
||||||
if (slot->loco>0) {
|
|
||||||
used ++;
|
used ++;
|
||||||
StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d momentum=%d\n"),
|
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||||
slot->loco, slot->speedCode, slot->targetSpeed,
|
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
||||||
slot->millis_per_notch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("Used=%d, max=%d, momentum=%d *>\n"),
|
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
||||||
used,MAX_LOCOS, DCC::defaultMomentum);
|
|
||||||
}
|
}
|
||||||
|
|
16
DCC.h
16
DCC.h
|
@ -59,10 +59,8 @@ public:
|
||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||||
static void estopAll();
|
|
||||||
static int8_t getThrottleSpeed(int cab);
|
static int8_t getThrottleSpeed(int cab);
|
||||||
static uint8_t getThrottleSpeedByte(int cab);
|
static uint8_t getThrottleSpeedByte(int cab);
|
||||||
static uint8_t getLocoSpeedByte(int cab); // may lag throttle
|
|
||||||
static uint8_t getThrottleFrequency(int cab);
|
static uint8_t getThrottleFrequency(int cab);
|
||||||
static bool getThrottleDirection(int cab);
|
static bool getThrottleDirection(int cab);
|
||||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||||
|
@ -104,24 +102,20 @@ public:
|
||||||
byte speedCode;
|
byte speedCode;
|
||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
uint32_t functions;
|
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 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 cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
static bool setMomentum(int locoId,int16_t millis_per_notch);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static int16_t defaultMomentum; // Millis per speed step
|
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
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 void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
||||||
static bool issueReminder(LOCO * slot);
|
static bool issueReminder(int reg);
|
||||||
static LOCO* nextLocoReminder;
|
static int lastLocoReminder;
|
||||||
|
static int highestUsedReg;
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
|
|
|
@ -68,8 +68,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
K, Reserved for future use - Potentially Railcom
|
K, Reserved for future use - Potentially Railcom
|
||||||
l, Loco speedbyte/function map broadcast
|
l, Loco speedbyte/function map broadcast
|
||||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||||
m, message to throttles (broadcast output)
|
m, message to throttles broadcast
|
||||||
m, set momentum
|
|
||||||
M, Write DCC packet
|
M, Write DCC packet
|
||||||
n, Reserved for SensorCam
|
n, Reserved for SensorCam
|
||||||
N, Reserved for Sensorcam
|
N, Reserved for Sensorcam
|
||||||
|
@ -289,9 +288,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
int16_t direction;
|
int16_t direction;
|
||||||
|
|
||||||
if (params==1) { // <t cab> display state
|
if (params==1) { // <t cab> display state
|
||||||
if (p[0]<=0) break;
|
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||||
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false));
|
if (slot>=0)
|
||||||
return;
|
CommandDistributor::broadcastLoco(slot);
|
||||||
|
else // send dummy state speed 0 fwd no functions.
|
||||||
|
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params == 4)
|
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]);
|
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case 'm': // <m cabid momentum>
|
|
||||||
if (params!=2) break;
|
|
||||||
if (DCC::setMomentum(p[0],p[1])) return;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
|
@ -583,7 +580,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
case '!': // ESTOP ALL <!>
|
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;
|
return;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
|
|
|
@ -566,10 +566,6 @@ void RMFT2::loop2() {
|
||||||
forward=DCC::getThrottleDirection(loco)^invert;
|
forward=DCC::getThrottleDirection(loco)^invert;
|
||||||
driveLoco(operand);
|
driveLoco(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_MOMENTUM:
|
|
||||||
DCC::setMomentum(loco,operand);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case OPCODE_FORGET:
|
case OPCODE_FORGET:
|
||||||
if (loco!=0) {
|
if (loco!=0) {
|
||||||
|
@ -683,7 +679,7 @@ void RMFT2::loop2() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_PAUSE:
|
case OPCODE_PAUSE:
|
||||||
DCC::estopAll(); // pause all locos on the track
|
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||||
pausingTask=this;
|
pausingTask=this;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
//
|
//
|
||||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||||
OPCODE_MOMENTUM,
|
|
||||||
OPCODE_RESERVE,OPCODE_FREE,
|
OPCODE_RESERVE,OPCODE_FREE,
|
||||||
OPCODE_AT,OPCODE_AFTER,
|
OPCODE_AT,OPCODE_AFTER,
|
||||||
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
||||||
|
|
|
@ -98,7 +98,6 @@
|
||||||
#undef LCC
|
#undef LCC
|
||||||
#undef LCCX
|
#undef LCCX
|
||||||
#undef LCN
|
#undef LCN
|
||||||
#undef MOMENTUM
|
|
||||||
#undef MOVETT
|
#undef MOVETT
|
||||||
#undef ACON
|
#undef ACON
|
||||||
#undef ACOF
|
#undef ACOF
|
||||||
|
@ -266,7 +265,6 @@
|
||||||
#define LCC(eventid)
|
#define LCC(eventid)
|
||||||
#define LCCX(senderid,eventid)
|
#define LCCX(senderid,eventid)
|
||||||
#define LCD(row,msg)
|
#define LCD(row,msg)
|
||||||
#define MOMENTUM(mspertick)
|
|
||||||
#define SCREEN(display,row,msg)
|
#define SCREEN(display,row,msg)
|
||||||
#define LCN(msg)
|
#define LCN(msg)
|
||||||
#define MESSAGE(msg)
|
#define MESSAGE(msg)
|
||||||
|
|
|
@ -276,7 +276,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case "PAUSE"_hk: // </ PAUSE>
|
case "PAUSE"_hk: // </ PAUSE>
|
||||||
if (paramCount!=1) return false;
|
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
|
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
|
@ -551,7 +551,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||||
#define STEALTH_GLOBAL(code...)
|
#define STEALTH_GLOBAL(code...)
|
||||||
#define LCN(msg) PRINT(msg)
|
#define LCN(msg) PRINT(msg)
|
||||||
#define MESSAGE(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 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 ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
#define GITHUB_SHA "devel-202406182019Z"
|
#define GITHUB_SHA "devel-202408080849Z"
|
||||||
|
|
|
@ -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 `<m cabid mS>`
|
|
||||||
|
|
||||||
For example:
|
|
||||||
`<m 3 0>` sets loco 3 to no momentum.
|
|
||||||
`<m 3 21>` sets loco 3 to 21 mS/step.
|
|
||||||
`<m 0 21>` sets the default momentum to 21mS/Step for all current and future locos that have not been specifically set.
|
|
||||||
`<m 3 -1>` 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 `<m>` command may be extended in future to separate these values.
|
|
||||||
|
|
|
@ -368,7 +368,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::applyDCSpeed(byte t) {
|
void TrackManager::applyDCSpeed(byte t) {
|
||||||
track[t]->setDCSignal(DCC::getLocoSpeedByte(trackDCAddr[t]),
|
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
||||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#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.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.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.71 - Broadcasts of loco forgets.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user