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:
parent
5a23fc717f
commit
b0debd1fab
61
DCC.cpp
61
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<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
4
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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
55
avdweb_AnalogReadFast.h
Normal 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
|
Loading…
Reference in New Issue
Block a user