1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-23 08:06:13 +01:00

Interrupt time ACK manager

This commit is contained in:
Asbelos 2020-07-02 12:49:35 +01:00
parent 5a23fc717f
commit b0debd1fab
7 changed files with 160 additions and 69 deletions

61
DCC.cpp
View File

@ -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<RESET_MIN) return; // try later
ackTriggerMilliamps=Hardware::getCurrentMilliamps(false) + ACK_MIN_PULSE;
if (debugMode) DIAG(F("\nACK_BASELINE trigger mA=%d\n"),ackTriggerMilliamps);
DCCWaveform::progTrack.setAckBaseline(debugMode);
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
@ -445,9 +437,7 @@ void DCC::ackManagerLoop() {
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), 6);
ackPulseStart=0;
ackMaxCurrent=0;
ackPollCount=0;
DCCWaveform::progTrack.setAckPending(debugMode);
}
break;
@ -456,9 +446,7 @@ void DCC::ackManagerLoop() {
if (resets<RESET_MIN) return; // try later
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), 6);
ackPulseStart=0;
ackMaxCurrent=0;
ackPollCount=0;
DCCWaveform::progTrack.setAckPending(debugMode);
}
break;
@ -468,9 +456,7 @@ void DCC::ackManagerLoop() {
if (debugMode) DIAG(F("\nVB %d %d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), 5);
ackPulseStart=0;
ackMaxCurrent=0;
ackPollCount=0;
DCCWaveform::progTrack.setAckPending(debugMode);
}
break;
@ -482,48 +468,17 @@ void DCC::ackManagerLoop() {
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), 5);
ackPulseStart=0;
ackMaxCurrent=0;
ackPollCount=0;
DCCWaveform::progTrack.setAckPending(debugMode);
}
break;
case WACK: // wait for ack (or absence of ack)
{
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!!
byte ackState=DCCWaveform::progTrack.getAck(debugMode);
if (ackState==2) return; // keep polling
ackReceived=ackState==1;
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
}
case ITC0:
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
if (ackReceived) {

4
DCC.h
View File

@ -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();

View File

@ -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
}

View File

@ -44,13 +44,16 @@ 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;
@ -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

View File

@ -1,5 +1,6 @@
#include <Arduino.h>
#include <TimerOne.h> // 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);
}

View File

@ -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);

55
avdweb_AnalogReadFast.h Normal file
View File

@ -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