mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-26 17:46:14 +01:00
Fix auto rejoin after prog cmd (needs version n umber!) (#148)
* ack down flank double check * ack gap properly reported * zero gap count; tolerate 2 samples per gap * Fix auto rejoin after prog cmd Moved more setup out of the BASELINE loop so its not checked every time while waiting for reset counter. Added REJOIN diag.. * Stable 100mS and off 30mS * Init powerOff after flag. Co-authored-by: Harald Barth <haba@kth.se>
This commit is contained in:
parent
ebbe698e51
commit
67c8366512
105
DCC.cpp
105
DCC.cpp
|
@ -691,9 +691,31 @@ byte DCC::ackManagerBitNum;
|
||||||
bool DCC::ackReceived;
|
bool DCC::ackReceived;
|
||||||
bool DCC::ackManagerRejoin;
|
bool DCC::ackManagerRejoin;
|
||||||
|
|
||||||
|
CALLBACK_STATE DCC::callbackState=READY;
|
||||||
|
|
||||||
ACK_CALLBACK DCC::ackManagerCallback;
|
ACK_CALLBACK DCC::ackManagerCallback;
|
||||||
|
|
||||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
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);
|
||||||
|
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||||
|
}
|
||||||
|
|
||||||
ackManagerCv = cv;
|
ackManagerCv = cv;
|
||||||
ackManagerProg = program;
|
ackManagerProg = program;
|
||||||
ackManagerByte = byteValueOrBitnum;
|
ackManagerByte = byteValueOrBitnum;
|
||||||
|
@ -703,8 +725,7 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
|
||||||
|
|
||||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||||
ackManagerWord=wordval;
|
ackManagerWord=wordval;
|
||||||
ackManagerProg = program;
|
ackManagerSetup(0, 0, program, callback);
|
||||||
ackManagerCallback = callback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||||
|
@ -723,21 +744,9 @@ void DCC::ackManagerLoop() {
|
||||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case BASELINE:
|
case BASELINE:
|
||||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
|
||||||
callback(-2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProgTrackSyncMain(false);
|
|
||||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
|
||||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
|
||||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
|
||||||
DCCWaveform::progTrack.autoPowerOff=true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
|
||||||
DCCWaveform::progTrack.setAckBaseline();
|
DCCWaveform::progTrack.setAckBaseline();
|
||||||
|
callbackState=READY;
|
||||||
break;
|
break;
|
||||||
case W0: // write 0 bit
|
case W0: // write 0 bit
|
||||||
case W1: // write 1 bit
|
case W1: // write 1 bit
|
||||||
|
@ -748,6 +757,7 @@ void DCC::ackManagerLoop() {
|
||||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -758,6 +768,7 @@ void DCC::ackManagerLoop() {
|
||||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -888,21 +899,61 @@ void DCC::ackManagerLoop() {
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void DCC::callback(int value) {
|
|
||||||
ackManagerProg=NULL; // no more steps to execute
|
|
||||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
|
||||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
|
||||||
DCCWaveform::progTrack.doAutoPowerOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore <1 JOIN> to state before BASELINE
|
void DCC::callback(int value) {
|
||||||
setProgTrackSyncMain(ackManagerRejoin);
|
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
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
// Restore <1 JOIN> to state before BASELINE
|
||||||
|
if (ackManagerRejoin) {
|
||||||
|
setProgTrackSyncMain(true);
|
||||||
|
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||||
|
}
|
||||||
|
|
||||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
ackManagerProg=NULL; // no more steps to execute
|
||||||
(ackManagerCallback)( value);
|
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||||
|
(ackManagerCallback)( value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::displayCabList(Print * stream) {
|
void DCC::displayCabList(Print * stream) {
|
||||||
|
|
||||||
int used=0;
|
int used=0;
|
||||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
|
|
11
DCC.h
11
DCC.h
|
@ -54,6 +54,14 @@ enum ackOp : byte
|
||||||
SKIPTARGET = 0xFF // jump to target
|
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
|
||||||
#ifdef ARDUINO_AVR_UNO
|
#ifdef ARDUINO_AVR_UNO
|
||||||
|
@ -141,12 +149,13 @@ private:
|
||||||
static bool ackReceived;
|
static bool ackReceived;
|
||||||
static bool ackManagerRejoin;
|
static bool ackManagerRejoin;
|
||||||
static ACK_CALLBACK ackManagerCallback;
|
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 cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||||
static void ackManagerLoop();
|
static void ackManagerLoop();
|
||||||
static bool checkResets( uint8_t numResets);
|
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)
|
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;
|
||||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||||
|
|
|
@ -31,7 +31,10 @@ 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;
|
int DCCWaveform::progTripValue=0;
|
||||||
|
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(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
mainTrack.motorDriver=mainDriver;
|
mainTrack.motorDriver=mainDriver;
|
||||||
progTrack.motorDriver=progDriver;
|
progTrack.motorDriver=progDriver;
|
||||||
|
@ -290,13 +293,15 @@ void DCCWaveform::setAckPending() {
|
||||||
ackPulseDuration=0;
|
ackPulseDuration=0;
|
||||||
ackDetected=false;
|
ackDetected=false;
|
||||||
ackCheckStart=millis();
|
ackCheckStart=millis();
|
||||||
|
numAckSamples=0;
|
||||||
|
numAckGaps=0;
|
||||||
ackPending=true; // interrupt routines will now take note
|
ackPending=true; // interrupt routines will now take note
|
||||||
}
|
}
|
||||||
|
|
||||||
byte DCCWaveform::getAck() {
|
byte DCCWaveform::getAck() {
|
||||||
if (ackPending) return (2); // still waiting
|
if (ackPending) return (2); // still waiting
|
||||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
|
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||||
if (ackDetected) return (1); // Yes we had an ack
|
if (ackDetected) return (1); // Yes we had an ack
|
||||||
return(0); // pending set off but not detected means no ACK.
|
return(0); // pending set off but not detected means no ACK.
|
||||||
}
|
}
|
||||||
|
@ -310,10 +315,15 @@ void DCCWaveform::checkAck() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int current=motorDriver->getCurrentRaw();
|
int current=motorDriver->getCurrentRaw();
|
||||||
|
numAckSamples++;
|
||||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||||
|
|
||||||
if (current>ackThreshold) {
|
if (current>ackThreshold) {
|
||||||
|
if (trailingEdgeCounter > 0) {
|
||||||
|
numAckGaps++;
|
||||||
|
trailingEdgeCounter = 0;
|
||||||
|
}
|
||||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -321,9 +331,21 @@ void DCCWaveform::checkAck() {
|
||||||
// not in pulse
|
// not in pulse
|
||||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||||
|
|
||||||
|
// if we reach to this point, we have
|
||||||
// detected trailing edge of pulse
|
// detected trailing edge of pulse
|
||||||
ackPulseDuration=micros()-ackPulseStart;
|
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) {
|
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||||
ackCheckDuration=millis()-ackCheckStart;
|
ackCheckDuration=millis()-ackCheckStart;
|
||||||
ackDetected=true;
|
ackDetected=true;
|
||||||
|
|
|
@ -161,8 +161,11 @@ class DCCWaveform {
|
||||||
unsigned int ackPulseDuration; // micros
|
unsigned int ackPulseDuration; // micros
|
||||||
unsigned long ackPulseStart; // micros
|
unsigned long ackPulseStart; // micros
|
||||||
|
|
||||||
unsigned int minAckPulseDuration = 2000; // micros
|
unsigned int minAckPulseDuration = 4000; // micros
|
||||||
unsigned int maxAckPulseDuration = 8500; // micros
|
unsigned int maxAckPulseDuration = 8500; // micros
|
||||||
|
|
||||||
|
volatile static uint8_t numAckGaps;
|
||||||
|
volatile static uint8_t numAckSamples;
|
||||||
|
static uint8_t trailingEdgeCounter;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "3.0.13"
|
#define VERSION "3.0.14"
|
||||||
|
// 3.0.14 gap in ack tolerant fix, prog track power management over join fix.
|
||||||
// 3.0.13 Functions>127 fix
|
// 3.0.13 Functions>127 fix
|
||||||
// 3.0.12 Fix HOSTNAME function for STA mode for WiFi
|
// 3.0.12 Fix HOSTNAME function for STA mode for WiFi
|
||||||
// 3.0.11 ?
|
// 3.0.11 ?
|
||||||
|
// 3.0.11 28 speedstep support
|
||||||
// 3.0.10 Teensy Support
|
// 3.0.10 Teensy Support
|
||||||
// 3.0.9 rearranges serial newlines for the benefit of JMRI.
|
// 3.0.9 rearranges serial newlines for the benefit of JMRI.
|
||||||
// 3.0.8 Includes <* *> wraps around DIAGs for the benefit of JMRI.
|
// 3.0.8 Includes <* *> wraps around DIAGs for the benefit of JMRI.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user