Skip to content

Commit ffc9c78

Browse files
committed
Railcom tidy (AVR timer ok)
1 parent c166081 commit ffc9c78

File tree

8 files changed

+61
-72
lines changed

8 files changed

+61
-72
lines changed

DCCEXParser.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,24 +1306,19 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
13061306
{ // <C RAILCOM ON|OFF|DEBUG >
13071307
if (params<2) return false;
13081308
bool on=false;
1309-
bool debug=false;
13101309
switch (p[1]) {
13111310
case "ON"_hk:
13121311
case 1:
13131312
on=true;
13141313
break;
1315-
case "DEBUG"_hk:
1316-
on=true;
1317-
debug=true;
1318-
break;
13191314
case "OFF"_hk:
13201315
case 0:
13211316
break;
13221317
default:
13231318
return false;
13241319
}
13251320
DIAG(F("Railcom %S")
1326-
,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF"));
1321+
,DCCWaveform::setRailcom(on)?F("ON"):F("OFF"));
13271322
return true;
13281323
}
13291324
#endif

DCCTimerAVR.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,14 @@ void DCCTimer::startRailcomTimer(byte brakePin) {
6666
// diagnostic digitalWrite(4,HIGH);
6767

6868
/* The Railcom timer is started in such a way that it
69-
- First triggers 58+29 uS after the previous TIMER1 tick.
69+
- First triggers a calculated time after the previous TIMER1 tick.
7070
This provides an accurate offset (in High Accuracy mode)
71-
for the start of the Railcom cutout.
72-
- Sets the Railcom pin high at first tick and subsequent ticks
71+
for the start of the Railcom cutout. The delayBeforeCutout value
72+
reflects that this call is made at the start of the end-packet bit
73+
thus the delay is nominally 58+58+Tcs BUT
74+
in HA mode, the dcc pin is flipped at the NEXT
75+
interrupt so runs 1 tick behind the code. Thus an extra 58uS is needed.
76+
- Timer2 Sets the Railcom pin high at first tick and subsequent ticks
7377
until its reset to setting pin 9 low at next tick.
7478
7579
- Cycles at cutoutDuration so the second tick is the
@@ -80,29 +84,32 @@ void DCCTimer::startRailcomTimer(byte brakePin) {
8084
(there will be 7 DCC timer1 ticks in which to do this.)
8185
8286
*/
83-
const int Tcs=28; // (26+32)/2 would be half way spec Desired time from idealised setup call (at previous DCC timer interrupt) to the cutout but choose even
84-
const int cutoutDuration = 450; // As chosen by most
85-
const byte delayBeforeCutout=58+58+Tcs; // Expected time from idealised setup call (at previous DCC timer interrupt) to the cutout. This is the time we need to wait before we can set pin 9 high. We will then set pin 9 low at the next tick which is cutoutDuration later. This value should be reduced to reflect the Timer1 value measuring the time since the previous hardware interrupt.
87+
const int Tcs=28; // NMRA spec is 26..32
88+
const int cutoutDuration_uS = 450; // As chosen by most
89+
const uint16_t delayBeforeCutout_uS=58+58+58+Tcs;
90+
const uint16_t timer1_ticks_per_uS = 8;
91+
const uint16_t timer2_uS_per_tick = 2;
8692

8793
// Set Timer2 to CTC mode with set on compare match
8894
TCCR2A = (1 << WGM21) | (1 << COM2B0) | (1 << COM2B1);
8995
// Prescaler of 32
9096
TCCR2B = (1 << CS21) | (1 << CS20);
91-
OCR2A = cutoutDuration/2; // Compare match value for cutout duration in timer2 ticks (2uSec)
97+
OCR2A = cutoutDuration_uS/timer2_uS_per_tick; // Compare match value for cutout duration in timer2 ticks (2uSec)
9298
// Enable Timer2 output on pin 9 (OC2B)
9399
DDRB |= (1 << DDB1);
94100

95101
// timeSlip is the expired time since the DCC timer interrupt that triggered this call.
96102
// This allows us to cope with any delays between the interrupt and this code executing.
97103

98-
// tcnt1 = prescaler 64, tcnt2 prescaler=32
104+
99105
noInterrupts();
100-
uint16_t timeSlip=(TCNT1/64)*32;
101-
// Adjust the Timer2 counter so it first triggers at the right place in the waveform.
102-
// TCNT1 moves 8 ticks per uSec
103-
// TCNT2 uses 2 uSec for each tick
104-
// TCNT1 is 8*2 = 16 times faster than TCNT2
105-
TCNT2=(cutoutDuration+TCNT1/8-delayBeforeCutout)/2;
106+
// Time already used since DCC interrupt
107+
uint16_t timeSlip_uS=TCNT1/timer1_ticks_per_uS;
108+
109+
// Calculate the time left before the cutout is to be triggered
110+
uint16_t timeToCutout_uS=cutoutDuration_uS+timeSlip_uS-delayBeforeCutout_uS;
111+
// Adjust clock2 to trigger on time
112+
TCNT2=timeToCutout_uS/timer2_uS_per_tick;
106113
interrupts();
107114
}
108115

