diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4e5ecb9..c4b489d 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -28,6 +28,7 @@ #include "defines.h" #include "DCCWaveform.h" #include "DCC.h" +#include "TrackManager.h" #if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON) // This section of CommandDistributor is simply not relevant on a uno or similar @@ -119,9 +120,9 @@ void CommandDistributor::broadcastLoco(byte slot) { } void CommandDistributor::broadcastPower() { - bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON; - bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON; - bool join=DCCWaveform::progTrackSyncMain; + bool main=TrackManager::getMainPower()==POWERMODE::ON; + bool prog=TrackManager::getProgPower()==POWERMODE::ON; + bool join=DCCWaveform::isJoined(); const FSH * reason=F(""); char state='1'; if (main && prog && join) reason=F(" JOIN"); diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 80d8b4e..52cf822 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -89,7 +89,7 @@ void setup() // Standard supported devices have pre-configured macros but custome hardware installations require // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - DCC::begin(MOTOR_SHIELD_TYPE); + TrackManager::Setup(MOTOR_SHIELD_TYPE); // Start RMFT aka EX-RAIL (ignored if no automnation) RMFT::begin(); diff --git a/DCC.cpp b/DCC.cpp index 6674fb9..7790936 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -56,10 +56,9 @@ const byte FN_GROUP_4=0x08; const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; -byte DCC::joinRelay=UNUSED_PIN; byte DCC::globalSpeedsteps=128; -void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { +void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); @@ -72,16 +71,9 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv EEStore::init(); #endif - DCCWaveform::begin(mainDriver,progDriver); + DCCWaveform::begin(); } -void DCC::setJoinRelayPin(byte joinRelayPin) { - joinRelay=joinRelayPin; - if (joinRelay!=UNUSED_PIN) { - pinMode(joinRelay,OUTPUT); - digitalWrite(joinRelay,LOW); // LOW is relay disengaged - } -} void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { byte speedCode = (tSpeed & 0x7F) + tDirection * 128; @@ -296,14 +288,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { DCCWaveform::mainTrack.schedulePacket(b, nB, 4); } -void DCC::setProgTrackSyncMain(bool on) { - if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW); - DCCWaveform::progTrackSyncMain=on; -} -void DCC::setProgTrackBoost(bool on) { - DCCWaveform::progTrackBoosted=on; -} - FSH* DCC::getMotorShieldName() { return shieldName; } @@ -514,35 +498,35 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { }; void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { - ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback); + DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback); } void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); + else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); } void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { - ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback); + DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback); } void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); + else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); } void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback); + else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback); } void DCC::readCV(int16_t cv, ACK_CALLBACK callback) { - ackManagerSetup(cv, 0,READ_CV_PROG, callback); + DCCACK::Setup(cv, 0,READ_CV_PROG, callback); } void DCC::getLocoId(ACK_CALLBACK callback) { - ackManagerSetup(0,0, LOCO_ID_PROG, callback); + DCCACK::Setup(0,0, LOCO_ID_PROG, callback); } void DCC::setLocoId(int id,ACK_CALLBACK callback) { @@ -551,9 +535,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) { return; } if (id<=HIGHEST_SHORT_ADDR) - ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback); + DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback); else - ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback); + DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback); } void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco @@ -570,8 +554,8 @@ void DCC::forgetAllLocos() { // removes all speed reminders byte DCC::loopStatus=0; void DCC::loop() { - DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks - ackManagerLoop(); // maintain prog track ack manager + DCCWaveform::loop(); // power overload checks + DCCACK::loop(); // maintain prog track ack manager issueReminders(); } @@ -695,319 +679,6 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { DCC::LOCO DCC::speedTable[MAX_LOCOS]; int DCC::nextLoco = 0; -//ACK MANAGER -ackOp const * DCC::ackManagerProg; -ackOp const * DCC::ackManagerProgStart; -byte DCC::ackManagerByte; -byte DCC::ackManagerByteVerify; -byte DCC::ackManagerStash; -int DCC::ackManagerWord; -byte DCC::ackManagerRetry; -byte DCC::ackRetry = 2; -int16_t DCC::ackRetrySum; -int16_t DCC::ackRetryPSum; -int DCC::ackManagerCv; -byte DCC::ackManagerBitNum; -bool DCC::ackReceived; -bool DCC::ackManagerRejoin; - -CALLBACK_STATE DCC::callbackState=READY; - -ACK_CALLBACK DCC::ackManagerCallback; - -void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { - if (!DCCWaveform::progTrack.canMeasureCurrent()) { - callback(-2); - return; - } - - ackManagerRejoin=DCCWaveform::progTrackSyncMain; - if (ackManagerRejoin ) { - // Change from JOIN must zero resets packet. - setProgTrackSyncMain(false); - DCCWaveform::progTrack.sentResetsSincePacket = 0; - } - - DCCWaveform::progTrack.autoPowerOff=false; - if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) { - DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards - if (Diag::ACK) DIAG(F("Auto Prog power on")); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - if (MotorDriver::commonFaultPin) - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.sentResetsSincePacket = 0; - } - - ackManagerCv = cv; - ackManagerProg = program; - ackManagerProgStart = program; - ackManagerRetry = ackRetry; - ackManagerByte = byteValueOrBitnum; - ackManagerByteVerify = byteValueOrBitnum; - ackManagerBitNum=byteValueOrBitnum; - ackManagerCallback = callback; -} - -void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) { - ackManagerWord=wordval; - ackManagerSetup(0, 0, program, callback); - } - -const byte RESET_MIN=8; // tuning of reset counter before sending message - -// checkRessets return true if the caller should yield back to loop and try later. -bool DCC::checkResets(uint8_t numResets) { - return DCCWaveform::progTrack.sentResetsSincePacket < numResets; -} - -void DCC::ackManagerLoop() { - while (ackManagerProg) { - byte opcode=GETFLASH(ackManagerProg); - - // breaks from this switch will step to next prog entry - // returns from this switch will stay on same entry - // (typically waiting for a reset counter or ACK waiting, or when all finished.) - switch (opcode) { - case BASELINE: - if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return; - if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return; - DCCWaveform::progTrack.setAckBaseline(); - callbackState=READY; - break; - case W0: // write 0 bit - case W1: // write 1 bit - { - if (checkResets(RESET_MIN)) return; - if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); - byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; - byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - callbackState=AFTER_WRITE; - } - break; - - case WB: // write byte - { - if (checkResets( RESET_MIN)) return; - if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); - byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - callbackState=AFTER_WRITE; - } - break; - - case VB: // Issue validate Byte packet - { - if (checkResets( RESET_MIN)) return; - if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); - byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - } - break; - - case V0: - case V1: // Issue validate bit=0 or bit=1 packet - { - if (checkResets(RESET_MIN)) return; - if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); - byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; - byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - } - break; - - case WACK: // wait for ack (or absence of ack) - { - byte ackState=2; // keep polling - - ackState=DCCWaveform::progTrack.getAck(); - if (ackState==2) return; // keep polling - ackReceived=ackState==1; - break; // we have a genuine ACK result - } - case ITC0: - case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK) - if (ackReceived) { - callback(opcode==ITC0?0:1); - return; - } - break; - - case ITCB: // If True callback(byte) - if (ackReceived) { - callback(ackManagerByte); - return; - } - break; - - case ITCBV: // If True callback(byte) - Verify - if (ackReceived) { - if (ackManagerByte == ackManagerByteVerify) { - ackRetrySum ++; - LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum); - } - callback(ackManagerByte); - return; - } - break; - - case ITCB7: // If True callback(byte & 0x7F) - if (ackReceived) { - callback(ackManagerByte & 0x7F); - return; - } - break; - - case NAKFAIL: // If nack callback(-1) - if (!ackReceived) { - callback(-1); - return; - } - break; - - case FAIL: // callback(-1) - callback(-1); - return; - - case BIV: // ackManagerByte initial value - ackManagerByte = ackManagerByteVerify; - break; - - case STARTMERGE: - ackManagerBitNum=7; - ackManagerByte=0; - break; - - case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) - ackManagerByte <<= 1; - // ackReceived means bit is zero. - if (!ackReceived) ackManagerByte |= 1; - ackManagerBitNum--; - break; - - case SETBIT: - ackManagerProg++; - ackManagerBitNum=GETFLASH(ackManagerProg); - break; - - case SETCV: - ackManagerProg++; - ackManagerCv=GETFLASH(ackManagerProg); - break; - - case SETBYTE: - ackManagerProg++; - ackManagerByte=GETFLASH(ackManagerProg); - break; - - case SETBYTEH: - ackManagerByte=highByte(ackManagerWord); - break; - - case SETBYTEL: - ackManagerByte=lowByte(ackManagerWord); - break; - - case STASHLOCOID: - ackManagerStash=ackManagerByte; // stash value from CV17 - break; - - case COMBINELOCOID: - // ackManagerStash is cv17, ackManagerByte is CV 18 - callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); - return; - - case ITSKIP: - if (!ackReceived) break; - // SKIP opcodes until SKIPTARGET found - while (opcode!=SKIPTARGET) { - ackManagerProg++; - opcode=GETFLASH(ackManagerProg); - } - break; - case SKIPTARGET: - break; - default: - DIAG(F("!! ackOp %d FAULT!!"),opcode); - callback( -1); - return; - - } // end of switch - ackManagerProg++; - } -} - -void DCC::callback(int value) { - // check for automatic retry - if (value == -1 && ackManagerRetry > 0) { - ackRetrySum ++; - LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum); - ackManagerRetry --; - ackManagerProg = ackManagerProgStart; - return; - } - - static unsigned long callbackStart; - // We are about to leave programming mode - // Rule 1: If we have written to a decoder we must maintain power for 100mS - // Rule 2: If we are re-joining the main track we must power off for 30mS - - switch (callbackState) { - case AFTER_WRITE: // first attempt to callback after a write operation - if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) { - callbackState=READY; - break; - } // lines 906-910 added. avoid wait after write. use 1 PROG - callbackStart=millis(); - callbackState=WAITING_100; - if (Diag::ACK) DIAG(F("Stable 100mS")); - break; - - case WAITING_100: // waiting for 100mS - if (millis()-callbackStart < 100) break; - // stable after power maintained for 100mS - - // If we are going to power off anyway, it doesnt matter - // but if we will keep the power on, we must off it for 30mS - if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY; - else { // Need to cycle power off and on - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); - callbackStart=millis(); - callbackState=WAITING_30; - if (Diag::ACK) DIAG(F("OFF 30mS")); - } - break; - - case WAITING_30: // waiting for 30mS with power off - if (millis()-callbackStart < 30) break; - //power has been off for 30mS - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - callbackState=READY; - break; - - case READY: // ready after read, or write after power delay and off period. - // power off if we powered it on - if (DCCWaveform::progTrack.autoPowerOff) { - if (Diag::ACK) DIAG(F("Auto Prog power off")); - DCCWaveform::progTrack.doAutoPowerOff(); - if (MotorDriver::commonFaultPin) - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - } - // Restore <1 JOIN> to state before BASELINE - if (ackManagerRejoin) { - setProgTrackSyncMain(true); - if (Diag::ACK) DIAG(F("Auto JOIN")); - } - - ackManagerProg=NULL; // no more steps to execute - if (Diag::ACK) DIAG(F("Callback(%d)"),value); - (ackManagerCallback)( value); - } -} void DCC::displayCabList(Print * stream) { diff --git a/DCC.h b/DCC.h index fbeb603..0effee2 100644 --- a/DCC.h +++ b/DCC.h @@ -36,48 +36,9 @@ #error short addr greater than 127 does not make sense #endif #endif +#include "DCCACK.h" const uint16_t LONG_ADDR_MARKER = 0x4000; -typedef void (*ACK_CALLBACK)(int16_t result); - -enum ackOp : byte -{ // Program opcodes for the ack Manager - BASELINE, // ensure enough resets sent before starting and obtain baseline current - W0, - W1, // issue write bit (0..1) packet - WB, // issue write byte packet - VB, // Issue validate Byte packet - V0, // Issue validate bit=0 packet - V1, // issue validate bit=1 packlet - WACK, // wait for ack (or absence of ack) - ITC1, // If True Callback(1) (if prevous WACK got an ACK) - ITC0, // If True callback(0); - ITCB, // If True callback(byte) - ITCBV, // If True callback(byte) - end of Verify Byte - ITCB7, // If True callback(byte &0x7F) - NAKFAIL, // if false callback(-1) - FAIL, // callback(-1) - BIV, // Set ackManagerByte to initial value for Verify retry - STARTMERGE, // Clear bit and byte settings ready for merge pass - MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) - SETBIT, // sets bit number to next prog byte - SETCV, // sets cv number to next prog byte - SETBYTE, // sets current byte to next prog byte - SETBYTEH, // sets current byte to word high byte - SETBYTEL, // sets current byte to word low byte - STASHLOCOID, // keeps current byte value for later - COMBINELOCOID, // combines current value with stashed value and returns it - ITSKIP, // skip to SKIPTARGET if ack true - SKIPTARGET = 0xFF // jump to target -}; - -enum CALLBACK_STATE : byte { - AFTER_WRITE, // Start callback sequence after something was written to the decoder - WAITING_100, // Waiting for 100mS of stable power - WAITING_30, // waiting to 30ms of power off gap. - READY, // Ready to complete callback - }; - // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created @@ -92,8 +53,7 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); - static void setJoinRelayPin(byte joinRelayPin); + static void begin(const FSH * motorShieldName); static void loop(); // Public DCC API functions @@ -110,9 +70,7 @@ public: static void updateGroupflags(byte &flags, int16_t functionNumber); static void setAccessory(int aAdd, byte aNum, bool activate); static bool writeTextPacket(byte *b, int nBytes); - static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable - static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply - + // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 static void readCV(int16_t cv, ACK_CALLBACK callback); static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error @@ -133,13 +91,7 @@ public: static inline void setGlobalSpeedsteps(byte s) { globalSpeedsteps = s; }; - static inline int16_t setAckRetry(byte retry) { - ackRetry = retry; - ackRetryPSum = ackRetrySum; - ackRetrySum = 0; // reset running total - return ackRetryPSum; - }; - + struct LOCO { int loco; @@ -148,9 +100,9 @@ public: unsigned long functions; }; static LOCO speedTable[MAX_LOCOS]; - + static byte cv1(byte opcode, int cv); + static byte cv2(int cv); private: - static byte joinRelay; static byte loopStatus; static void setThrottle2(uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); @@ -160,34 +112,11 @@ private: static FSH *shieldName; static byte globalSpeedsteps; - static byte cv1(byte opcode, int cv); - static byte cv2(int cv); + static int lookupSpeedTable(int locoId); static void issueReminders(); static void callback(int value); - // ACK MANAGER - static ackOp const *ackManagerProg; - static ackOp const *ackManagerProgStart; - static byte ackManagerByte; - static byte ackManagerByteVerify; - static byte ackManagerBitNum; - static int ackManagerCv; - static byte ackManagerRetry; - static byte ackRetry; - static int16_t ackRetrySum; - static int16_t ackRetryPSum; - static int ackManagerWord; - static byte ackManagerStash; - static bool ackReceived; - static bool ackManagerRejoin; - static ACK_CALLBACK ackManagerCallback; - static CALLBACK_STATE callbackState; - static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback); - static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback); - static void ackManagerLoop(); - static bool checkResets( uint8_t numResets); - static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) // NMRA codes # static const byte SET_SPEED = 0x3f; diff --git a/DCCACK.cpp b/DCCACK.cpp new file mode 100644 index 0000000..9198893 --- /dev/null +++ b/DCCACK.cpp @@ -0,0 +1,467 @@ +/* + * © 2021 M Steve Todd + * © 2021 Mike S + * © 2021 Fred Decker + * © 2020-2021 Harald Barth + * © 2020-2022 Chris Harlow + * All rights reserved. + * + * This file is part of CommandStation-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 . + */ +#include "DCCACK.h" +#include "DIAG.h" +#include "DCC.h" +#include "DCCWaveform.h" +#include "TrackManager.h" + +unsigned int DCCACK::minAckPulseDuration = 4000; // micros +unsigned int DCCACK::maxAckPulseDuration = 8500; // micros + +MotorDriver * DCCACK::progDriver=NULL; +ackOp const * DCCACK::ackManagerProg; +ackOp const * DCCACK::ackManagerProgStart; +byte DCCACK::ackManagerByte; +byte DCCACK::ackManagerByteVerify; +byte DCCACK::ackManagerStash; +int DCCACK::ackManagerWord; +byte DCCACK::ackManagerRetry; +byte DCCACK::ackRetry = 2; +int16_t DCCACK::ackRetrySum; +int16_t DCCACK::ackRetryPSum; +int DCCACK::ackManagerCv; +byte DCCACK::ackManagerBitNum; +bool DCCACK::ackReceived; +bool DCCACK::ackManagerRejoin; +volatile uint8_t DCCACK::numAckGaps=0; +volatile uint8_t DCCACK::numAckSamples=0; +uint8_t DCCACK::trailingEdgeCounter=0; + + + unsigned int DCCACK::ackPulseDuration; // micros + unsigned long DCCACK::ackPulseStart; // micros + volatile bool DCCACK::ackDetected; + unsigned long DCCACK::ackCheckStart; // millis + volatile bool DCCACK::ackPending; + bool DCCACK::autoPowerOff; + int DCCACK::ackThreshold; + int DCCACK::ackLimitmA; + int DCCACK::ackMaxCurrent; + unsigned int DCCACK::ackCheckDuration; // millis + + +CALLBACK_STATE DCCACK::callbackState=READY; + +ACK_CALLBACK DCCACK::ackManagerCallback; + +void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { + progDriver=TrackManager::getProgDriver(); + if (progDriver==NULL) { + callback(-3); // we dont have a prog track! + return; + } + if (!progDriver->canMeasureCurrent()) { + callback(-2); // our prog track cant measure current + return; + } + + ackManagerRejoin=DCCWaveform::isJoined(); + if (ackManagerRejoin ) { + // Change from JOIN must zero resets packet. + DCCWaveform::setJoin(false); + DCCWaveform::progTrack.sentResetsSincePacket = 0; + } + + autoPowerOff=false; + if (progDriver->getPower() == POWERMODE::OFF) { + autoPowerOff=true; // power off afterwards + if (Diag::ACK) DIAG(F("Auto Prog power on")); + progDriver->setPower(POWERMODE::ON); + + /* TODO !!! in MotorDriver surely! + if (MotorDriver::commonFaultPin) + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.sentResetsSincePacket = 0; + **/ + } + + + ackManagerCv = cv; + ackManagerProg = program; + ackManagerProgStart = program; + ackManagerRetry = ackRetry; + ackManagerByte = byteValueOrBitnum; + ackManagerByteVerify = byteValueOrBitnum; + ackManagerBitNum=byteValueOrBitnum; + ackManagerCallback = callback; +} + +void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) { + ackManagerWord=wordval; + Setup(0, 0, program, callback); + } + +const byte RESET_MIN=8; // tuning of reset counter before sending message + +// checkRessets return true if the caller should yield back to loop and try later. +bool DCCACK::checkResets(uint8_t numResets) { + return DCCWaveform::progTrack.sentResetsSincePacket < numResets; +} +// Operations applicable to PROG track ONLY. +// (yes I know I could have subclassed the main track but...) + +void DCCACK::setAckBaseline() { + int baseline=progDriver->getCurrentRaw(); + ackThreshold= baseline + progDriver->mA2raw(ackLimitmA); + if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), + baseline,progDriver->raw2mA(baseline), + ackThreshold,progDriver->raw2mA(ackThreshold), + minAckPulseDuration, maxAckPulseDuration); +} + +void DCCACK::setAckPending() { + ackMaxCurrent=0; + ackPulseStart=0; + ackPulseDuration=0; + ackDetected=false; + ackCheckStart=millis(); + numAckSamples=0; + numAckGaps=0; + ackPending=true; // interrupt routines will now take note +} + +byte DCCACK::getAck() { + if (ackPending) return (2); // still waiting + if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, + ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); + if (ackDetected) return (1); // Yes we had an ack + return(0); // pending set off but not detected means no ACK. +} + + +void DCCACK::loop() { + while (ackManagerProg) { + byte opcode=GETFLASH(ackManagerProg); + + // breaks from this switch will step to next prog entry + // returns from this switch will stay on same entry + // (typically waiting for a reset counter or ACK waiting, or when all finished.) + switch (opcode) { + case BASELINE: + if (progDriver->getPower()==POWERMODE::OVERLOAD) return; + if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return; + setAckBaseline(); + callbackState=AFTER_READ; + break; + case W0: // write 0 bit + case W1: // write 1 bit + { + if (checkResets(RESET_MIN)) return; + if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); + byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; + byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction }; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + callbackState=AFTER_WRITE; + } + break; + + case WB: // write byte + { + if (checkResets( RESET_MIN)) return; + if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); + byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte}; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + callbackState=AFTER_WRITE; + } + break; + + case VB: // Issue validate Byte packet + { + if (checkResets( RESET_MIN)) return; + if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); + byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte}; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + } + break; + + case V0: + case V1: // Issue validate bit=0 or bit=1 packet + { + if (checkResets(RESET_MIN)) return; + if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); + byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; + byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction }; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + } + break; + + case WACK: // wait for ack (or absence of ack) + { + byte ackState=2; // keep polling + + ackState=getAck(); + if (ackState==2) return; // keep polling + ackReceived=ackState==1; + break; // we have a genuine ACK result + } + case ITC0: + case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK) + if (ackReceived) { + callback(opcode==ITC0?0:1); + return; + } + break; + + case ITCB: // If True callback(byte) + if (ackReceived) { + callback(ackManagerByte); + return; + } + break; + + case ITCBV: // If True callback(byte) - Verify + if (ackReceived) { + if (ackManagerByte == ackManagerByteVerify) { + ackRetrySum ++; + LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum); + } + callback(ackManagerByte); + return; + } + break; + + case ITCB7: // If True callback(byte & 0x7F) + if (ackReceived) { + callback(ackManagerByte & 0x7F); + return; + } + break; + + case NAKFAIL: // If nack callback(-1) + if (!ackReceived) { + callback(-1); + return; + } + break; + + case FAIL: // callback(-1) + callback(-1); + return; + + case BIV: // ackManagerByte initial value + ackManagerByte = ackManagerByteVerify; + break; + + case STARTMERGE: + ackManagerBitNum=7; + ackManagerByte=0; + break; + + case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) + ackManagerByte <<= 1; + // ackReceived means bit is zero. + if (!ackReceived) ackManagerByte |= 1; + ackManagerBitNum--; + break; + + case SETBIT: + ackManagerProg++; + ackManagerBitNum=GETFLASH(ackManagerProg); + break; + + case SETCV: + ackManagerProg++; + ackManagerCv=GETFLASH(ackManagerProg); + break; + + case SETBYTE: + ackManagerProg++; + ackManagerByte=GETFLASH(ackManagerProg); + break; + + case SETBYTEH: + ackManagerByte=highByte(ackManagerWord); + break; + + case SETBYTEL: + ackManagerByte=lowByte(ackManagerWord); + break; + + case STASHLOCOID: + ackManagerStash=ackManagerByte; // stash value from CV17 + break; + + case COMBINELOCOID: + // ackManagerStash is cv17, ackManagerByte is CV 18 + callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); + return; + + case ITSKIP: + if (!ackReceived) break; + // SKIP opcodes until SKIPTARGET found + while (opcode!=SKIPTARGET) { + ackManagerProg++; + opcode=GETFLASH(ackManagerProg); + } + break; + case SKIPTARGET: + break; + default: + DIAG(F("!! ackOp %d FAULT!!"),opcode); + callback( -1); + return; + + } // end of switch + ackManagerProg++; + } +} + +void DCCACK::callback(int value) { + // check for automatic retry + if (value == -1 && ackManagerRetry > 0) { + ackRetrySum ++; + LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum); + ackManagerRetry --; + ackManagerProg = ackManagerProgStart; + return; + } + + static unsigned long callbackStart; + // We are about to leave programming mode + // Rule 1: If we have written to a decoder we must maintain power for 100mS + // Rule 2: If we are re-joining the main track we must power off for 30mS + + switch (callbackState) { + case AFTER_READ: + if (ackManagerRejoin && autoPowerOff) { + progDriver->setPower(POWERMODE::OFF); + callbackStart=millis(); + callbackState=WAITING_30; + if (Diag::ACK) DIAG(F("OFF 30mS")); + } else { + callbackState=READY; + } + break; + + case AFTER_WRITE: // first attempt to callback after a write operation + if (!ackManagerRejoin && !autoPowerOff) { + callbackState=READY; + break; + } // lines 906-910 added. avoid wait after write. use 1 PROG + callbackStart=millis(); + callbackState=WAITING_100; + if (Diag::ACK) DIAG(F("Stable 100mS")); + break; + + case WAITING_100: // waiting for 100mS + if (millis()-callbackStart < 100) break; + // stable after power maintained for 100mS + + // If we are going to power off anyway, it doesnt matter + // but if we will keep the power on, we must off it for 30mS + if (autoPowerOff) callbackState=READY; + else { // Need to cycle power off and on + progDriver->setPower(POWERMODE::OFF); + callbackStart=millis(); + callbackState=WAITING_30; + if (Diag::ACK) DIAG(F("OFF 30mS")); + } + break; + + case WAITING_30: // waiting for 30mS with power off + if (millis()-callbackStart < 30) break; + //power has been off for 30mS + progDriver->setPower(POWERMODE::ON); + callbackState=READY; + break; + + case READY: // ready after read, or write after power delay and off period. + // power off if we powered it on + if (autoPowerOff) { + if (Diag::ACK) DIAG(F("Auto Prog power off")); + progDriver->setPower(POWERMODE::OFF); + /* TODO + if (MotorDriver::commonFaultPin) + DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + **/ + } + // Restore <1 JOIN> to state before BASELINE + if (ackManagerRejoin) { + DCCWaveform::setJoin(true); + if (Diag::ACK) DIAG(F("Auto JOIN")); + } + + ackManagerProg=NULL; // no more steps to execute + if (Diag::ACK) DIAG(F("Callback(%d)"),value); + (ackManagerCallback)( value); + } +} + + +void DCCACK::checkAck(byte sentResetsSincePacket) { + if (!ackPending) return; + // This function operates in interrupt() time so must be fast and can't DIAG + if (sentResetsSincePacket > 6) { //ACK timeout + ackCheckDuration=millis()-ackCheckStart; + ackPending = false; + return; + } + + int current=progDriver->getCurrentRaw(); + numAckSamples++; + if (current > ackMaxCurrent) ackMaxCurrent=current; + // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) + + if (current>ackThreshold) { + if (trailingEdgeCounter > 0) { + numAckGaps++; + trailingEdgeCounter = 0; + } + if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected + return; + } + + // not in pulse + if (ackPulseStart==0) return; // keep waiting for leading edge + + // if we reach to this point, we have + // detected trailing edge of pulse + if (trailingEdgeCounter == 0) { + ackPulseDuration=micros()-ackPulseStart; + } + + // but we do not trust it yet and return (which will force another + // measurement) and first the third time around with low current + // the ack detection will be finalized. + if (trailingEdgeCounter < 2) { + trailingEdgeCounter++; + return; + } + trailingEdgeCounter = 0; + + if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) { + ackCheckDuration=millis()-ackCheckStart; + ackDetected=true; + ackPending=false; + DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets + return; // we have a genuine ACK result + } + ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge +} + diff --git a/DCCACK.h b/DCCACK.h new file mode 100644 index 0000000..8536ce1 --- /dev/null +++ b/DCCACK.h @@ -0,0 +1,156 @@ +/* + * © 2021 M Steve Todd + * © 2021 Mike S + * © 2021 Fred Decker + * © 2020-2021 Harald Barth + * © 2020-2022 Chris Harlow + * All rights reserved. + * + * This file is part of CommandStation-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 . + */ +#ifndef DCCACK_h +#define DCCACK_h + +#include "MotorDriver.h" + +typedef void (*ACK_CALLBACK)(int16_t result); + +enum ackOp : byte +{ // Program opcodes for the ack Manager + BASELINE, // ensure enough resets sent before starting and obtain baseline current + W0, + W1, // issue write bit (0..1) packet + WB, // issue write byte packet + VB, // Issue validate Byte packet + V0, // Issue validate bit=0 packet + V1, // issue validate bit=1 packlet + WACK, // wait for ack (or absence of ack) + ITC1, // If True Callback(1) (if prevous WACK got an ACK) + ITC0, // If True callback(0); + ITCB, // If True callback(byte) + ITCBV, // If True callback(byte) - end of Verify Byte + ITCB7, // If True callback(byte &0x7F) + NAKFAIL, // if false callback(-1) + FAIL, // callback(-1) + BIV, // Set ackManagerByte to initial value for Verify retry + STARTMERGE, // Clear bit and byte settings ready for merge pass + MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) + SETBIT, // sets bit number to next prog byte + SETCV, // sets cv number to next prog byte + SETBYTE, // sets current byte to next prog byte + SETBYTEH, // sets current byte to word high byte + SETBYTEL, // sets current byte to word low byte + STASHLOCOID, // keeps current byte value for later + COMBINELOCOID, // combines current value with stashed value and returns it + ITSKIP, // skip to SKIPTARGET if ack true + SKIPTARGET = 0xFF // jump to target +}; + +enum CALLBACK_STATE : byte { + + AFTER_READ, // Start callback sequence after something was read from the decoder + AFTER_WRITE, // Start callback sequence after something was written to the decoder + WAITING_100, // Waiting for 100mS of stable power + WAITING_30, // waiting to 30ms of power off gap. + READY, // Ready to complete callback + }; + + + +class DCCACK { + public: + static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting + static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker + static inline void setAckLimit(int mA) { + ackLimitmA = mA; + } + static inline void setMinAckPulseDuration(unsigned int i) { + minAckPulseDuration = i; + } + static inline void setMaxAckPulseDuration(unsigned int i) { + maxAckPulseDuration = i; + } + + static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback); + static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback); + static void loop(); + static bool isActive() { return ackManagerProg!=NULL;} + static inline int16_t setAckRetry(byte retry) { + ackRetry = retry; + ackRetryPSum = ackRetrySum; + ackRetrySum = 0; // reset running total + return ackRetryPSum; + }; + + + private: + static const byte SET_SPEED = 0x3f; + static const byte WRITE_BYTE = 0x7C; + static const byte VERIFY_BYTE = 0x74; + static const byte BIT_MANIPULATE = 0x78; + static const byte WRITE_BIT = 0xF0; + static const byte VERIFY_BIT = 0xE0; + static const byte BIT_ON = 0x08; + static const byte BIT_OFF = 0x00; + + static void setAckBaseline(); + static void setAckPending(); + static void callback(int value); + + static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) + + // ACK management (Prog track only) + static void checkAck(); + static bool checkResets(uint8_t numResets); + + static volatile bool ackPending; + static volatile bool ackDetected; + static int ackThreshold; + static int ackLimitmA; + static int ackMaxCurrent; + static unsigned long ackCheckStart; // millis + static unsigned int ackCheckDuration; // millis + + static unsigned int ackPulseDuration; // micros + static unsigned long ackPulseStart; // micros + + static unsigned int minAckPulseDuration ; // micros + static unsigned int maxAckPulseDuration ; // micros + static MotorDriver* progDriver; + static volatile uint8_t numAckGaps; + static volatile uint8_t numAckSamples; + static uint8_t trailingEdgeCounter; + static ackOp const * ackManagerProg; +static ackOp const * ackManagerProgStart; +static byte ackManagerByte; +static byte ackManagerByteVerify; +static byte ackManagerStash; +static int ackManagerWord; +static byte ackManagerRetry; +static byte ackRetry; +static int16_t ackRetrySum; +static int16_t ackRetryPSum; +static int ackManagerCv; +static byte ackManagerBitNum; +static bool ackReceived; +static bool ackManagerRejoin; +static bool autoPowerOff; +static CALLBACK_STATE callbackState; +static ACK_CALLBACK ackManagerCallback; + + +}; +#endif diff --git a/DCCEX.h b/DCCEX.h index 00d89de..a78c6c1 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -45,5 +45,5 @@ #include "Outputs.h" #include "EXRAIL.h" #include "CommandDistributor.h" - +#include "TrackManager.h" #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 796abd2..7c32c13 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -402,9 +402,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) } else break; // will reply } - if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(join); + if (main) TrackManager::setMainPower(POWERMODE::ON); + if (prog) TrackManager::setProgPower(POWERMODE::ON); + DCCWaveform::setJoin(join); CommandDistributor::broadcastPower(); return; @@ -429,12 +429,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) else break; // will reply } - if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + if (main) TrackManager::setMainPower(POWERMODE::OFF); if (prog) { - DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off + TrackManager::setProgPower(POWERMODE::OFF); } - DCC::setProgTrackSyncMain(false); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); return; @@ -445,18 +445,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; case 'c': // SEND METER RESPONSES - // - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.getCurrentmA(), - DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA()); - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available - return; + // No longer supported because of multiple tracks + break; case 'Q': // SENSORS Sensor::printAll(stream); return; case 's': // - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); Turnout::printAll(stream); //send all Turnout states Output::printAll(stream); //send all Output states @@ -508,8 +504,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) case '+': // Complex Wifi interface command (not usual parse) if (atCommandCallback && !ringStream) { - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + TrackManager::setPower(POWERMODE::OFF); atCommandCallback((HardwareSerial *)stream,com); return; } @@ -743,17 +738,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_ACK: // if (params >= 3) { if (p[1] == HASH_KEYWORD_LIMIT) { - DCCWaveform::progTrack.setAckLimit(p[2]); + DCCACK::setAckLimit(p[2]); LCD(1, F("Ack Limit=%dmA"), p[2]); // } else if (p[1] == HASH_KEYWORD_MIN) { - DCCWaveform::progTrack.setMinAckPulseDuration(p[2]); + DCCACK::setMinAckPulseDuration(p[2]); LCD(0, F("Ack Min=%uus"), p[2]); // } else if (p[1] == HASH_KEYWORD_MAX) { - DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]); + DCCACK::setMaxAckPulseDuration(p[2]); LCD(0, F("Ack Max=%uus"), p[2]); // } else if (p[1] == HASH_KEYWORD_RETRY) { if (p[2] >255) p[2]=3; - LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // + LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // } } else { StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); @@ -784,7 +779,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) #endif case HASH_KEYWORD_PROGBOOST: - DCC::setProgTrackBoost(true); + DCCWaveform::progTrackBoosted=true; return true; case HASH_KEYWORD_RESET: diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 2c5ab9a..79c8c97 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -25,7 +25,9 @@ #include #include "DCCWaveform.h" +#include "TrackManager.h" #include "DCCTimer.h" +#include "DCCACK.h" #include "DIAG.h" #include "freeMemory.h" @@ -34,30 +36,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; -int DCCWaveform::progTripValue=0; -volatile uint8_t DCCWaveform::numAckGaps=0; -volatile uint8_t DCCWaveform::numAckSamples=0; -uint8_t DCCWaveform::trailingEdgeCounter=0; +int16_t DCCWaveform::joinRelay=UNUSED_PIN; -void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - mainTrack.motorDriver=mainDriver; - progTrack.motorDriver=progDriver; - progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static - mainTrack.setPowerMode(POWERMODE::OFF); - progTrack.setPowerMode(POWERMODE::OFF); - // Fault pin config for odd motor boards (example pololu) - MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) - && (mainDriver->getFaultPin() != UNUSED_PIN)); - // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work - MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); - DIAG(F("Signal pin config: %S accuracy waveform"), - MotorDriver::usePWM ? F("high") : F("normal") ); +void DCCWaveform::begin() { + + TrackManager::setPower(POWERMODE::OFF); DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::loop(bool ackManagerActive) { - mainTrack.checkPowerOverload(false); - progTrack.checkPowerOverload(ackManagerActive); +void DCCWaveform::loop() { + DCCACK::loop(); + TrackManager::loop(DCCACK::isActive() || progTrackSyncMain || progTrackBoosted ); } #pragma GCC push_options @@ -69,8 +58,8 @@ void DCCWaveform::interruptHandler() { byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; // Set the signal state for both tracks - mainTrack.motorDriver->setSignal(sigMain); - progTrack.motorDriver->setSignal(sigProg); + TrackManager::setDCCSignal(sigMain); + TrackManager::setPROGSignal(sigProg); // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; @@ -80,10 +69,22 @@ void DCCWaveform::interruptHandler() { // WAVE_PENDING means we dont yet know what the next bit is if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); - else if (progTrack.ackPending) progTrack.checkAck(); + else DCCACK::checkAck(progTrack.sentResetsSincePacket); } #pragma GCC push_options +void DCCWaveform::setJoinRelayPin(byte joinRelayPin) { + joinRelay=joinRelayPin; + if (joinRelay!=UNUSED_PIN) { + pinMode(joinRelay,OUTPUT); + digitalWrite(joinRelay,LOW); // LOW is relay disengaged + } +} + +void DCCWaveform::setJoin(bool joined) { + progTrackSyncMain=joined; + if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW); +} // An instance of this class handles the DCC transmissions for one track. (main or prog) // Interrupts are marshalled via the statics. @@ -105,87 +106,10 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { requiredPreambles = preambleBits+1; bytes_sent = 0; bits_sent = 0; - sampleDelay = 0; - lastSampleTaken = millis(); - ackPending=false; -} - -POWERMODE DCCWaveform::getPowerMode() { - return powerMode; -} - -void DCCWaveform::setPowerMode(POWERMODE mode) { - powerMode = mode; - bool ison = (mode == POWERMODE::ON); - motorDriver->setPower( ison); - sentResetsSincePacket=0; } -void DCCWaveform::checkPowerOverload(bool ackManagerActive) { - if (millis() - lastSampleTaken < sampleDelay) return; - lastSampleTaken = millis(); - int tripValue= motorDriver->getRawCurrentTripValue(); - if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted) - tripValue=progTripValue; - - // Trackname for diag messages later - const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG"); - switch (powerMode) { - case POWERMODE::OFF: - sampleDelay = POWER_SAMPLE_OFF_WAIT; - break; - case POWERMODE::ON: - // Check current - lastCurrent=motorDriver->getCurrentRaw(); - if (lastCurrent < 0) { - // We have a fault pin condition to take care of - lastCurrent = -lastCurrent; - setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - if (MotorDriver::commonFaultPin) { - if (lastCurrent <= tripValue) { - setPowerMode(POWERMODE::ON); // maybe other track - } - // Write this after the fact as we want to turn on as fast as possible - // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname); - } else { - DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname); - if (lastCurrent < tripValue) { - lastCurrent = tripValue; // exaggerate - } - } - } - if (lastCurrent < tripValue) { - sampleDelay = POWER_SAMPLE_ON_WAIT; - if(power_good_counter<100) - power_good_counter++; - else - if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; - } else { - setPowerMode(POWERMODE::OVERLOAD); - unsigned int mA=motorDriver->raw2mA(lastCurrent); - unsigned int maxmA=motorDriver->raw2mA(tripValue); - power_good_counter=0; - sampleDelay = power_sample_overload_wait; - DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay); - if (power_sample_overload_wait >= 10000) - power_sample_overload_wait = 10000; - else - power_sample_overload_wait *= 2; - } - break; - case POWERMODE::OVERLOAD: - // Try setting it back on after the OVERLOAD_WAIT - setPowerMode(POWERMODE::ON); - sampleDelay = POWER_SAMPLE_ON_WAIT; - // Debug code.... - DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay); - break; - default: - sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. - } -} + // For each state of the wave nextState=stateTransform[currentState] const WAVE_STATE DCCWaveform::stateTransform[]={ /* WAVE_START -> */ WAVE_PENDING, @@ -282,88 +206,6 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea sentResetsSincePacket=0; } -// Operations applicable to PROG track ONLY. -// (yes I know I could have subclassed the main track but...) - -void DCCWaveform::setAckBaseline() { - if (isMainTrack) return; - int baseline=motorDriver->getCurrentRaw(); - ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); - if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), - baseline,motorDriver->raw2mA(baseline), - ackThreshold,motorDriver->raw2mA(ackThreshold), - minAckPulseDuration, maxAckPulseDuration); -} - -void DCCWaveform::setAckPending() { - if (isMainTrack) return; - ackMaxCurrent=0; - ackPulseStart=0; - ackPulseDuration=0; - ackDetected=false; - ackCheckStart=millis(); - numAckSamples=0; - numAckGaps=0; - ackPending=true; // interrupt routines will now take note -} - -byte DCCWaveform::getAck() { - if (ackPending) return (2); // still waiting - if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, - ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); - if (ackDetected) return (1); // Yes we had an ack - return(0); // pending set off but not detected means no ACK. -} - #pragma GCC push_options #pragma GCC optimize ("-O3") -void DCCWaveform::checkAck() { - // This function operates in interrupt() time so must be fast and can't DIAG - if (sentResetsSincePacket > 6) { //ACK timeout - ackCheckDuration=millis()-ackCheckStart; - ackPending = false; - return; - } - - int current=motorDriver->getCurrentRaw(); - numAckSamples++; - if (current > ackMaxCurrent) ackMaxCurrent=current; - // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) - - if (current>ackThreshold) { - if (trailingEdgeCounter > 0) { - numAckGaps++; - trailingEdgeCounter = 0; - } - if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected - return; - } - - // not in pulse - if (ackPulseStart==0) return; // keep waiting for leading edge - - // if we reach to this point, we have - // detected trailing edge of pulse - if (trailingEdgeCounter == 0) { - ackPulseDuration=micros()-ackPulseStart; - } - - // but we do not trust it yet and return (which will force another - // measurement) and first the third time around with low current - // the ack detection will be finalized. - if (trailingEdgeCounter < 2) { - trailingEdgeCounter++; - return; - } - trailingEdgeCounter = 0; - - if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) { - ackCheckDuration=millis()-ackCheckStart; - ackDetected=true; - ackPending=false; - transmitRepeats=0; // shortcut remaining repeat packets - return; // we have a genuine ACK result - } - ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge -} #pragma GCC pop_options diff --git a/DCCWaveform.h b/DCCWaveform.h index 14f2f11..2ad1c67 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -26,10 +26,7 @@ #include "MotorDriver.h" -// Wait times for power management. Unit: milliseconds -const int POWER_SAMPLE_ON_WAIT = 100; -const int POWER_SAMPLE_OFF_WAIT = 1000; -const int POWER_SAMPLE_OVERLOAD_WAIT = 20; + // Number of preamble bits. const int PREAMBLE_BITS_MAIN = 16; @@ -45,7 +42,6 @@ enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WA // one instance is created for each track. -enum class POWERMODE : byte { OFF, ON, OVERLOAD }; const byte idlePacket[] = {0xFF, 0x00, 0xFF}; const byte resetPacket[] = {0x00, 0x00, 0x00}; @@ -53,66 +49,24 @@ const byte resetPacket[] = {0x00, 0x00, 0x00}; class DCCWaveform { public: DCCWaveform( byte preambleBits, bool isMain); - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver); - static void loop(bool ackManagerActive); + static void begin(); + static void loop(); static DCCWaveform mainTrack; static DCCWaveform progTrack; void beginTrack(); - void setPowerMode(POWERMODE); - POWERMODE getPowerMode(); - void checkPowerOverload(bool ackManagerActive); - inline int get1024Current() { - if (powerMode == POWERMODE::ON) - return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); - return 0; - } - inline int getCurrentmA() { - if (powerMode == POWERMODE::ON) - return motorDriver->raw2mA(lastCurrent); - return 0; - } - inline int getMaxmA() { - if (maxmA == 0) { //only calculate this for first request, it doesn't change - maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc - } - return maxmA; - } - inline int getTripmA() { - if (tripmA == 0) { //only calculate this for first request, it doesn't change - tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); - } - return tripmA; - } + static void setJoin(bool join); + static bool isJoined() { return progTrackSyncMain;} + void clearRepeats() {pendingRepeats=0;} void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; - volatile byte sentResetsSincePacket; - volatile bool autoPowerOff=false; - void setAckBaseline(); //prog track only - void setAckPending(); //prog track only - byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting - static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackBoosted; // true when prog track is not current limited - inline void doAutoPowerOff() { - if (autoPowerOff) { - setPowerMode(POWERMODE::OFF); - autoPowerOff=false; - } - }; - inline bool canMeasureCurrent() { - return motorDriver->canMeasureCurrent(); - }; - inline void setAckLimit(int mA) { - ackLimitmA = mA; - } - inline void setMinAckPulseDuration(unsigned int i) { - minAckPulseDuration = i; - } - inline void setMaxAckPulseDuration(unsigned int i) { - maxAckPulseDuration = i; - } - + volatile byte sentResetsSincePacket; + static void setJoinRelayPin(byte joinRelayPin); + static int16_t joinRelay; + private: + static bool progTrackSyncMain; // true when prog track is a siding switched to main // For each state of the wave nextState=stateTransform[currentState] static const WAVE_STATE stateTransform[6]; @@ -122,10 +76,8 @@ class DCCWaveform { static void interruptHandler(); void interrupt2(); - void checkAck(); bool isMainTrack; - MotorDriver* motorDriver; // Transmission controller byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitLength; @@ -138,38 +90,6 @@ class DCCWaveform { byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte pendingLength; byte pendingRepeats; - int lastCurrent; - static int progTripValue; - int maxmA; - int tripmA; - - // current sampling - POWERMODE powerMode; - unsigned long lastSampleTaken; - unsigned int sampleDelay; - // Trip current for programming track, 250mA. Change only if you really - // need to be non-NMRA-compliant because of decoders that are not either. - static const int TRIP_CURRENT_PROG=250; - unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; - unsigned int power_good_counter = 0; - // ACK management (Prog track only) - volatile bool ackPending; - volatile bool ackDetected; - int ackThreshold; - int ackLimitmA = 60; - int ackMaxCurrent; - unsigned long ackCheckStart; // millis - unsigned int ackCheckDuration; // millis - - unsigned int ackPulseDuration; // micros - unsigned long ackPulseStart; // micros - - unsigned int minAckPulseDuration = 4000; // micros - unsigned int maxAckPulseDuration = 8500; // micros - - volatile static uint8_t numAckGaps; - volatile static uint8_t numAckSamples; - static uint8_t trailingEdgeCounter; }; #endif diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index af6f0a1..f021594 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -50,7 +50,7 @@ #include "DCCEXParser.h" #include "Turnouts.h" #include "CommandDistributor.h" - +#include "TrackManager.h" // Command parsing keywords const int16_t HASH_KEYWORD_EXRAIL=15435; @@ -465,10 +465,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) { void RMFT2::driveLoco(byte speed) { if (loco<=0) return; // Prevent broadcast! if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); - if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + /* TODO..... + power on appropriate track if DC or main if dcc + if (TrackManager::getMainPowerMode()==POWERMODE::OFF) { + TrackManager::setMainPower(POWERMODE::ON); CommandDistributor::broadcastPower(); } + **********/ + DCC::setThrottle(loco,speed, forward^invert); speedo=speed; } @@ -648,9 +652,8 @@ void RMFT2::loop2() { break; case OPCODE_POWEROFF: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); - DCC::setProgTrackSyncMain(false); + TrackManager::setPower(POWERMODE::OFF); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; @@ -789,14 +792,13 @@ void RMFT2::loop2() { return; case OPCODE_JOIN: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); + TrackManager::setPower(POWERMODE::ON); + DCCWaveform::setJoin(true); CommandDistributor::broadcastPower(); break; case OPCODE_UNJOIN: - DCC::setProgTrackSyncMain(false); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index d65d2ea..79167bc 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -82,6 +82,12 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 else DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"), currentPin-A0, senseOffset,rawCurrentTripValue); + + // prepare values for current detection + sampleDelay = 0; + lastSampleTaken = millis(); + progTripValue = mA2raw(TRIP_CURRENT_PROG); + } bool MotorDriver::isPWMCapable() { @@ -89,7 +95,8 @@ bool MotorDriver::isPWMCapable() { } -void MotorDriver::setPower(bool on) { +void MotorDriver::setPower(POWERMODE mode) { + bool on=mode==POWERMODE::ON; if (on) { // toggle brake before turning power on - resets overcurrent error // on the Pololu board if brake is wired to ^D2. @@ -98,6 +105,7 @@ void MotorDriver::setPower(bool on) { setHIGH(fastPowerPin); } else setLOW(fastPowerPin); + powerMode=mode; } // setBrake applies brake if on == true. So to get @@ -190,3 +198,65 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res result.maskLOW = ~result.maskHIGH; // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } + +void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { + if (millis() - lastSampleTaken < sampleDelay) return; + lastSampleTaken = millis(); + int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); + + // Trackname for diag messages later + switch (powerMode) { + case POWERMODE::OFF: + sampleDelay = POWER_SAMPLE_OFF_WAIT; + break; + case POWERMODE::ON: + // Check current + lastCurrent=getCurrentRaw(); + if (lastCurrent < 0) { + // We have a fault pin condition to take care of + lastCurrent = -lastCurrent; + setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again + if (commonFaultPin) { + if (lastCurrent <= tripValue) { + setPower(POWERMODE::ON); // maybe other track + } + // Write this after the fact as we want to turn on as fast as possible + // because we don't know which output actually triggered the fault pin + DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %d"), trackno); + } else { + DIAG(F("TRACK %d FAULT PIN ACTIVE - OVERLOAD"), trackno); + if (lastCurrent < tripValue) { + lastCurrent = tripValue; // exaggerate + } + } + } + if (lastCurrent < tripValue) { + sampleDelay = POWER_SAMPLE_ON_WAIT; + if(power_good_counter<100) + power_good_counter++; + else + if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; + } else { + setPower(POWERMODE::OVERLOAD); + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + power_good_counter=0; + sampleDelay = power_sample_overload_wait; + DIAG(F("TRACK %d POWER OVERLOAD current=%d max=%d offtime=%d"), trackno, mA, maxmA, sampleDelay); + if (power_sample_overload_wait >= 10000) + power_sample_overload_wait = 10000; + else + power_sample_overload_wait *= 2; + } + break; + case POWERMODE::OVERLOAD: + // Try setting it back on after the OVERLOAD_WAIT + setPower(POWERMODE::ON); + sampleDelay = POWER_SAMPLE_ON_WAIT; + // Debug code.... + DIAG(F("TRACK %d POWER RESET delay=%d"), trackno, sampleDelay); + break; + default: + sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. + } +} diff --git a/MotorDriver.h b/MotorDriver.h index 62d9cb5..105bff6 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -43,11 +43,15 @@ struct FASTPIN { }; #endif +enum class POWERMODE : byte { OFF, ON, OVERLOAD }; + class MotorDriver { public: + MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); - virtual void setPower( bool on); + virtual void setPower( POWERMODE mode); + virtual POWERMODE getPower() { return powerMode;} virtual void setSignal( bool high); virtual void setBrake( bool on); virtual int getCurrentRaw(); @@ -63,6 +67,7 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } + void checkPowerOverload(bool useProgLimit, byte trackno); private: void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { @@ -76,6 +81,26 @@ class MotorDriver { int senseOffset; unsigned int tripMilliamps; int rawCurrentTripValue; + // current sampling + POWERMODE powerMode; + unsigned long lastSampleTaken; + unsigned int sampleDelay; + int progTripValue; + int lastCurrent; + int maxmA; + int tripmA; + + // Wait times for power management. Unit: milliseconds + static const int POWER_SAMPLE_ON_WAIT = 100; + static const int POWER_SAMPLE_OFF_WAIT = 1000; + static const int POWER_SAMPLE_OVERLOAD_WAIT = 20; + + // Trip current for programming track, 250mA. Change only if you really + // need to be non-NMRA-compliant because of decoders that are not either. + static const int TRIP_CURRENT_PROG=250; + unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + unsigned int power_good_counter = 0; + #if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) static bool disableInterrupts() { uint32_t primask; diff --git a/TrackManager.cpp b/TrackManager.cpp index 75c758f..10b61c2 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -19,6 +19,7 @@ */ #include "TrackManager.h" #include "FSH.h" +#include "DCCWaveform.h" #include "MotorDriver.h" #include "DIAG.h" // Virtualised Motor shield multi-track hardware Interface @@ -35,6 +36,8 @@ const int16_t HASH_KEYWORD_DC = 9192; // TODO MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; + POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; + void TrackManager::Setup(const FSH * shieldname, MotorDriver * track0, MotorDriver * track1, MotorDriver * track2, @@ -50,15 +53,19 @@ void TrackManager::Setup(const FSH * shieldname, track[6]=track6; track[7]=track7; - trackMode[0]=TRACK_MODE_MAIN; - trackMode[1]=TRACK_MODE_PROG; - trackMode[2]=TRACK_MODE_OFF; - trackMode[3]=TRACK_MODE_OFF; - trackMode[4]=TRACK_MODE_OFF; - trackMode[5]=TRACK_MODE_OFF; - trackMode[6]=TRACK_MODE_OFF; - trackMode[7]=TRACK_MODE_OFF; - + setTrackMode(0,TRACK_MODE_MAIN); + setTrackMode(1,TRACK_MODE_PROG); + setTrackMode(2,TRACK_MODE_OFF); + setTrackMode(3,TRACK_MODE_OFF); + setTrackMode(4,TRACK_MODE_OFF); + setTrackMode(5,TRACK_MODE_OFF); + setTrackMode(6,TRACK_MODE_OFF); + setTrackMode(7,TRACK_MODE_OFF); + // TODO Fault pin config for odd motor boards (example pololu) + // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) + // && (mainDriver->getFaultPin() != UNUSED_PIN)); + DIAG(F("Signal pin config: %S accuracy waveform"), + MotorDriver::usePWM ? F("high") : F("normal") ); } void TrackManager::setDCCSignal( bool on) { @@ -80,11 +87,16 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { if (trackToSet>=8 || track[trackToSet]==NULL) return false; + if (modeOrAddr==TRACK_MODE_PROG) { + // only allow 1 track to be prog + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) trackMode[t]=TRACK_MODE_OFF; + } trackMode[trackToSet]=modeOrAddr; // re-evaluate HighAccuracy mode bool canDo=true; for (byte t=0;t<8;t++) - if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG ) + if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); MotorDriver::usePWM=canDo; return true; @@ -92,7 +104,6 @@ bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) { - int16_t mode; if (params==0) { // List track assignments for (byte t=0;t<8;t++) { @@ -134,4 +145,39 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return false; } +byte TrackManager::nextCycleTrack=MAX_TRACKS; +void TrackManager::loop(bool dontLimitProg) { + nextCycleTrack++; + if (nextCycleTrack>=MAX_TRACKS) nextCycleTrack=0; + if (track[nextCycleTrack]==NULL) return; + MotorDriver * motorDriver=track[nextCycleTrack]; + bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG; + motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack); +} + +MotorDriver * TrackManager::getProgDriver() { + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) return track[t]; + return NULL; +} +void TrackManager::setPower2(bool setProg,POWERMODE mode) { + if (setProg) { + LOOPMODE(TRACK_MODE_PROG,setPower(mode)) + } + else { + mainPowerGuess=mode; + for (byte t=0;t<8;t++) + if (track[t] + && trackMode[t]!=TRACK_MODE_OFF + && trackMode[t]!=TRACK_MODE_PROG + ) track[t]->setPower(mode); + } +} + POWERMODE TrackManager::getProgPower() { + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) + return track[t]->getPower(); + return POWERMODE::OFF; + } + diff --git a/TrackManager.h b/TrackManager.h index 19c1b23..fe06422 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -41,16 +41,27 @@ class TrackManager { static void setCutout( bool on); static void setPROGSignal( bool on); static void setDCSignal(int16_t cab, byte speedbyte); + static MotorDriver * getProgDriver(); + static void setPower2(bool progTrack,POWERMODE mode); + static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} + static void setMainPower(POWERMODE mode) {setPower2(false,mode);} + static void setProgPower(POWERMODE mode) {setPower2(true,mode);} + static const int16_t TRACK_MODE_MAIN=32760; static const int16_t TRACK_MODE_PROG=32761; static const int16_t TRACK_MODE_OFF=0; static const int16_t MAX_TRACKS=8; static bool setTrackMode(byte track, int16_t DCaddrOrMode); static bool parseJ(Print * stream, int16_t params, int16_t p[]); - - + static void loop(bool dontLimitProg); + static POWERMODE getMainPower() {return mainPowerGuess;} + static POWERMODE getProgPower(); + private: + static byte nextCycleTrack; + static POWERMODE mainPowerGuess; + static MotorDriver* track[MAX_TRACKS]; static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF }; diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 312cf93..82c1064 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -55,6 +55,7 @@ #include "version.h" #include "EXRAIL2.h" #include "CommandDistributor.h" +#include "TrackManager.h" #define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;locomultithrottle(stashStream, (byte *)addcmd); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away + TrackManager::setMainPower(POWERMODE::ON); + DCCWaveform::setJoin(true); // <1 JOIN> so we can drive loco away } } stashStream->commit();