From b0debd1fabcc6b8832bf1f39678815142832a04a Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 2 Jul 2020 12:49:35 +0100 Subject: [PATCH] Interrupt time ACK manager --- DCC.cpp | 67 +++++++-------------------------------- DCC.h | 4 --- DCCWaveform.cpp | 70 +++++++++++++++++++++++++++++++++++++++-- DCCWaveform.h | 23 +++++++++++--- Hardware.cpp | 8 ++++- WifiInterface.cpp | 2 +- avdweb_AnalogReadFast.h | 55 ++++++++++++++++++++++++++++++++ 7 files changed, 160 insertions(+), 69 deletions(-) create mode 100644 avdweb_AnalogReadFast.h diff --git a/DCC.cpp b/DCC.cpp index 2725f28..7f2f88e 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -402,10 +402,6 @@ byte DCC::ackManagerStash; int DCC::ackManagerCv; byte DCC::ackManagerBitNum; bool DCC::ackReceived; -int DCC::ackTriggerMilliamps; -unsigned long DCC::ackPulseStart; -int DCC::ackMaxCurrent; -int DCC::ackPollCount; bool DCC::debugMode=false; ACK_CALLBACK DCC::ackManagerCallback; @@ -416,8 +412,6 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[] ackManagerByte = byteValueOrBitnum; ackManagerBitNum=byteValueOrBitnum; ackManagerCallback = callback; - ackMaxCurrent=0; - ackPollCount=0; } const byte RESET_MIN=8; // tuning of reset counter before sending message @@ -434,10 +428,8 @@ void DCC::ackManagerLoop() { switch (opcode) { case BASELINE: if (resets 6) { //ACK timeout - if (debugMode) DIAG(F("\nWACK fail polls=%d, resets=%d, max=%dmA"), ackPollCount, resets, ackMaxCurrent); - ackReceived = false; - break; // move on to next prog step - } - - int current=Hardware::getCurrentMilliamps(false); - if (current > ackMaxCurrent) ackMaxCurrent=current; - - ackPollCount++; - - // An ACK is a pulse lasting between 4.5 and 8.5 mSecs (refer @haba) - - if (current>ackTriggerMilliamps) { - if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected - return; - } - - // not in pulse - if (ackPulseStart==0) return; // keep waiting for leading edge - - // detected trailing edge of pulse - long pulseDuration=micros()-ackPulseStart; - - if (pulseDuration>1000 && pulseDuration<9000) { - if (debugMode) DIAG(F("\nWACK-OK polls=%d, max=%dmA, pulse=%duS"),ackPollCount, ackMaxCurrent, pulseDuration); - ackReceived=true; - DCCWaveform::progTrack.killRemainingRepeats(); // probably no need after 8.5ms!! - break; // we have a genuine ACK result - } - if (debugMode) DIAG(F("\nWACK-bad pulse polls=%d, max=%dmA, now=%dmA, pulse=%duS"), ackPollCount, ackMaxCurrent,current, pulseDuration); - ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge - return; // keep waiting + byte ackState=DCCWaveform::progTrack.getAck(debugMode); + 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) diff --git a/DCC.h b/DCC.h index a74191a..b7d1a9b 100644 --- a/DCC.h +++ b/DCC.h @@ -86,10 +86,6 @@ private: static int ackManagerCv; static byte ackManagerStash; static bool ackReceived; - static int ackTriggerMilliamps; - static int ackMaxCurrent; - static int ackPollCount; - static unsigned long ackPulseStart; static ACK_CALLBACK ackManagerCallback; static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback); static void ackManagerLoop(); diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 88a082f..f9e45b5 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -59,6 +59,7 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { bits_sent = 0; sampleDelay = 0; lastSampleTaken = millis(); + ackPending=false; } void DCCWaveform::beginTrack() { setPowerMode(POWERMODE::ON); @@ -138,9 +139,6 @@ bool DCCWaveform::interrupt1() { } -void DCCWaveform::killRemainingRepeats() { - transmitRepeats=0; // will go idle at end of current packet -} void DCCWaveform::interrupt2() { // set currentBit to be the next bit to be sent. @@ -187,6 +185,11 @@ void DCCWaveform::interrupt2() { } } } + + // ACK check is prog track only and will only be checked if bits_sent=4 ... + // This means only once per 9-bit-byte AND never at the same cycle as the + // relatively expensive packet change code just above. + if (ackPending && bits_sent==4) checkAck(); } @@ -211,3 +214,64 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea int DCCWaveform::getLastCurrent() { return lastCurrent; } + +// Operations applicable to PROG track ONLY. +// (yes I know I could have subclassed the main track but...) + +void DCCWaveform::setAckBaseline(bool debug) { + if (isMainTrack) return; + ackThreshold=Hardware::getCurrentMilliamps(false) + ACK_MIN_PULSE; + if (debug) DIAG(F("\nACK-BASELINE mA=%d\n"),ackThreshold); +} + +void DCCWaveform::setAckPending(bool debug) { + if (isMainTrack) return; + (void)debug; + ackMaxCurrent=0; + ackPulseStart=0; + ackPulseDuration=0; + ackDetected=false; + ackCheckStart=millis(); + ackPending=true; // interrupt routines will now take note +} + +byte DCCWaveform::getAck(bool debug) { + if (ackPending) return (2); // still waiting + if (debug) DIAG(F("\nACK-%S after %dmS max=%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration, ackMaxCurrent, ackPulseDuration); + if (ackDetected) return (1); // Yes we had an ack + return(0); // pending set off but not detected means no ACK. +} + +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; + } + + lastCurrent=Hardware::getCurrentMilliamps(false); + if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent; + // An ACK is a pulse lasting between 4.5 and 8.5 mSecs (refer @haba) + + if (lastCurrent>ackThreshold) { + if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected + return; + } + + // not in pulse + if (ackPulseStart==0) return; // keep waiting for leading edge + + // detected trailing edge of pulse + ackPulseDuration=micros()-ackPulseStart; + + if (ackPulseDuration>1000 && ackPulseDuration<9000) { + 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 +} diff --git a/DCCWaveform.h b/DCCWaveform.h index 1b1628d..5c6cd15 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -44,16 +44,19 @@ class DCCWaveform { void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; volatile byte sentResetsSincePacket; - void killRemainingRepeats(); - + void setAckBaseline(bool debug); //prog track only + void setAckPending(bool debug); //prog track only + byte getAck(bool debug); //prog track only 0=NACK, 1=ACK 2=keep waiting + private: static void interruptHandler(); bool interrupt1(); void interrupt2(); - + void checkAck(); + bool isMainTrack; - + // Transmission controller byte transmitPacket[MAX_PACKET_SIZE]; // packet being transmitted byte transmitLength; @@ -75,5 +78,17 @@ class DCCWaveform { POWERMODE powerMode; unsigned long lastSampleTaken; unsigned int sampleDelay; + + // ACK management (Prog track only) + bool ackPending; + bool ackDetected; + int ackThreshold; + int ackMaxCurrent; + unsigned long ackCheckStart; // millis + unsigned int ackCheckDuration; // millis + + unsigned int ackPulseDuration; // micros + unsigned long ackPulseStart; // micros + }; #endif diff --git a/Hardware.cpp b/Hardware.cpp index a3d46c9..e9eb218 100644 --- a/Hardware.cpp +++ b/Hardware.cpp @@ -1,5 +1,6 @@ #include #include // use IDE menu Tools..Manage Libraries to locate and install TimerOne +#include "avdweb_AnalogReadFast.h" #include "Hardware.h" #include "Config.h" #include "DIAG.h" @@ -41,7 +42,12 @@ void Hardware::setSignal(bool isMainTrack, bool high) { int Hardware::getCurrentMilliamps(bool isMainTrack) { int pin = isMainTrack ? MAIN_SENSE_PIN : PROG_SENSE_PIN; float factor = isMainTrack ? MAIN_SENSE_FACTOR : PROG_SENSE_FACTOR; - int rawCurrent = analogRead(pin); + + // IMPORTANT: This function can be called in Interrupt() time within the 56uS timer + // The default analogRead takes ~100uS which is catastrphic + // so analogReadFast is used here. (-2uS) + int rawCurrent = analogReadFast(pin); + return (int)(rawCurrent * factor); } diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c50962b..42c69e3 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -107,7 +107,6 @@ void WifiInterface::loop(Stream & wifiStream) { switch (loopstate) { case 0: // looking for + connectionId=0; - streamer.flush(); if (ch=='+') loopstate=1; break; case 1: // Looking for I @@ -129,6 +128,7 @@ void WifiInterface::loop(Stream & wifiStream) { case 6: // reading for length if (ch==':') loopstate=(datalength==0)?99:7; // 99 is getout without reading next char else datalength=datalength*10 + (ch-'0'); + streamer.flush(); break; case 7: // reading data streamer.write(ch); diff --git a/avdweb_AnalogReadFast.h b/avdweb_AnalogReadFast.h new file mode 100644 index 0000000..b6be443 --- /dev/null +++ b/avdweb_AnalogReadFast.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2016 Albert van Dalen http://www.avdweb.nl +This program 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. +This program 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 at http://www.gnu.org/licenses . + +AUTHOR: Albert van Dalen +WEBSITE: http://www.avdweb.nl/arduino/libraries/fast-10-bit-adc.html + +HISTORY: +1.0.0 7-3-2018 analogReadFast for SAMD21 (10/12bit) and AVR (10bit) +*/ + +#ifndef analogReadFast_H +#define analogReadFast_H + +int inline analogReadFast(byte ADCpin); + +#if defined(__arm__) +int inline analogReadFast(byte ADCpin) // inline library functions must be in header +{ ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + int CTRLBoriginal = ADC->CTRLB.reg; + int AVGCTRLoriginal = ADC->AVGCTRL.reg; + int SAMPCTRLoriginal = ADC->SAMPCTRL.reg; + + ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64; // divide Clock by 64 + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 + + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization + + int adc = analogRead(ADCpin); + + ADC->CTRLB.reg = CTRLBoriginal; + ADC->AVGCTRL.reg = AVGCTRLoriginal; + ADC->SAMPCTRL.reg = SAMPCTRLoriginal; + + return adc; +} +#else +int inline analogReadFast(byte ADCpin) +{ byte ADCSRAoriginal = ADCSRA; + ADCSRA = (ADCSRA & B11111000) | 4; + int adc = analogRead(ADCpin); + ADCSRA = ADCSRAoriginal; + return adc; +} +#endif +#endif