mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-01-12 05:41:02 +01:00
Momentum
This commit is contained in:
parent
6f0ff49945
commit
e20935848f
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Harald Barth
|
* © 2022 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2020 Gregor Baues
|
* © 2020 Gregor Baues
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -207,9 +207,13 @@ int16_t CommandDistributor::retClockTime() {
|
|||||||
return lastclocktime;
|
return lastclocktime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastLoco(byte slot) {
|
void CommandDistributor::broadcastLoco(DCC::LOCO* sp) {
|
||||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
if (!sp) {
|
||||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
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
|
#ifdef SABERTOOTH
|
||||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||||
static uint8_t rampingmode = 0;
|
static uint8_t rampingmode = 0;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Harald Barth
|
* © 2022 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2020 Gregor Baues
|
* © 2020 Gregor Baues
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
*
|
*
|
||||||
@ -28,6 +28,7 @@
|
|||||||
#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
|
||||||
@ -46,7 +47,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(byte slot);
|
static void broadcastLoco(DCC::LOCO * 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);
|
||||||
|
310
DCC.cpp
310
DCC.cpp
@ -5,7 +5,7 @@
|
|||||||
* © 2021 Herb Morton
|
* © 2021 Herb Morton
|
||||||
* © 2020-2022 Harald Barth
|
* © 2020-2022 Harald Barth
|
||||||
* © 2020-2021 M Steve Todd
|
* © 2020-2021 M Steve Todd
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX
|
* This file is part of DCC-EX
|
||||||
@ -60,6 +60,8 @@ 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
|
||||||
@ -72,13 +74,49 @@ void DCC::begin() {
|
|||||||
#endif
|
#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) {
|
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;
|
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);
|
setThrottle2(cab, speedCode);
|
||||||
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
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) {
|
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)
|
// 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) {
|
||||||
int reg=lookupSpeedTable(cab);
|
return getThrottleSpeedByte(cab) & 0x7F;
|
||||||
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) {
|
||||||
int reg=lookupSpeedTable(cab);
|
LOCO * slot=lookupSpeedTable(cab,false);
|
||||||
if (reg<0)
|
return slot?slot->targetSpeed:128;
|
||||||
return 128;
|
}
|
||||||
return speedTable[reg].speedCode;
|
// 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
|
// returns 0 to 7 for frequency
|
||||||
@ -159,12 +201,11 @@ uint8_t DCC::getThrottleFrequency(int cab) {
|
|||||||
(void)cab;
|
(void)cab;
|
||||||
return 0;
|
return 0;
|
||||||
#else
|
#else
|
||||||
int reg=lookupSpeedTable(cab);
|
LOCO* slot=lookupSpeedTable(cab);
|
||||||
if (reg<0)
|
if (!slot) return 0; // use default frequency
|
||||||
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)(speedTable[reg].functions >>29);
|
uint8_t res = (uint8_t)(slot->functions >>29);
|
||||||
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
//DIAG(F("Speed table %d functions %l shifted %d"), reg, slot->functions, res);
|
||||||
return res;
|
return res;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -172,9 +213,7 @@ 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) {
|
||||||
int reg=lookupSpeedTable(cab);
|
return getThrottleSpeedByte(cab) & 0x80;
|
||||||
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
|
||||||
@ -207,22 +246,21 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
|||||||
if (functionNumber > 31)
|
if (functionNumber > 31)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
int reg = lookupSpeedTable(cab);
|
LOCO * slot = 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=speedTable[reg].functions;
|
uint32_t previous=slot->functions;
|
||||||
uint32_t funcmask = (1UL<<functionNumber);
|
uint32_t funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
speedTable[reg].functions |= funcmask;
|
slot->functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
speedTable[reg].functions &= ~funcmask;
|
slot->functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
if (speedTable[reg].functions != previous) {
|
if (slot->functions != previous) {
|
||||||
if (functionNumber <= 28)
|
if (functionNumber <= 28)
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(slot->groupFlags, functionNumber);
|
||||||
CommandDistributor::broadcastLoco(reg);
|
CommandDistributor::broadcastLoco(slot);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -230,14 +268,13 @@ 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;
|
||||||
int reg = lookupSpeedTable(cab);
|
auto slot=lookupSpeedTable(cab);
|
||||||
if (reg<0) return;
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
speedTable[reg].functions ^= funcmask;
|
slot->functions ^= funcmask;
|
||||||
if (functionNumber <= 28) {
|
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)
|
// 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) {
|
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
|
||||||
int reg = lookupSpeedTable(cab);
|
auto slot = lookupSpeedTable(cab);
|
||||||
if (reg<0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
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.
|
// 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) {
|
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
|
||||||
int reg = lookupSpeedTable(cab);
|
auto slot = lookupSpeedTable(cab,false);
|
||||||
return (reg<0)?0:speedTable[reg].functions;
|
return slot?slot->functions:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 reg=lookupSpeedTable(cab,true);
|
auto slot=lookupSpeedTable(cab,true);
|
||||||
// drop and replace F29,30,31 (top 3 bits)
|
// 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
|
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==speedTable[reg].functions) return; // no change
|
if (newFunctions==slot->functions) return; // no change
|
||||||
speedTable[reg].functions=newFunctions;
|
slot->functions=newFunctions;
|
||||||
CommandDistributor::broadcastLoco(reg);
|
CommandDistributor::broadcastLoco(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
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
|
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
|
||||||
int reg=lookupSpeedTable(cab, false);
|
auto slot=lookupSpeedTable(cab, false);
|
||||||
if (reg>=0) {
|
if (slot) {
|
||||||
speedTable[reg].loco=0;
|
slot->loco=-1; // no longer used but not end of world
|
||||||
setThrottle2(cab,1); // ESTOP if this loco still on track
|
|
||||||
CommandDistributor::broadcastForgetLoco(cab);
|
CommandDistributor::broadcastForgetLoco(cab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -749,7 +783,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;
|
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 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.
|
||||||
int reg = lastLocoReminder+1;
|
auto slot = nextLocoReminder;
|
||||||
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
||||||
if (speedTable[reg].loco > 0) {
|
if (slot->loco > 0)
|
||||||
// have found loco to remind
|
if (!issueReminder(slot))
|
||||||
if (issueReminder(reg))
|
return;
|
||||||
lastLocoReminder = reg;
|
// a loco=0 is at the end of the list, a loco <0 is deleted
|
||||||
} else
|
if (slot->loco==0) nextLocoReminder = &speedTable[0];
|
||||||
lastLocoReminder = reg;
|
else nextLocoReminder=slot+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::issueReminder(int reg) {
|
int16_t normalize(byte speed) {
|
||||||
unsigned long functions=speedTable[reg].functions;
|
if (speed & 0x80) return speed & 0x7F;
|
||||||
int loco=speedTable[reg].loco;
|
return 0-1-speed;
|
||||||
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: {
|
||||||
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
// calculate any momentum change going on
|
||||||
setThrottle2(loco, speedTable[reg].speedCode);
|
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;
|
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)
|
||||||
@ -844,70 +919,85 @@ byte DCC::cv2(int cv) {
|
|||||||
return lowByte(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
|
// determine speed reg for this loco
|
||||||
int firstEmpty = MAX_LOCOS;
|
LOCO * firstEmpty=nullptr;
|
||||||
int reg;
|
SLOTLOOP {
|
||||||
for (reg = 0; reg < MAX_LOCOS; reg++) {
|
if (firstEmpty==nullptr && slot->loco<=0) firstEmpty=slot;
|
||||||
if (speedTable[reg].loco == locoId) break;
|
if (slot->loco == locoId) return slot;
|
||||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
if (slot->loco==0) break;
|
||||||
}
|
}
|
||||||
|
if (!autoCreate) return nullptr;
|
||||||
// return -1 if not found and not auto creating
|
if (firstEmpty==nullptr) {
|
||||||
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
// return last slot if full
|
||||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
DIAG(F("Too many locos, reusing last slot"));
|
||||||
if (reg >= MAX_LOCOS) {
|
firstEmpty=&speedTable[MAX_LOCOS-1];
|
||||||
DIAG(F("Too many locos"));
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
if (reg==firstEmpty){
|
// fill first empty slot with new entry
|
||||||
speedTable[reg].loco = locoId;
|
firstEmpty->loco = locoId;
|
||||||
speedTable[reg].speedCode=128; // default direction forward
|
firstEmpty->speedCode=128; // default direction forward
|
||||||
speedTable[reg].groupFlags=0;
|
firstEmpty->targetSpeed=128; // default direction forward
|
||||||
speedTable[reg].functions=0;
|
firstEmpty->groupFlags=0;
|
||||||
}
|
firstEmpty->functions=0;
|
||||||
if (reg > highestUsedReg) highestUsedReg = reg;
|
firstEmpty->momentumA=MOMENTUM_USE_DEFAULT;
|
||||||
return reg;
|
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) {
|
// Values stored are 255=MOMENTUM_USE_DEFAULT, or millis/MOMENTUM_FACTOR.
|
||||||
// broadcast stop/estop but dont change direction
|
// This is to keep the values in a byte rather than int16
|
||||||
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
// thus saving 2 bytes RAM per loco slot.
|
||||||
if (speedTable[reg].loco==0) continue;
|
LOCO* slot=lookupSpeedTable(locoId,true);
|
||||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
slot->momentumA=(accelerating<0)? MOMENTUM_USE_DEFAULT: (accelerating/MOMENTUM_FACTOR);
|
||||||
if (speedTable[reg].speedCode != newspeed) {
|
slot->momentumD=(decelerating<0)? MOMENTUM_USE_DEFAULT: (decelerating/MOMENTUM_FACTOR);
|
||||||
speedTable[reg].speedCode = newspeed;
|
return true;
|
||||||
CommandDistributor::broadcastLoco(reg);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine speed reg for this loco
|
|
||||||
int reg=lookupSpeedTable(loco);
|
void DCC::estopAll() {
|
||||||
if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
|
setThrottle2(0,1); // estop all locos
|
||||||
speedTable[reg].speedCode = speedCode;
|
TrackManager::setDCSignal(0,1);
|
||||||
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];
|
||||||
int DCC::lastLocoReminder = 0;
|
DCC::LOCO * DCC::nextLocoReminder = &DCC::speedTable[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;
|
||||||
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
SLOTLOOP {
|
||||||
if (speedTable[reg].loco>0) {
|
if (slot->loco==0) break; // no more locos
|
||||||
|
if (slot->loco>0) {
|
||||||
used ++;
|
used ++;
|
||||||
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
StringFormatter::send(stream,F("cab=%d, speed=%d, target=%d momentum=%d/%d\n"),
|
||||||
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
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
24
DCC.h
@ -3,7 +3,7 @@
|
|||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2021 Herb Morton
|
* © 2021 Herb Morton
|
||||||
* © 2020-2021 Harald Barth
|
* © 2020-2021 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC API
|
* This file is part of Asbelos DCC API
|
||||||
@ -59,8 +59,10 @@ 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);
|
||||||
@ -102,20 +104,30 @@ 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
|
||||||
|
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 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 cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
|
static bool setMomentum(int locoId,int16_t accelerating, int16_t decelerating);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
|
static byte defaultMomentumA; // Accelerating
|
||||||
|
static byte defaultMomentumD; // Accelerating
|
||||||
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(int reg);
|
static bool issueReminder(LOCO * slot);
|
||||||
static int lastLocoReminder;
|
static LOCO* nextLocoReminder;
|
||||||
static int highestUsedReg;
|
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* © 2020-2023 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
* © 2020-2021 M Steve Todd
|
* © 2020-2021 M Steve Todd
|
||||||
* © 2020-2021 Fred Decker
|
* © 2020-2021 Fred Decker
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2022 Colin Murdoch
|
* © 2022 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -68,7 +68,8 @@ 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
|
m, message to throttles (broadcast output)
|
||||||
|
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
|
||||||
@ -316,11 +317,8 @@ 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
|
||||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
if (p[0]<=0) break;
|
||||||
if (slot>=0)
|
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(p[0],false));
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,6 +488,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
return;
|
return;
|
||||||
#endif
|
#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>
|
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
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 <!>
|
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;
|
return;
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* © 2024 Paul M. Antoine
|
* © 2024 Paul M. Antoine
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2020-2023 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2025 Morten Nielsen
|
* © 2025 Morten Nielsen
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -586,6 +586,10 @@ void RMFT2::loop2() {
|
|||||||
driveLoco(operand);
|
driveLoco(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OPCODE_MOMENTUM:
|
||||||
|
DCC::setMomentum(loco,operand,getOperand(1));
|
||||||
|
break;
|
||||||
|
|
||||||
case OPCODE_FORGET:
|
case OPCODE_FORGET:
|
||||||
if (loco!=0) {
|
if (loco!=0) {
|
||||||
DCC::forgetLoco(loco);
|
DCC::forgetLoco(loco);
|
||||||
@ -699,7 +703,7 @@ void RMFT2::loop2() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_PAUSE:
|
case OPCODE_PAUSE:
|
||||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
DCC::estopAll(); // pause all locos on the track
|
||||||
pausingTask=this;
|
pausingTask=this;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
* © 2025 Morten Nielsen
|
||||||
@ -36,6 +36,7 @@
|
|||||||
//
|
//
|
||||||
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,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
* © 2020-2025 Chris Harlow. All rights reserved.
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
* © 2025 Morten Nielsen
|
||||||
@ -99,6 +99,7 @@
|
|||||||
#undef LCC
|
#undef LCC
|
||||||
#undef LCCX
|
#undef LCCX
|
||||||
#undef LCN
|
#undef LCN
|
||||||
|
#undef MOMENTUM
|
||||||
#undef MOVETT
|
#undef MOVETT
|
||||||
#undef NEOPIXEL
|
#undef NEOPIXEL
|
||||||
#undef NEOPIXEL_OFF
|
#undef NEOPIXEL_OFF
|
||||||
@ -271,6 +272,7 @@
|
|||||||
#define LCC(eventid)
|
#define LCC(eventid)
|
||||||
#define LCCX(senderid,eventid)
|
#define LCCX(senderid,eventid)
|
||||||
#define LCD(row,msg)
|
#define LCD(row,msg)
|
||||||
|
#define MOMENTUM(accel,decel...)
|
||||||
#define SCREEN(display,row,msg)
|
#define SCREEN(display,row,msg)
|
||||||
#define LCN(msg)
|
#define LCN(msg)
|
||||||
#define MESSAGE(msg)
|
#define MESSAGE(msg)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2020-2023 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -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::setThrottle(0,1,true); // pause all locos on the track
|
DCC::estopAll(); // pause all locos on the track
|
||||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2025 Chris Harlow
|
||||||
* © 2022-2023 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* © 2025 Morten Nielsen
|
* © 2025 Morten Nielsen
|
||||||
@ -565,6 +565,7 @@ 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(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 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),\
|
#define NEOPIXEL(id,r,g,b,count...) OPCODE_NEOPIXEL,V(id),\
|
||||||
OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\
|
OPCODE_PAD,V(((r & 0xff)<<8) | (g & 0xff)),\
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Chris Harlow
|
* © 2022-2025 Chris Harlow
|
||||||
* © 2022-2024 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
* © 2023-2024 Paul M. Antoine
|
* © 2023-2024 Paul M. Antoine
|
||||||
* © 2024 Herb Morton
|
* © 2024 Herb Morton
|
||||||
@ -379,7 +379,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::getThrottleSpeedByte(trackDCAddr[t]),
|
track[t]->setDCSignal(DCC::getLocoSpeedByte(trackDCAddr[t]),
|
||||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#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.5.0 - New version on devel
|
||||||
// 5.4.0 - New version on master
|
// 5.4.0 - New version on master
|
||||||
// 5.2.96 - EXRAIL additions XFWD() and XREV()
|
// 5.2.96 - EXRAIL additions XFWD() and XREV()
|
||||||
|
Loading…
Reference in New Issue
Block a user