mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-02-16 22:19:14 +01:00
It builds....
massive track reorganization
This commit is contained in:
parent
8db937e985
commit
a7740d652d
@ -28,6 +28,7 @@
|
|||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
|
|
||||||
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||||
// This section of CommandDistributor is simply not relevant on a uno or similar
|
// 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() {
|
void CommandDistributor::broadcastPower() {
|
||||||
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||||
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||||
bool join=DCCWaveform::progTrackSyncMain;
|
bool join=DCCWaveform::isJoined();
|
||||||
const FSH * reason=F("");
|
const FSH * reason=F("");
|
||||||
char state='1';
|
char state='1';
|
||||||
if (main && prog && join) reason=F(" JOIN");
|
if (main && prog && join) reason=F(" JOIN");
|
||||||
|
@ -89,7 +89,7 @@ void setup()
|
|||||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
// 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.
|
// 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
|
// 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)
|
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||||
RMFT::begin();
|
RMFT::begin();
|
||||||
|
355
DCC.cpp
355
DCC.cpp
@ -56,10 +56,9 @@ const byte FN_GROUP_4=0x08;
|
|||||||
const byte FN_GROUP_5=0x10;
|
const byte FN_GROUP_5=0x10;
|
||||||
|
|
||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
byte DCC::joinRelay=UNUSED_PIN;
|
|
||||||
byte DCC::globalSpeedsteps=128;
|
byte DCC::globalSpeedsteps=128;
|
||||||
|
|
||||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
void DCC::begin(const FSH * motorShieldName) {
|
||||||
shieldName=(FSH *)motorShieldName;
|
shieldName=(FSH *)motorShieldName;
|
||||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\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();
|
EEStore::init();
|
||||||
#endif
|
#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) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
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);
|
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() {
|
FSH* DCC::getMotorShieldName() {
|
||||||
return shieldName;
|
return shieldName;
|
||||||
}
|
}
|
||||||
@ -514,35 +498,35 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
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) {
|
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||||
if (bitNum >= 8) callback(-1);
|
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) {
|
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) {
|
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||||
if (bitNum >= 8) callback(-1);
|
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) {
|
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||||
if (bitNum >= 8) callback(-1);
|
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) {
|
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) {
|
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) {
|
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||||
@ -551,9 +535,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id<=HIGHEST_SHORT_ADDR)
|
if (id<=HIGHEST_SHORT_ADDR)
|
||||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback);
|
||||||
else
|
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
|
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;
|
byte DCC::loopStatus=0;
|
||||||
|
|
||||||
void DCC::loop() {
|
void DCC::loop() {
|
||||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
DCCWaveform::loop(); // power overload checks
|
||||||
ackManagerLoop(); // maintain prog track ack manager
|
DCCACK::loop(); // maintain prog track ack manager
|
||||||
issueReminders();
|
issueReminders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,319 +679,6 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
|||||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||||
int DCC::nextLoco = 0;
|
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) {
|
void DCC::displayCabList(Print * stream) {
|
||||||
|
|
||||||
|
85
DCC.h
85
DCC.h
@ -36,48 +36,9 @@
|
|||||||
#error short addr greater than 127 does not make sense
|
#error short addr greater than 127 does not make sense
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#include "DCCACK.h"
|
||||||
const uint16_t LONG_ADDR_MARKER = 0x4000;
|
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..!
|
// Allocations with memory implications..!
|
||||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
// 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
|
class DCC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
static void begin(const FSH * motorShieldName);
|
||||||
static void setJoinRelayPin(byte joinRelayPin);
|
|
||||||
static void loop();
|
static void loop();
|
||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
@ -110,9 +70,7 @@ public:
|
|||||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||||
static bool writeTextPacket(byte *b, int nBytes);
|
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
|
// 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 readCV(int16_t cv, ACK_CALLBACK callback);
|
||||||
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
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) {
|
static inline void setGlobalSpeedsteps(byte s) {
|
||||||
globalSpeedsteps = s;
|
globalSpeedsteps = s;
|
||||||
};
|
};
|
||||||
static inline int16_t setAckRetry(byte retry) {
|
|
||||||
ackRetry = retry;
|
|
||||||
ackRetryPSum = ackRetrySum;
|
|
||||||
ackRetrySum = 0; // reset running total
|
|
||||||
return ackRetryPSum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LOCO
|
struct LOCO
|
||||||
{
|
{
|
||||||
int loco;
|
int loco;
|
||||||
@ -148,9 +100,9 @@ public:
|
|||||||
unsigned long functions;
|
unsigned long functions;
|
||||||
};
|
};
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
|
static byte cv1(byte opcode, int cv);
|
||||||
|
static byte cv2(int cv);
|
||||||
private:
|
private:
|
||||||
static byte joinRelay;
|
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
static void updateLocoReminder(int loco, byte speedCode);
|
static void updateLocoReminder(int loco, byte speedCode);
|
||||||
@ -160,34 +112,11 @@ private:
|
|||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
static byte cv1(byte opcode, int cv);
|
|
||||||
static byte cv2(int cv);
|
|
||||||
static int lookupSpeedTable(int locoId);
|
static int lookupSpeedTable(int locoId);
|
||||||
static void issueReminders();
|
static void issueReminders();
|
||||||
static void callback(int value);
|
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 #
|
// NMRA codes #
|
||||||
static const byte SET_SPEED = 0x3f;
|
static const byte SET_SPEED = 0x3f;
|
||||||
|
467
DCCACK.cpp
Normal file
467
DCCACK.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
156
DCCACK.h
Normal file
156
DCCACK.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#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
|
2
DCCEX.h
2
DCCEX.h
@ -45,5 +45,5 @@
|
|||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
#include "EXRAIL.h"
|
#include "EXRAIL.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -402,9 +402,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
}
|
}
|
||||||
else break; // will reply <X>
|
else break; // will reply <X>
|
||||||
}
|
}
|
||||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||||
DCC::setProgTrackSyncMain(join);
|
DCCWaveform::setJoin(join);
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
return;
|
return;
|
||||||
@ -429,12 +429,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
else break; // will reply <X>
|
else break; // will reply <X>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||||
if (prog) {
|
if (prog) {
|
||||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
DCCWaveform::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
TrackManager::setProgPower(POWERMODE::OFF);
|
||||||
}
|
}
|
||||||
DCC::setProgTrackSyncMain(false);
|
DCCWaveform::setJoin(false);
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
return;
|
return;
|
||||||
@ -445,18 +445,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case 'c': // SEND METER RESPONSES <c>
|
case 'c': // SEND METER RESPONSES <c>
|
||||||
// <c MeterName value C/V unit min max res warn>
|
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
|
||||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
break;
|
||||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
|
||||||
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'Q': // SENSORS <Q>
|
case 'Q': // SENSORS <Q>
|
||||||
Sensor::printAll(stream);
|
Sensor::printAll(stream);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 's': // <s>
|
case 's': // <s>
|
||||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
|
||||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
Turnout::printAll(stream); //send all Turnout states
|
Turnout::printAll(stream); //send all Turnout states
|
||||||
Output::printAll(stream); //send all Output 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)
|
case '+': // Complex Wifi interface command (not usual parse)
|
||||||
if (atCommandCallback && !ringStream) {
|
if (atCommandCallback && !ringStream) {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
TrackManager::setPower(POWERMODE::OFF);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
|
||||||
atCommandCallback((HardwareSerial *)stream,com);
|
atCommandCallback((HardwareSerial *)stream,com);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -743,17 +738,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||||
if (params >= 3) {
|
if (params >= 3) {
|
||||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
DCCACK::setAckLimit(p[2]);
|
||||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
DCCACK::setMinAckPulseDuration(p[2]);
|
||||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||||
if (p[2] >255) p[2]=3;
|
if (p[2] >255) p[2]=3;
|
||||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
|
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
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
|
#endif
|
||||||
|
|
||||||
case HASH_KEYWORD_PROGBOOST:
|
case HASH_KEYWORD_PROGBOOST:
|
||||||
DCC::setProgTrackBoost(true);
|
DCCWaveform::progTrackBoosted=true;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HASH_KEYWORD_RESET:
|
case HASH_KEYWORD_RESET:
|
||||||
|
208
DCCWaveform.cpp
208
DCCWaveform.cpp
@ -25,7 +25,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
|
#include "DCCACK.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "freeMemory.h"
|
#include "freeMemory.h"
|
||||||
|
|
||||||
@ -34,30 +36,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
|||||||
|
|
||||||
bool DCCWaveform::progTrackSyncMain=false;
|
bool DCCWaveform::progTrackSyncMain=false;
|
||||||
bool DCCWaveform::progTrackBoosted=false;
|
bool DCCWaveform::progTrackBoosted=false;
|
||||||
int DCCWaveform::progTripValue=0;
|
int16_t DCCWaveform::joinRelay=UNUSED_PIN;
|
||||||
volatile uint8_t DCCWaveform::numAckGaps=0;
|
|
||||||
volatile uint8_t DCCWaveform::numAckSamples=0;
|
|
||||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
void DCCWaveform::begin() {
|
||||||
mainTrack.motorDriver=mainDriver;
|
|
||||||
progTrack.motorDriver=progDriver;
|
TrackManager::setPower(POWERMODE::OFF);
|
||||||
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") );
|
|
||||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::loop(bool ackManagerActive) {
|
void DCCWaveform::loop() {
|
||||||
mainTrack.checkPowerOverload(false);
|
DCCACK::loop();
|
||||||
progTrack.checkPowerOverload(ackManagerActive);
|
TrackManager::loop(DCCACK::isActive() || progTrackSyncMain || progTrackBoosted );
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
@ -69,8 +58,8 @@ void DCCWaveform::interruptHandler() {
|
|||||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
// Set the signal state for both tracks
|
// Set the signal state for both tracks
|
||||||
mainTrack.motorDriver->setSignal(sigMain);
|
TrackManager::setDCCSignal(sigMain);
|
||||||
progTrack.motorDriver->setSignal(sigProg);
|
TrackManager::setPROGSignal(sigProg);
|
||||||
|
|
||||||
// Move on in the state engine
|
// Move on in the state engine
|
||||||
mainTrack.state=stateTransform[mainTrack.state];
|
mainTrack.state=stateTransform[mainTrack.state];
|
||||||
@ -80,10 +69,22 @@ void DCCWaveform::interruptHandler() {
|
|||||||
// WAVE_PENDING means we dont yet know what the next bit is
|
// WAVE_PENDING means we dont yet know what the next bit is
|
||||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||||
else if (progTrack.ackPending) progTrack.checkAck();
|
else DCCACK::checkAck(progTrack.sentResetsSincePacket);
|
||||||
|
|
||||||
}
|
}
|
||||||
#pragma GCC push_options
|
#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)
|
// An instance of this class handles the DCC transmissions for one track. (main or prog)
|
||||||
// Interrupts are marshalled via the statics.
|
// Interrupts are marshalled via the statics.
|
||||||
@ -105,87 +106,10 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
|||||||
requiredPreambles = preambleBits+1;
|
requiredPreambles = preambleBits+1;
|
||||||
bytes_sent = 0;
|
bytes_sent = 0;
|
||||||
bits_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]
|
// For each state of the wave nextState=stateTransform[currentState]
|
||||||
const WAVE_STATE DCCWaveform::stateTransform[]={
|
const WAVE_STATE DCCWaveform::stateTransform[]={
|
||||||
/* WAVE_START -> */ WAVE_PENDING,
|
/* WAVE_START -> */ WAVE_PENDING,
|
||||||
@ -282,88 +206,6 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||||||
sentResetsSincePacket=0;
|
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 push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#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
|
#pragma GCC pop_options
|
||||||
|
102
DCCWaveform.h
102
DCCWaveform.h
@ -26,10 +26,7 @@
|
|||||||
|
|
||||||
#include "MotorDriver.h"
|
#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.
|
// Number of preamble bits.
|
||||||
const int PREAMBLE_BITS_MAIN = 16;
|
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.
|
// one instance is created for each track.
|
||||||
|
|
||||||
|
|
||||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
|
||||||
|
|
||||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||||
@ -53,66 +49,24 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
|
|||||||
class DCCWaveform {
|
class DCCWaveform {
|
||||||
public:
|
public:
|
||||||
DCCWaveform( byte preambleBits, bool isMain);
|
DCCWaveform( byte preambleBits, bool isMain);
|
||||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
static void begin();
|
||||||
static void loop(bool ackManagerActive);
|
static void loop();
|
||||||
static DCCWaveform mainTrack;
|
static DCCWaveform mainTrack;
|
||||||
static DCCWaveform progTrack;
|
static DCCWaveform progTrack;
|
||||||
|
|
||||||
void beginTrack();
|
void beginTrack();
|
||||||
void setPowerMode(POWERMODE);
|
static void setJoin(bool join);
|
||||||
POWERMODE getPowerMode();
|
static bool isJoined() { return progTrackSyncMain;}
|
||||||
void checkPowerOverload(bool ackManagerActive);
|
void clearRepeats() {pendingRepeats=0;}
|
||||||
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;
|
|
||||||
}
|
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
volatile bool packetPending;
|
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
|
static bool progTrackBoosted; // true when prog track is not current limited
|
||||||
inline void doAutoPowerOff() {
|
volatile byte sentResetsSincePacket;
|
||||||
if (autoPowerOff) {
|
static void setJoinRelayPin(byte joinRelayPin);
|
||||||
setPowerMode(POWERMODE::OFF);
|
static int16_t joinRelay;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||||
|
|
||||||
// For each state of the wave nextState=stateTransform[currentState]
|
// For each state of the wave nextState=stateTransform[currentState]
|
||||||
static const WAVE_STATE stateTransform[6];
|
static const WAVE_STATE stateTransform[6];
|
||||||
@ -122,10 +76,8 @@ class DCCWaveform {
|
|||||||
|
|
||||||
static void interruptHandler();
|
static void interruptHandler();
|
||||||
void interrupt2();
|
void interrupt2();
|
||||||
void checkAck();
|
|
||||||
|
|
||||||
bool isMainTrack;
|
bool isMainTrack;
|
||||||
MotorDriver* motorDriver;
|
|
||||||
// Transmission controller
|
// Transmission controller
|
||||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte transmitLength;
|
byte transmitLength;
|
||||||
@ -138,38 +90,6 @@ class DCCWaveform {
|
|||||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
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
|
#endif
|
||||||
|
22
EXRAIL2.cpp
22
EXRAIL2.cpp
@ -50,7 +50,7 @@
|
|||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
|
|
||||||
// Command parsing keywords
|
// Command parsing keywords
|
||||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||||
@ -465,10 +465,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
|
|||||||
void RMFT2::driveLoco(byte speed) {
|
void RMFT2::driveLoco(byte speed) {
|
||||||
if (loco<=0) return; // Prevent broadcast!
|
if (loco<=0) return; // Prevent broadcast!
|
||||||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
/* TODO.....
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
power on appropriate track if DC or main if dcc
|
||||||
|
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||||
|
TrackManager::setMainPower(POWERMODE::ON);
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
}
|
}
|
||||||
|
**********/
|
||||||
|
|
||||||
DCC::setThrottle(loco,speed, forward^invert);
|
DCC::setThrottle(loco,speed, forward^invert);
|
||||||
speedo=speed;
|
speedo=speed;
|
||||||
}
|
}
|
||||||
@ -648,9 +652,8 @@ void RMFT2::loop2() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_POWEROFF:
|
case OPCODE_POWEROFF:
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
TrackManager::setPower(POWERMODE::OFF);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::setJoin(false);
|
||||||
DCC::setProgTrackSyncMain(false);
|
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -789,14 +792,13 @@ void RMFT2::loop2() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case OPCODE_JOIN:
|
case OPCODE_JOIN:
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
TrackManager::setPower(POWERMODE::ON);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::setJoin(true);
|
||||||
DCC::setProgTrackSyncMain(true);
|
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_UNJOIN:
|
case OPCODE_UNJOIN:
|
||||||
DCC::setProgTrackSyncMain(false);
|
DCCWaveform::setJoin(false);
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -82,6 +82,12 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
else
|
else
|
||||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
||||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||||
|
|
||||||
|
// prepare values for current detection
|
||||||
|
sampleDelay = 0;
|
||||||
|
lastSampleTaken = millis();
|
||||||
|
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MotorDriver::isPWMCapable() {
|
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) {
|
if (on) {
|
||||||
// toggle brake before turning power on - resets overcurrent error
|
// toggle brake before turning power on - resets overcurrent error
|
||||||
// on the Pololu board if brake is wired to ^D2.
|
// on the Pololu board if brake is wired to ^D2.
|
||||||
@ -98,6 +105,7 @@ void MotorDriver::setPower(bool on) {
|
|||||||
setHIGH(fastPowerPin);
|
setHIGH(fastPowerPin);
|
||||||
}
|
}
|
||||||
else setLOW(fastPowerPin);
|
else setLOW(fastPowerPin);
|
||||||
|
powerMode=mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setBrake applies brake if on == true. So to get
|
// 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;
|
result.maskLOW = ~result.maskHIGH;
|
||||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,11 +43,15 @@ struct FASTPIN {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||||
|
|
||||||
class MotorDriver {
|
class MotorDriver {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
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 setSignal( bool high);
|
||||||
virtual void setBrake( bool on);
|
virtual void setBrake( bool on);
|
||||||
virtual int getCurrentRaw();
|
virtual int getCurrentRaw();
|
||||||
@ -63,6 +67,7 @@ class MotorDriver {
|
|||||||
inline byte getFaultPin() {
|
inline byte getFaultPin() {
|
||||||
return faultPin;
|
return faultPin;
|
||||||
}
|
}
|
||||||
|
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||||
private:
|
private:
|
||||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||||
@ -76,6 +81,26 @@ class MotorDriver {
|
|||||||
int senseOffset;
|
int senseOffset;
|
||||||
unsigned int tripMilliamps;
|
unsigned int tripMilliamps;
|
||||||
int rawCurrentTripValue;
|
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)
|
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||||
static bool disableInterrupts() {
|
static bool disableInterrupts() {
|
||||||
uint32_t primask;
|
uint32_t primask;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
#include "DCCWaveform.h"
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
// Virtualised Motor shield multi-track hardware Interface
|
// Virtualised Motor shield multi-track hardware Interface
|
||||||
@ -35,6 +36,8 @@ const int16_t HASH_KEYWORD_DC = 9192; // TODO
|
|||||||
|
|
||||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||||
int16_t TrackManager::trackMode[MAX_TRACKS];
|
int16_t TrackManager::trackMode[MAX_TRACKS];
|
||||||
|
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
||||||
|
|
||||||
|
|
||||||
void TrackManager::Setup(const FSH * shieldname,
|
void TrackManager::Setup(const FSH * shieldname,
|
||||||
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
||||||
@ -50,15 +53,19 @@ void TrackManager::Setup(const FSH * shieldname,
|
|||||||
track[6]=track6;
|
track[6]=track6;
|
||||||
track[7]=track7;
|
track[7]=track7;
|
||||||
|
|
||||||
trackMode[0]=TRACK_MODE_MAIN;
|
setTrackMode(0,TRACK_MODE_MAIN);
|
||||||
trackMode[1]=TRACK_MODE_PROG;
|
setTrackMode(1,TRACK_MODE_PROG);
|
||||||
trackMode[2]=TRACK_MODE_OFF;
|
setTrackMode(2,TRACK_MODE_OFF);
|
||||||
trackMode[3]=TRACK_MODE_OFF;
|
setTrackMode(3,TRACK_MODE_OFF);
|
||||||
trackMode[4]=TRACK_MODE_OFF;
|
setTrackMode(4,TRACK_MODE_OFF);
|
||||||
trackMode[5]=TRACK_MODE_OFF;
|
setTrackMode(5,TRACK_MODE_OFF);
|
||||||
trackMode[6]=TRACK_MODE_OFF;
|
setTrackMode(6,TRACK_MODE_OFF);
|
||||||
trackMode[7]=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) {
|
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) {
|
bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) {
|
||||||
if (trackToSet>=8 || track[trackToSet]==NULL) return false;
|
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;
|
trackMode[trackToSet]=modeOrAddr;
|
||||||
// re-evaluate HighAccuracy mode
|
// re-evaluate HighAccuracy mode
|
||||||
bool canDo=true;
|
bool canDo=true;
|
||||||
for (byte t=0;t<8;t++)
|
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();
|
canDo &= track[t]->isPWMCapable();
|
||||||
MotorDriver::usePWM=canDo;
|
MotorDriver::usePWM=canDo;
|
||||||
return true;
|
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[])
|
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
int16_t mode;
|
|
||||||
|
|
||||||
if (params==0) { // <J> List track assignments
|
if (params==0) { // <J> List track assignments
|
||||||
for (byte t=0;t<8;t++) {
|
for (byte t=0;t<8;t++) {
|
||||||
@ -134,4 +145,39 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -41,16 +41,27 @@ class TrackManager {
|
|||||||
static void setCutout( bool on);
|
static void setCutout( bool on);
|
||||||
static void setPROGSignal( bool on);
|
static void setPROGSignal( bool on);
|
||||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
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_MAIN=32760;
|
||||||
static const int16_t TRACK_MODE_PROG=32761;
|
static const int16_t TRACK_MODE_PROG=32761;
|
||||||
static const int16_t TRACK_MODE_OFF=0;
|
static const int16_t TRACK_MODE_OFF=0;
|
||||||
static const int16_t MAX_TRACKS=8;
|
static const int16_t MAX_TRACKS=8;
|
||||||
static bool setTrackMode(byte track, int16_t DCaddrOrMode);
|
static bool setTrackMode(byte track, int16_t DCaddrOrMode);
|
||||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
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:
|
private:
|
||||||
|
static byte nextCycleTrack;
|
||||||
|
static POWERMODE mainPowerGuess;
|
||||||
|
|
||||||
static MotorDriver* track[MAX_TRACKS];
|
static MotorDriver* track[MAX_TRACKS];
|
||||||
static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF
|
static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "EXRAIL2.h"
|
#include "EXRAIL2.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "TrackManager.h"
|
||||||
|
|
||||||
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
||||||
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
||||||
@ -151,9 +152,12 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
TrackManager::setMainPower(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
|
/* TODO
|
||||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
|
*/
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
CommandDistributor::broadcastPower();
|
||||||
}
|
}
|
||||||
#if defined(EXRAIL_ACTIVE)
|
#if defined(EXRAIL_ACTIVE)
|
||||||
@ -204,7 +208,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
RMFT2::emitWithrottleRoster(stream);
|
RMFT2::emitWithrottleRoster(stream);
|
||||||
#endif
|
#endif
|
||||||
@ -530,8 +534,8 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
|||||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||||
itoa(locoid,addcmd+4,10);
|
itoa(locoid,addcmd+4,10);
|
||||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
TrackManager::setMainPower(POWERMODE::ON);
|
||||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
DCCWaveform::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stashStream->commit();
|
stashStream->commit();
|
||||||
|
Loading…
Reference in New Issue
Block a user