1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 21:01:25 +01:00

Railcom timing

This commit is contained in:
Asbelos 2024-09-27 12:21:43 +01:00
parent dc481a2f0c
commit 61c8f6b047
3 changed files with 44 additions and 46 deletions

View File

@ -57,66 +57,59 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
interrupts();
//diagnostic pinMode(4,OUTPUT);
}
void DCCTimer::startRailcomTimer(byte brakePin) {
(void) brakePin; // Ignored... works on pin 9 only
// diagnostic digitalWrite(4,HIGH);
/* The Railcom timer is started in such a way that it
- First triggers 28uS after the last TIMER1 tick.
- First triggers 58+29 uS after the previous TIMER1 tick.
This provides an accurate offset (in High Accuracy mode)
for the start of the Railcom cutout.
- Sets the Railcom pin high at first tick,
because its been setup with 100% PWM duty cycle.
- Sets the Railcom pin high at first tick and subsequent ticks
until its reset to setting pin 9 low at next tick.
- Cycles at 436uS so the second tick is the
correct distance from the cutout.
- Waveform code is responsible for altering the PWM
duty cycle to 0% any time between the first and last tick.
- Waveform code is responsible for resetting
any time between the first and second tick.
(there will be 7 DCC timer1 ticks in which to do this.)
*/
(void) brakePin; // Ignored... works on pin 9 only
const int cutoutDuration = 430; // Desired interval in microseconds
// Set up Timer2 for CTC mode (Clear Timer on Compare Match)
TCCR2A = 0; // Clear Timer2 control register A
TCCR2B = 0; // Clear Timer2 control register B
TCNT2 = 0; // Initialize Timer2 counter value to 0
// Configure Phase and Frequency Correct PWM mode
TCCR2A = (1 << COM2B1); // enable pwm on pin 9
TCCR2A |= (1 << WGM20);
const int cycle=cutoutDuration/2;
// Set Timer 2 prescaler to 32
TCCR2B = (1 << CS21) | (1 << CS20); // 32 prescaler
// Set the compare match value for desired interval
OCR2A = (F_CPU / 1000000) * cutoutDuration / 64 - 1;
// Calculate the compare match value for desired duty cycle
OCR2B = OCR2A+1; // set duty cycle to 100%= OCR2A)
const byte RailcomFudge0=58+58+29;
// Set Timer2 to CTC mode with set on compare match
TCCR2A = (1 << WGM21) | (1 << COM2B0) | (1 << COM2B1);
// Prescaler of 32
TCCR2B = (1 << CS21) | (1 << CS20);
OCR2A = cycle-1; // Compare match value for 430 uS
// Enable Timer2 output on pin 9 (OC2B)
DDRB |= (1 << DDB1);
// TODO Fudge TCNT2 to sync with last tcnt1 tick + 28uS
// RailcomFudge2 is the expected time from idealised
// setup call (at previous DCC timer interrupt) to the cutout.
// This value should be reduced to reflect the Timer1 value
// measuring the time since the previous hardware interrupt
byte tcfudge=TCNT1/16;
TCNT2=cycle-RailcomFudge0/2+tcfudge/2;
// Previous TIMER1 Tick was at rising end-of-packet bit
// Cutout starts half way through first preamble
// that is 2.5 * 58uS later.
// TCNT1 ticks 8 times / microsecond
// auto microsendsToFirstRailcomTick=(58+58+29)-(TCNT1/8);
// set the railcom timer counter allowing for phase-correct
// CHris's NOTE:
// I dont kniow quite how this calculation works out but
// it does seems to get a good answer.
TCNT2=193 + (ICR1 - TCNT1)/8;
}
}
void DCCTimer::ackRailcomTimer() {
OCR2B= 0x00; // brake pin pwm duty cycle 0 at next tick
// Change Timer2 to CTC mode with RESET pin 9 on next compare match
TCCR2A = (1 << WGM21) | (1 << COM2B1);
// diagnostic digitalWrite(4,LOW);
}

View File

@ -31,7 +31,7 @@
#include "DCCACK.h"
#include "DIAG.h"
bool DCCWaveform::cutoutNextTime=false;
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
@ -71,9 +71,14 @@ void DCCWaveform::loop() {
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack
// member functions would be cleaner but have more overhead
if (cutoutNextTime) {
cutoutNextTime=false;
DCCTimer::startRailcomTimer(9);
}
byte sigMain=signalTransform[mainTrack.state];
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
@ -140,6 +145,12 @@ void DCCWaveform::interrupt2() {
// or WAVE_HIGH_0 for a 0 bit.
if (remainingPreambles > 0 ) {
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
// predict railcom cutout on next interrupt
cutoutNextTime= remainingPreambles==requiredPreambles
&& railcomActive
&& isMainTrack;
remainingPreambles--;
// As we get to the end of the preambles, open the reminder window.
@ -147,7 +158,7 @@ void DCCWaveform::interrupt2() {
// that the reminder doesn't block a more urgent packet.
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
if (remainingPreambles==1) promotePendingPacket();
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
else if (remainingPreambles==14 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
// Update free memory diagnostic as we don't have anything else to do this time.
// Allow for checkAck and its called functions using 22 bytes more.
else DCCTimer::updateMinimumFreeMemoryISR(22);
@ -171,13 +182,7 @@ void DCCWaveform::interrupt2() {
bytes_sent = 0;
// preamble for next packet will start...
remainingPreambles = requiredPreambles;
// set the railcom coundown to trigger half way
// through the first preamble bit.
// Note.. we are still sending the last packet bit
// and we then have to allow for the packet end bit
if (isMainTrack && railcomActive) DCCTimer::startRailcomTimer(9);
}
}
}
}
#pragma GCC pop_options

View File

@ -114,7 +114,7 @@ class DCCWaveform {
byte pendingRepeats;
static volatile bool railcomActive; // switched on by user
static volatile bool railcomDebug; // switched on by user
static bool cutoutNextTime; // railcom
#ifdef ARDUINO_ARCH_ESP32
static RMTChannel *rmtMainChannel;
static RMTChannel *rmtProgChannel;