DCCWaveform.cpp

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "DCCTimer.h"
3030
#include "DCCACK.h"
3131
#include "DIAG.h"
32+
#include "Railcom.h"
3233

3334
bool DCCWaveform::cutoutNextTime=false;
3435
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
@@ -77,8 +78,7 @@ void DCCWaveform::interruptHandler() {
7778
#if defined(HAS_ENOUGH_MEMORY)
7879
if (cutoutNextTime) {
7980
cutoutNextTime=false;
80-
railcomSampleWindow=false; // about to cutout, stop reading railcom data.
81-
railcomCutoutCounter++;
81+
Railcom::incCutout();
8282
DCCTimer::startRailcomTimer(9);
8383
}
8484
#endif
@@ -126,21 +126,13 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
126126

127127
bool DCCWaveform::railcomPossible=false; // High accuracy only
128128
volatile bool DCCWaveform::railcomActive=false; // switched on by user
129-
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
130-
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
131-
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
132-
volatile byte DCCWaveform::railcomLastAddressHigh=0;
133-
volatile byte DCCWaveform::railcomLastAddressLow=0;
134-
135-
bool DCCWaveform::setRailcom(bool on, bool debug) {
129+
130+
bool DCCWaveform::setRailcom(bool on) {
136131
if (on && railcomPossible) {
137132
railcomActive=true;
138-
railcomDebug=debug;
139133
}
140134
else {
141135
railcomActive=false;
142-
railcomDebug=false;
143-
railcomSampleWindow=false;
144136
}
145137
return railcomActive;
146138
}
@@ -174,9 +166,7 @@ void DCCWaveform::interrupt2() {
174166
// if preamble length is 16 then this evaluates to 5
175167
// Remember address bytes of last sent packet so that Railcom can
176168
// work out where the channel2 data came from.
177-
railcomLastAddressHigh=transmitPacket[0];
178-
railcomLastAddressLow =transmitPacket[1];
179-
railcomSampleWindow=true;
169+
Railcom::setLoco(transmitPacket[0],transmitPacket[1]);
180170
} else if (remainingPreambles==(requiredPreambles-3)) {
181171
// cutout can be ended when read
182172
// see above for requiredPreambles
@@ -258,11 +248,7 @@ void DCCWaveform::promotePendingPacket() {
258248

259249
// nothing to do, just send idles or resets
260250
// Fortunately reset and idle packets are the same length
261-
// Note: If railcomDebug is on, then we send resets to the main
262-
// track instead of idles. This means that all data will be zeros
263-
// and only the presets will be ones, making it much
264-
// easier to read on a logic analyser.
265-
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
251+
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
266252
transmitLength = sizeof(idlePacket);
267253
transmitRepeats = 0;
268254
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)

DCCWaveform.h

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,30 +82,18 @@ class DCCWaveform {
8282
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
8383
bool isReminderWindowOpen();
8484
void promotePendingPacket();
85-
static bool setRailcom(bool on, bool debug);
85+
static bool setRailcom(bool on);
8686
inline static bool isRailcom() {
8787
return railcomActive;
8888
};
89-
inline static byte getRailcomCutoutCounter() {
90-
return railcomCutoutCounter;
91-
};
92-
inline static bool isRailcomSampleWindow() {
93-
return railcomSampleWindow;
94-
};
9589
inline static bool isRailcomPossible() {
9690
return railcomPossible;
9791
};
9892
inline static void setRailcomPossible(bool yes) {
9993
railcomPossible=yes;
100-
if (!yes) setRailcom(false,false);
94+
if (!yes) setRailcom(false);
10195
};
102-
inline static uint16_t getRailcomLastLocoAddress() {
103-
// first 2 bits 00=short loco, 11=long loco , 01/10 = accessory
104-
byte addressType=railcomLastAddressHigh & 0xC0;
105-
if (addressType==0xC0) return ((railcomLastAddressHigh & 0x3f)<<8) | railcomLastAddressLow;
106-
if (addressType==0x00) return railcomLastAddressHigh & 0x3F;
107-
return 0;
108-
}
96+
10997

11098
private:
11199
#ifndef ARDUINO_ARCH_ESP32
@@ -133,10 +121,6 @@ class DCCWaveform {
133121
byte pendingRepeats;
134122
static bool railcomPossible; // High accuracy mode only
135123
static volatile bool railcomActive; // switched on by user
136-
static volatile bool railcomDebug; // switched on by user
137-
static volatile bool railcomSampleWindow; // when safe to sample
138-
static volatile byte railcomCutoutCounter; // incremented for each cutout
139-
static volatile byte railcomLastAddressHigh,railcomLastAddressLow;
140124
static bool cutoutNextTime; // railcom
141125
#ifdef ARDUINO_ARCH_ESP32
142126
static RMTChannel *rmtMainChannel;

DCCWaveformRMT.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ RMTChannel *DCCWaveform::rmtProgChannel = NULL;
3434

3535
bool DCCWaveform::railcomPossible=false; // High accuracy only
3636
volatile bool DCCWaveform::railcomActive=false; // switched on by user
37-
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
38-
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
39-
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
40-
volatile byte DCCWaveform::railcomLastAddressHigh=0;
41-
volatile byte DCCWaveform::railcomLastAddressLow=0;
4237

4338
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
4439
isMainTrack = isMain;
@@ -112,7 +107,7 @@ void IRAM_ATTR DCCWaveform::loop() {
112107
DCCACK::checkAck(progTrack.getResets());
113108
}
114109

115-
bool DCCWaveform::setRailcom(bool on, bool debug) {
110+
bool DCCWaveform::setRailcom(bool on) {
116111
// TODO... ESP32 railcom waveform
117112
return false;
118113
}

IO_I2CRailcom.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,15 @@ void I2CRailcom::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
8383

8484
// have we read this cutout already?
8585
// basically we only poll once per packet when railcom cutout is working
86-
auto cut=DCCWaveform::getRailcomCutoutCounter();
86+
auto cut=Railcom::getCutout();
8787
if (cutoutCounter==cut) return;
8888
cutoutCounter=cut;
8989
Railcom::loop(); // in case a csv read has timed out
9090

9191
// Obtain data length from the collector
9292
byte inbuf[1];
93-
byte queryLength[]={'?'};
93+
uint16_t loco=Railcom::getLoco();
94+
byte queryLength[]={'?',(byte)(loco >>8) , (byte)(loco & 0xff)};
9495
auto state=I2CManager.read(_I2CAddress, inbuf, 1,queryLength,sizeof(queryLength));
9596
if (state) {
9697
DIAG(F("RC ? state=%d"),state);

Railcom.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "EXRAIL2.h"
3232

3333
uint16_t Railcom::expectLoco=0;
34+
uint16_t Railcom::nextLoco=0;
35+
3436
uint16_t Railcom::expectCV=0;
3537
unsigned long Railcom::expectWait=0;
3638
ACK_CALLBACK Railcom::expectCallback=0;
@@ -42,6 +44,17 @@ enum ResponseType: byte {
4244
CV_VALUE_LIST=0xC0, // list of cv values read from a POM, cv id and 4 values follow
4345
};
4446

47+
void Railcom::setLoco(byte packet0, byte packet1) {
48+
// first 2 bits 00=short loco, 11=long loco , 01/10 = accessory
49+
byte addressType=packet0 & 0xC0;
50+
if (addressType==0xC0) nextLoco=((packet0 & 0x3f)<<8) | packet1;
51+
else if (addressType==0x00) nextLoco=packet0 & 0x3F;
52+
else nextLoco=0;
53+
}
54+
55+
uint16_t Railcom::getLoco() {
56+
return nextLoco;
57+
}
4558

4659
// anticipate is used when waiting for a CV read from a railcom loco
4760
void Railcom::anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback) {
@@ -75,10 +88,7 @@ void Railcom::process(int16_t firstVpin,byte * buffer, byte length) {
7588
break;
7689
case CV_VALUE: { // csv value from POM read
7790
byte value=buffer[i+1];
78-
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
79-
if (expectCallback) expectCallback(value);
80-
expectCV=0;
81-
}
91+
if (expectCallback) expectCallback(value);
8292
i+=2;
8393
}
8494
break;
@@ -97,3 +107,8 @@ void Railcom::loop() {
97107
expectCV=0;
98108
}
99109
}
110+
111+
byte Railcom::cutoutCounter=0;
112+
void Railcom::incCutout() {cutoutCounter++;};
113+
byte Railcom::getCutout() {return cutoutCounter;};
114+

Railcom.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@ class Railcom {
2828
public:
2929
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback);
3030
static void process(int16_t firstVpin,byte * buffer, byte length );
31+
static void setLoco(byte packet0,byte packet1);
32+
static uint16_t getLoco();
3133
static void loop();
34+
static void incCutout();
35+
static byte getCutout();
3236
private:
3337
static const unsigned long POM_READ_TIMEOUT=500; // as per spec
34-
static uint16_t expectCV,expectLoco;
38+
static uint16_t expectCV,expectLoco, nextLoco;
3539
static unsigned long expectWait;
3640
static ACK_CALLBACK expectCallback;
41+
static byte cutoutCounter; // cyclic cutout
42+
3743
static const byte MAX_WAIT_FOR_GLITCH=20; // number of dead or empty packets before assuming loco=0
3844
};
3945

0 commit comments

Comments
 (0)