mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-23 12:51:24 +01:00
711 lines
24 KiB
C++
711 lines
24 KiB
C++
/*
|
|
* © 2022 Chris Harlow
|
|
* © 2022-2024 Harald Barth
|
|
* © 2023-2024 Paul M. Antoine
|
|
* © 2024 Herb Morton
|
|
* © 2023 Colin Murdoch
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of DCC++EX
|
|
*
|
|
* This is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* It is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "defines.h"
|
|
#include "TrackManager.h"
|
|
#include "FSH.h"
|
|
#include "DCCWaveform.h"
|
|
#include "DCC.h"
|
|
#include "MotorDriver.h"
|
|
#include "DCCTimer.h"
|
|
#include "DIAG.h"
|
|
#include "CommandDistributor.h"
|
|
#include "DCCEXParser.h"
|
|
#include "KeywordHasher.h"
|
|
// Virtualised Motor shield multi-track hardware Interface
|
|
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
|
|
|
#define APPLY_BY_MODE(findmode,function) \
|
|
FOR_EACH_TRACK(t) \
|
|
if (track[t]->getMode() & findmode) \
|
|
track[t]->function;
|
|
|
|
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
|
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
|
|
|
int8_t TrackManager::lastTrack=-1;
|
|
bool TrackManager::progTrackSyncMain=false;
|
|
bool TrackManager::progTrackBoosted=false;
|
|
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
|
|
#endif
|
|
|
|
#ifdef ANALOG_READ_INTERRUPT
|
|
/*
|
|
* sampleCurrent() runs from Interrupt
|
|
*/
|
|
void TrackManager::sampleCurrent() {
|
|
static byte tr = 0;
|
|
byte trAtStart = tr;
|
|
static bool waiting = false;
|
|
|
|
if (waiting) {
|
|
if (! track[tr]->sampleCurrentFromHW()) {
|
|
return; // no result, continue to wait
|
|
}
|
|
// found value, advance at least one track
|
|
// for scope debug track[1]->setBrake(0);
|
|
waiting = false;
|
|
tr++;
|
|
if (tr > lastTrack) tr = 0;
|
|
if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) {
|
|
return; // We could continue but for prog track we
|
|
// rather do it in next interrupt beacuse
|
|
// that gives us well defined sampling point.
|
|
// For other tracks we care less unless we
|
|
// have only few (max 2) tracks.
|
|
}
|
|
}
|
|
if (!waiting) {
|
|
// look for a valid track to sample or until we are around
|
|
while (true) {
|
|
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) {
|
|
track[tr]->startCurrentFromHW();
|
|
// for scope debug track[1]->setBrake(1);
|
|
waiting = true;
|
|
break;
|
|
}
|
|
tr++;
|
|
if (tr > lastTrack) tr = 0;
|
|
if (tr == trAtStart) // we are through and nothing found to do
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// The setup call is done this way so that the tracks can be in a list
|
|
// from the config... the tracks default to NULL in the declaration
|
|
void TrackManager::Setup(const FSH * shieldname,
|
|
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
|
MotorDriver * track3, MotorDriver * track4, MotorDriver * track5,
|
|
MotorDriver * track6, MotorDriver * track7 ) {
|
|
addTrack(0,track0);
|
|
addTrack(1,track1);
|
|
addTrack(2,track2);
|
|
addTrack(3,track3);
|
|
addTrack(4,track4);
|
|
addTrack(5,track5);
|
|
addTrack(6,track6);
|
|
addTrack(7,track7);
|
|
|
|
// Default the first 2 tracks (which may be null) and perform HA waveform check.
|
|
setTrackMode(0,TRACK_MODE_MAIN);
|
|
#ifndef DISABLE_PROG
|
|
setTrackMode(1,TRACK_MODE_PROG);
|
|
#else
|
|
setTrackMode(1,TRACK_MODE_MAIN);
|
|
#endif
|
|
|
|
// Fault pin config for odd motor boards (example pololu)
|
|
FOR_EACH_TRACK(t) {
|
|
for (byte s=t+1;s<=lastTrack;s++) {
|
|
if (track[t]->getFaultPin() != UNUSED_PIN &&
|
|
track[t]->getFaultPin() == track[s]->getFaultPin()) {
|
|
track[t]->setCommonFaultPin();
|
|
track[s]->setCommonFaultPin();
|
|
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
|
|
}
|
|
}
|
|
}
|
|
DCC::setShieldName(shieldname);
|
|
}
|
|
|
|
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
|
track[t]=driver;
|
|
if (driver) {
|
|
track[t]->setPower(POWERMODE::OFF);
|
|
track[t]->setMode(TRACK_MODE_NONE);
|
|
track[t]->setTrackLetter('A'+t);
|
|
lastTrack=t;
|
|
}
|
|
}
|
|
|
|
// setDCCSignal(), called from interrupt context
|
|
// does assume ports are shadowed if they can be
|
|
void TrackManager::setDCCSignal( bool on) {
|
|
HAVE_PORTA(shadowPORTA=PORTA);
|
|
HAVE_PORTB(shadowPORTB=PORTB);
|
|
HAVE_PORTC(shadowPORTC=PORTC);
|
|
HAVE_PORTD(shadowPORTD=PORTD);
|
|
HAVE_PORTE(shadowPORTE=PORTE);
|
|
HAVE_PORTF(shadowPORTF=PORTF);
|
|
HAVE_PORTG(shadowPORTG=PORTG);
|
|
HAVE_PORTH(shadowPORTH=PORTH);
|
|
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
|
HAVE_PORTA(PORTA=shadowPORTA);
|
|
HAVE_PORTB(PORTB=shadowPORTB);
|
|
HAVE_PORTC(PORTC=shadowPORTC);
|
|
HAVE_PORTD(PORTD=shadowPORTD);
|
|
HAVE_PORTE(PORTE=shadowPORTE);
|
|
HAVE_PORTF(PORTF=shadowPORTF);
|
|
HAVE_PORTG(PORTG=shadowPORTG);
|
|
HAVE_PORTH(PORTH=shadowPORTH);
|
|
}
|
|
|
|
// setPROGSignal(), called from interrupt context
|
|
// does assume ports are shadowed if they can be
|
|
void TrackManager::setPROGSignal( bool on) {
|
|
HAVE_PORTA(shadowPORTA=PORTA);
|
|
HAVE_PORTB(shadowPORTB=PORTB);
|
|
HAVE_PORTC(shadowPORTC=PORTC);
|
|
HAVE_PORTD(shadowPORTD=PORTD);
|
|
HAVE_PORTE(shadowPORTE=PORTE);
|
|
HAVE_PORTF(shadowPORTF=PORTF);
|
|
HAVE_PORTG(shadowPORTG=PORTG);
|
|
HAVE_PORTH(shadowPORTH=PORTH);
|
|
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
|
HAVE_PORTA(PORTA=shadowPORTA);
|
|
HAVE_PORTB(PORTB=shadowPORTB);
|
|
HAVE_PORTC(PORTC=shadowPORTC);
|
|
HAVE_PORTD(PORTD=shadowPORTD);
|
|
HAVE_PORTE(PORTE=shadowPORTE);
|
|
HAVE_PORTF(PORTF=shadowPORTF);
|
|
HAVE_PORTG(PORTG=shadowPORTG);
|
|
HAVE_PORTH(PORTH=shadowPORTH);
|
|
}
|
|
|
|
// setDCSignal(), called from normal context
|
|
// MotorDriver::setDCSignal handles shadowed IO port changes.
|
|
// with interrupts turned off around the critical section
|
|
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
|
FOR_EACH_TRACK(t) {
|
|
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
|
if (track[t]->getMode() & TRACK_MODE_DC)
|
|
track[t]->setDCSignal(speedbyte, DCC::getThrottleFrequency(trackDCAddr[t]));
|
|
}
|
|
}
|
|
|
|
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
|
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
|
|
|
// Remember track mode we came from for later
|
|
TRACK_MODE oldmode = track[trackToSet]->getMode();
|
|
|
|
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
|
// DC tracks require a motorDriver that can set brake!
|
|
if (mode & TRACK_MODE_DC) {
|
|
#if defined(ARDUINO_AVR_UNO)
|
|
DIAG(F("Uno has no PWM timers available for DC"));
|
|
return false;
|
|
#endif
|
|
if (!track[trackToSet]->brakeCanPWM()) {
|
|
DIAG(F("Brake pin can't PWM: No DC"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
// remove pin from MUX matrix and turn it off
|
|
pinpair p = track[trackToSet]->getSignalPin();
|
|
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
|
gpio_reset_pin((gpio_num_t)p.pin);
|
|
if (p.invpin != UNUSED_PIN) {
|
|
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
|
gpio_reset_pin((gpio_num_t)p.invpin);
|
|
}
|
|
#ifdef BOOSTER_INPUT
|
|
if (mode & TRACK_MODE_BOOST) {
|
|
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
|
|
pinMode(BOOSTER_INPUT, INPUT);
|
|
gpio_matrix_in(BOOSTER_INPUT, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
|
|
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
|
|
if (p.invpin != UNUSED_PIN) {
|
|
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
|
|
}
|
|
} else // elseif clause continues
|
|
#endif
|
|
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
|
|
// gpio_reset_pin may reset to input
|
|
pinMode(p.pin, OUTPUT);
|
|
if (p.invpin != UNUSED_PIN)
|
|
pinMode(p.invpin, OUTPUT);
|
|
}
|
|
|
|
#endif
|
|
#ifndef DISABLE_PROG
|
|
if (mode & TRACK_MODE_PROG) {
|
|
// only allow 1 track to be prog
|
|
FOR_EACH_TRACK(t)
|
|
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
|
|
track[t]->setPower(POWERMODE::OFF);
|
|
track[t]->setMode(TRACK_MODE_NONE);
|
|
track[t]->makeProgTrack(false); // revoke prog track special handling
|
|
streamTrackState(NULL,t);
|
|
}
|
|
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
|
} else {
|
|
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
|
}
|
|
#endif
|
|
|
|
// When a track is switched, we must clear any side effects of its previous
|
|
// state, otherwise trains run away or just dont move.
|
|
|
|
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
|
if (mode & TRACK_MODE_DC) {
|
|
if (trackDCAddr[trackToSet] != dcAddr) {
|
|
// new or changed DC Addr, run the new setup
|
|
if (trackDCAddr[trackToSet] != 0) {
|
|
// if we change dcAddr and not only
|
|
// change from another mode,
|
|
// first detach old DC signal
|
|
track[trackToSet]->detachDCSignal();
|
|
}
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
int trackfound = -1;
|
|
FOR_EACH_TRACK(t) {
|
|
//DIAG(F("Checking track %c mode %x dcAddr %d"), 'A'+t, track[t]->getMode(), trackDCAddr[t]);
|
|
if (t != trackToSet // not our track
|
|
&& (track[t]->getMode() & TRACK_MODE_DC) // right mode
|
|
&& trackDCAddr[t] == dcAddr) { // right addr
|
|
//DIAG(F("Found track %c"), 'A'+t);
|
|
trackfound = t;
|
|
break;
|
|
}
|
|
}
|
|
if (trackfound > -1) {
|
|
DCCTimer::DCCEXanalogCopyChannel(track[trackfound]->getBrakePinSigned(),
|
|
track[trackToSet]->getBrakePinSigned());
|
|
}
|
|
#endif
|
|
}
|
|
// set future DC Addr;
|
|
trackDCAddr[trackToSet]=dcAddr;
|
|
} else {
|
|
// DCC tracks need to have set the PWM to zero or they will not work.
|
|
track[trackToSet]->detachDCSignal();
|
|
track[trackToSet]->setBrake(false);
|
|
trackDCAddr[trackToSet]=0; // clear that an addr is set for DC as this is not a DC track
|
|
}
|
|
track[trackToSet]->setMode(mode);
|
|
|
|
// BOOST:
|
|
// Leave it as is
|
|
// otherwise:
|
|
// EXT is a special case where the signal pin is
|
|
// turned off. So unless that is set, the signal
|
|
// pin should be turned on
|
|
if (!(mode & TRACK_MODE_BOOST))
|
|
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
|
|
|
|
#ifndef ARDUINO_ARCH_ESP32
|
|
// re-evaluate HighAccuracy mode
|
|
// We can only do this is all main and prog tracks agree
|
|
bool canDo=true;
|
|
FOR_EACH_TRACK(t) {
|
|
// DC tracks must not have the DCC PWM switched on
|
|
// so we globally turn it off if one of the PWM
|
|
// capable tracks is now DC or DCX.
|
|
if (track[t]->getMode() & TRACK_MODE_DC) {
|
|
if (track[t]->isPWMCapable()) {
|
|
canDo=false; // this track is capable but can not run PWM
|
|
break; // in this mode, so abort and prevent globally below
|
|
} else {
|
|
track[t]->trackPWM=false; // this track sure can not run with PWM
|
|
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
|
}
|
|
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
|
|
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
|
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
|
canDo &= track[t]->trackPWM;
|
|
}
|
|
}
|
|
if (!canDo) {
|
|
// if we discover that HA mode was globally impossible
|
|
// we must adjust the trackPWM capabilities
|
|
FOR_EACH_TRACK(t) {
|
|
track[t]->trackPWM=false;
|
|
//DIAG(F("Track %c trackPWM 0 (global override)"), t+'A');
|
|
}
|
|
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
|
}
|
|
#else
|
|
// For ESP32 we just reinitialize the DCC Waveform
|
|
DCCWaveform::begin();
|
|
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
|
|
track[trackToSet]->setMode(mode);
|
|
#endif
|
|
|
|
// This block must be AFTER the PWM-Timer modifications
|
|
if (mode & TRACK_MODE_DC) {
|
|
// DC tracks need to be given speed of the throttle for that cab address
|
|
// otherwise will not match other tracks on same cab.
|
|
// This also needs to allow for inverted DCX
|
|
applyDCSpeed(trackToSet);
|
|
}
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
#ifndef DISABLE_PROG
|
|
if (tempProgTrack == trackToSet && oldmode & TRACK_MODE_MAIN && !(mode & TRACK_MODE_PROG)) {
|
|
// If we just take away the prog track, the join should not
|
|
// be active either. So do in effect an unjoin
|
|
//DIAG(F("Unsync"));
|
|
tempProgTrack = MAX_TRACKS+1;
|
|
progTrackSyncMain=false;
|
|
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,LOW);
|
|
}
|
|
#endif
|
|
#endif
|
|
// Turn off power if we changed the mode of this track
|
|
if (mode != oldmode) {
|
|
track[trackToSet]->setPower(POWERMODE::OFF);
|
|
}
|
|
|
|
streamTrackState(NULL,trackToSet);
|
|
//DIAG(F("TrackMode=%d"),mode);
|
|
return true;
|
|
}
|
|
|
|
void TrackManager::applyDCSpeed(byte t) {
|
|
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
|
DCC::getThrottleFrequency(trackDCAddr[t]));
|
|
}
|
|
|
|
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
|
{
|
|
|
|
if (params==0) { // <=> List track assignments
|
|
FOR_EACH_TRACK(t)
|
|
streamTrackState(stream,t);
|
|
return true;
|
|
|
|
}
|
|
|
|
p[0]-="A"_hk; // convert A... to 0....
|
|
|
|
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
|
return false;
|
|
|
|
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
|
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
|
if (params==2 && p[1]=="MAIN_INV"_hk) // <= id MAIN_INV>
|
|
return setTrackMode(p[0],TRACK_MODE_MAIN_INV);
|
|
if (params==2 && p[1]=="MAIN_AUTO"_hk) // <= id MAIN_AUTO>
|
|
return setTrackMode(p[0],TRACK_MODE_MAIN_AUTO);
|
|
|
|
#ifndef DISABLE_PROG
|
|
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
|
return setTrackMode(p[0],TRACK_MODE_PROG);
|
|
#endif
|
|
|
|
if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE>
|
|
return setTrackMode(p[0],TRACK_MODE_NONE);
|
|
|
|
if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
|
|
return setTrackMode(p[0],TRACK_MODE_EXT);
|
|
#ifdef BOOSTER_INPUT
|
|
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
|
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
|
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
|
if (TRACK_MODE_BOOST_INV != 0 && // compile time optimization
|
|
params==2 && p[1]=="BOOST_INV"_hk) // <= id BOOST_INV>
|
|
return setTrackMode(p[0],TRACK_MODE_BOOST_INV);
|
|
if (TRACK_MODE_BOOST_AUTO != 0 && // compile time optimization
|
|
params==2 && p[1]=="BOOST_AUTO"_hk) // <= id BOOST_AUTO>
|
|
return setTrackMode(p[0],TRACK_MODE_BOOST_AUTO);
|
|
#endif
|
|
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
|
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_AUTO);
|
|
|
|
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
|
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_INV);
|
|
|
|
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
|
|
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
|
|
|
if (params==3 && (p[1]=="DC_INV"_hk || // <= id DC_INV cab>
|
|
p[1]=="DCX"_hk) && p[2]>0) // <= id DCX cab>
|
|
return setTrackMode(p[0],TRACK_MODE_DC_INV,p[2]);
|
|
|
|
return false;
|
|
}
|
|
|
|
const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
|
const FSH *modename=F("---");
|
|
|
|
if (tm & TRACK_MODE_MAIN) {
|
|
if(tm & TRACK_MODIFIER_AUTO)
|
|
modename=F("MAIN A");
|
|
else if (tm & TRACK_MODIFIER_INV)
|
|
modename=F("MAIN I>\n");
|
|
else
|
|
modename=F("MAIN");
|
|
}
|
|
#ifndef DISABLE_PROG
|
|
else if (tm & TRACK_MODE_PROG)
|
|
modename=F("PROG");
|
|
#endif
|
|
else if (tm & TRACK_MODE_NONE)
|
|
modename=F("NONE");
|
|
else if(tm & TRACK_MODE_EXT)
|
|
modename=F("EXT");
|
|
else if(tm & TRACK_MODE_BOOST) {
|
|
if(tm & TRACK_MODIFIER_AUTO)
|
|
modename=F("BOOST A");
|
|
else if (tm & TRACK_MODIFIER_INV)
|
|
modename=F("BOOST I");
|
|
else
|
|
modename=F("BOOST");
|
|
}
|
|
else if (tm & TRACK_MODE_DC) {
|
|
if (tm & TRACK_MODIFIER_INV)
|
|
modename=F("DCX");
|
|
else
|
|
modename=F("DC");
|
|
}
|
|
return modename;
|
|
}
|
|
|
|
// null stream means send to commandDistributor for broadcast
|
|
void TrackManager::streamTrackState(Print* stream, byte t) {
|
|
const FSH *format;
|
|
|
|
if (track[t]==NULL) return;
|
|
TRACK_MODE tm = track[t]->getMode();
|
|
if (tm & TRACK_MODE_DC)
|
|
format=F("<= %c %S %d>\n");
|
|
else
|
|
format=F("<= %c %S>\n");
|
|
|
|
const FSH *modename=getModeName(tm);
|
|
if (stream) { // null stream means send to commandDistributor for broadcast
|
|
StringFormatter::send(stream,format,'A'+t, modename, trackDCAddr[t]);
|
|
} else {
|
|
CommandDistributor::broadcastTrackState(format,'A'+t, modename, trackDCAddr[t]);
|
|
CommandDistributor::broadcastPower();
|
|
}
|
|
|
|
}
|
|
|
|
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
|
|
|
void TrackManager::loop() {
|
|
DCCWaveform::loop();
|
|
#ifndef DISABLE_PROG
|
|
DCCACK::loop();
|
|
#endif
|
|
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
|
|
nextCycleTrack++;
|
|
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
|
if (track[nextCycleTrack]==NULL) return;
|
|
MotorDriver * motorDriver=track[nextCycleTrack];
|
|
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
|
|
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
|
}
|
|
|
|
MotorDriver * TrackManager::getProgDriver() {
|
|
FOR_EACH_TRACK(t)
|
|
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
|
std::vector<MotorDriver *> v;
|
|
FOR_EACH_TRACK(t)
|
|
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
|
|
return v;
|
|
}
|
|
#endif
|
|
|
|
// Set track power for all tracks with this mode
|
|
void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermode) {
|
|
bool didChange=false;
|
|
FOR_EACH_TRACK(t) {
|
|
MotorDriver *driver=track[t];
|
|
TRACK_MODE trackmodeOfTrack = driver->getMode();
|
|
if (trackmodeToMatch & trackmodeOfTrack) {
|
|
if (powermode != driver->getPower())
|
|
didChange=true;
|
|
if (powermode == POWERMODE::ON) {
|
|
if (trackmodeOfTrack & TRACK_MODE_DC) {
|
|
driver->setBrake(true); // DC starts with brake on
|
|
applyDCSpeed(t); // speed match DCC throttles
|
|
} else {
|
|
// toggle brake before turning power on - resets overcurrent error
|
|
// on the Pololu board if brake is wired to ^D2.
|
|
driver->setBrake(true);
|
|
driver->setBrake(false); // DCC runs with brake off
|
|
}
|
|
}
|
|
driver->setPower(powermode);
|
|
}
|
|
}
|
|
if (didChange)
|
|
CommandDistributor::broadcastPower();
|
|
}
|
|
|
|
// Set track power for this track, inependent of mode
|
|
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
|
MotorDriver *driver=track[t];
|
|
if (driver == NULL) { // track is not defined at all
|
|
DIAG(F("Error: Track %c does not exist"), t+'A');
|
|
return;
|
|
}
|
|
TRACK_MODE trackmode = driver->getMode();
|
|
POWERMODE oldpower = driver->getPower();
|
|
if (trackmode & TRACK_MODE_NONE) {
|
|
driver->setBrake(true); // Track is unused. Brake is good to have.
|
|
powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
|
|
} else if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
|
|
if (powermode == POWERMODE::ON) {
|
|
driver->setBrake(true); // DC starts with brake on
|
|
applyDCSpeed(t); // speed match DCC throttles
|
|
}
|
|
} else /* MAIN PROG EXT BOOST */ {
|
|
if (powermode == POWERMODE::ON) {
|
|
// toggle brake before turning power on - resets overcurrent error
|
|
// on the Pololu board if brake is wired to ^D2.
|
|
driver->setBrake(true);
|
|
driver->setBrake(false); // DCC runs with brake off
|
|
}
|
|
}
|
|
driver->setPower(powermode);
|
|
if (oldpower != driver->getPower())
|
|
CommandDistributor::broadcastPower();
|
|
}
|
|
|
|
// returns state of the one and only prog track
|
|
POWERMODE TrackManager::getProgPower() {
|
|
FOR_EACH_TRACK(t)
|
|
if (track[t]->getMode() & TRACK_MODE_PROG)
|
|
return track[t]->getPower(); // optimize: there is max one prog track
|
|
return POWERMODE::OFF;
|
|
}
|
|
|
|
// returns on if all are on. returns off otherwise
|
|
POWERMODE TrackManager::getMainPower() {
|
|
POWERMODE result = POWERMODE::OFF;
|
|
FOR_EACH_TRACK(t) {
|
|
if (track[t]->getMode() & TRACK_MODE_MAIN) {
|
|
POWERMODE p = track[t]->getPower();
|
|
if (p == POWERMODE::OFF)
|
|
return POWERMODE::OFF; // done and out
|
|
if (p == POWERMODE::ON)
|
|
result = POWERMODE::ON;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool TrackManager::getPower(byte t, char s[]) {
|
|
if (t > lastTrack)
|
|
return false;
|
|
if (track[t]) {
|
|
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
|
|
s[2] = t + 'A';
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
|
// This function is for backward JMRI compatibility only
|
|
// It reports the first track only, as main, regardless of track settings.
|
|
// <c MeterName value C/V unit min max res warn>
|
|
#ifdef HAS_ENOUGH_MEMORY
|
|
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
|
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
|
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
|
#else
|
|
(void)stream;
|
|
#endif
|
|
}
|
|
|
|
void TrackManager::reportCurrent(Print* stream) {
|
|
StringFormatter::send(stream,F("<jI"));
|
|
FOR_EACH_TRACK(t) {
|
|
StringFormatter::send(stream, F(" %d"),
|
|
(track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
|
|
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
|
|
}
|
|
StringFormatter::send(stream,F(">\n"));
|
|
}
|
|
|
|
void TrackManager::reportGauges(Print* stream) {
|
|
StringFormatter::send(stream,F("<jG"));
|
|
FOR_EACH_TRACK(t) {
|
|
StringFormatter::send(stream, F(" %d"),
|
|
track[t]->raw2mA(track[t]->getRawCurrentTripValue()));
|
|
}
|
|
StringFormatter::send(stream,F(">\n"));
|
|
}
|
|
|
|
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
|
joinRelay=joinRelayPin;
|
|
if (joinRelay!=UNUSED_PIN) {
|
|
pinMode(joinRelay,OUTPUT);
|
|
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
|
}
|
|
}
|
|
|
|
void TrackManager::setJoin(bool joined) {
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
if (joined) { // if we go into joined mode (PROG acts as MAIN)
|
|
FOR_EACH_TRACK(t) {
|
|
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
|
|
tempProgTrack = t; // remember PROG track
|
|
setTrackMode(t, TRACK_MODE_MAIN);
|
|
track[t]->setPower(POWERMODE::ON); // if joined, always on
|
|
break; // there is only one prog track, done
|
|
}
|
|
}
|
|
} else {
|
|
if (tempProgTrack != MAX_TRACKS+1) {
|
|
// setTrackMode defaults to power off, so we
|
|
// need to preserve that state.
|
|
POWERMODE tPTmode = track[tempProgTrack]->getPower(); // get current power status of this track
|
|
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
|
|
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
|
|
tempProgTrack = MAX_TRACKS+1;
|
|
} else {
|
|
DIAG(F("Unjoin but no remembered prog track"));
|
|
}
|
|
}
|
|
#endif
|
|
progTrackSyncMain=joined;
|
|
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
|
}
|
|
|
|
bool TrackManager::isPowerOn(byte t) {
|
|
if (track[t]->getPower()!=POWERMODE::ON)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool TrackManager::isProg(byte t) {
|
|
if (track[t]->getMode() & TRACK_MODE_PROG)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
TRACK_MODE TrackManager::getMode(byte t) {
|
|
return (track[t]->getMode());
|
|
}
|
|
|
|
int16_t TrackManager::returnDCAddr(byte t) {
|
|
return (trackDCAddr[t]);
|
|
}
|