1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-04-23 13:31:19 +02:00

RAILCOM cutout (mega)

This commit is contained in:
Asbelos 2025-01-12 20:37:39 +00:00
parent 7e8841611d
commit 680f765775
8 changed files with 121 additions and 66 deletions

View File

@ -1223,6 +1223,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
return true; return true;
#ifdef HAS_ENOUGH_MEMORY #ifdef HAS_ENOUGH_MEMORY
case "RAILCOM"_hk: // <D RAILCOM ON/OFF>
Diag::RAILCOM = onOff;
return true;
case "WIFI"_hk: // <D WIFI ON/OFF> case "WIFI"_hk: // <D WIFI ON/OFF>
Diag::WIFI = onOff; Diag::WIFI = onOff;
return true; return true;

View File

@ -2,7 +2,7 @@
* © 2021 Mike S * © 2021 Mike S
* © 2021-2023 Harald Barth * © 2021-2023 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2021 Chris Harlow * © 2021-2025 Chris Harlow
* © 2021 David Cutting * © 2021 David Cutting
* All rights reserved. * All rights reserved.
* *
@ -57,66 +57,59 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt TIMSK1 = _BV(TOIE1); // Enable Software interrupt
interrupts(); interrupts();
//diagnostic pinMode(4,OUTPUT);
} }
void DCCTimer::startRailcomTimer(byte brakePin) { 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 /* 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) This provides an accurate offset (in High Accuracy mode)
for the start of the Railcom cutout. for the start of the Railcom cutout.
- Sets the Railcom pin high at first tick, - Sets the Railcom pin high at first tick and subsequent ticks
because its been setup with 100% PWM duty cycle. until its reset to setting pin 9 low at next tick.
- Cycles at 436uS so the second tick is the - Cycles at 436uS so the second tick is the
correct distance from the cutout. correct distance from the cutout.
- Waveform code is responsible for altering the PWM - Waveform code is responsible for resetting
duty cycle to 0% any time between the first and last tick. any time between the first and second tick.
(there will be 7 DCC timer1 ticks in which to do this.) (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 const int cutoutDuration = 430; // Desired interval in microseconds
const int cycle=cutoutDuration/2;
// Set up Timer2 for CTC mode (Clear Timer on Compare Match) const byte RailcomFudge0=58+58+29;
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);
// 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)
// 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) // Enable Timer2 output on pin 9 (OC2B)
DDRB |= (1 << DDB1); 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 // Previous TIMER1 Tick was at rising end-of-packet bit
// Cutout starts half way through first preamble // Cutout starts half way through first preamble
// that is 2.5 * 58uS later. // 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() { 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

@ -21,18 +21,16 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
// This code is replaced entirely on an ESP32 see DCCWaveformRMT.cpp // This code is replaced entirely on an ESP32
#include <Arduino.h> #include <Arduino.h>
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "TrackManager.h" #include "TrackManager.h"
#include "DCCTimer.h" #include "DCCTimer.h"
#include "DCCACK.h" #include "DCCACK.h"
#include "DIAG.h" #include "DIAG.h"
bool DCCWaveform::cutoutNextTime=false;
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
@ -72,9 +70,18 @@ void DCCWaveform::loop() {
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("-O3") #pragma GCC optimize ("-O3")
void DCCWaveform::interruptHandler() { void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack // call the timer edge sensitive actions for progtrack and maintrack
// member functions would be cleaner but have more overhead // member functions would be cleaner but have more overhead
#if defined(HAS_ENOUGH_MEMORY)
if (cutoutNextTime) {
cutoutNextTime=false;
railcomSampleWindow=false; // about to cutout, stop reading railcom data.
railcomCutoutCounter++;
DCCTimer::startRailcomTimer(9);
}
#endif
byte sigMain=signalTransform[mainTrack.state]; byte sigMain=signalTransform[mainTrack.state];
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state]; byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
@ -117,18 +124,23 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
bits_sent = 0; bits_sent = 0;
} }
bool DCCWaveform::railcomPossible=false; // High accuracy only
volatile bool DCCWaveform::railcomActive=false; // switched on by user volatile bool DCCWaveform::railcomActive=false; // switched on by user
volatile bool DCCWaveform::railcomDebug=false; // switched on by user volatile bool DCCWaveform::railcomDebug=false; // switched on by user
volatile bool DCCWaveform::railcomSampleWindow=false; // true during packet transmit
volatile byte DCCWaveform::railcomCutoutCounter=0; // cyclic cutout
volatile byte DCCWaveform::railcomLastAddressHigh=0;
volatile byte DCCWaveform::railcomLastAddressLow=0;
bool DCCWaveform::setRailcom(bool on, bool debug) { bool DCCWaveform::setRailcom(bool on, bool debug) {
if (on) { if (on && railcomPossible) {
// TODO check possible
railcomActive=true; railcomActive=true;
railcomDebug=debug; railcomDebug=debug;
} }
else { else {
railcomActive=false; railcomActive=false;
railcomDebug=false; railcomDebug=false;
railcomSampleWindow=false;
} }
return railcomActive; return railcomActive;
} }
@ -141,14 +153,37 @@ void DCCWaveform::interrupt2() {
// or WAVE_HIGH_0 for a 0 bit. // or WAVE_HIGH_0 for a 0 bit.
if (remainingPreambles > 0 ) { if (remainingPreambles > 0 ) {
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
remainingPreambles--; remainingPreambles--;
// As we get to the end of the preambles, open the reminder window. // As we get to the end of the preambles, open the reminder window.
// This delays any reminder insertion until the last moment so // This delays any reminder insertion until the last moment so
// that the reminder doesn't block a more urgent packet. // that the reminder doesn't block a more urgent packet.
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1; reminderWindowOpen=transmitRepeats==0 && remainingPreambles<10 && remainingPreambles>1;
if (remainingPreambles==1) promotePendingPacket(); if (remainingPreambles==1)
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer(); promotePendingPacket();
#if defined(HAS_ENOUGH_MEMORY)
else if (isMainTrack && railcomActive) {
if (remainingPreambles==(requiredPreambles-1)) {
// First look if we need to start a railcom cutout on next interrupt
cutoutNextTime= true;
} else if (remainingPreambles==(requiredPreambles-12)) {
// cutout has ended so its now possible to poll the railcom detectors
// requiredPreambles is one higher that preamble length so
// if preamble length is 16 then this evaluates to 5
// Remember address bytes of last sent packet so that Railcom can
// work out where the channel2 data came from.
railcomLastAddressHigh=transmitPacket[0];
railcomLastAddressLow =transmitPacket[1];
railcomSampleWindow=true;
} else if (remainingPreambles==(requiredPreambles-3)) {
// cutout can be ended when read
// see above for requiredPreambles
DCCTimer::ackRailcomTimer();
}
}
#endif
// Update free memory diagnostic as we don't have anything else to do this time. // 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. // Allow for checkAck and its called functions using 22 bytes more.
else DCCTimer::updateMinimumFreeMemoryISR(22); else DCCTimer::updateMinimumFreeMemoryISR(22);
@ -172,13 +207,7 @@ void DCCWaveform::interrupt2() {
bytes_sent = 0; bytes_sent = 0;
// preamble for next packet will start... // preamble for next packet will start...
remainingPreambles = requiredPreambles; 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 #pragma GCC pop_options
@ -231,7 +260,7 @@ void DCCWaveform::promotePendingPacket() {
// Fortunately reset and idle packets are the same length // Fortunately reset and idle packets are the same length
// Note: If railcomDebug is on, then we send resets to the main // Note: If railcomDebug is on, then we send resets to the main
// track instead of idles. This means that all data will be zeros // track instead of idles. This means that all data will be zeros
// and only the porersets will be ones, making it much // and only the presets will be ones, making it much
// easier to read on a logic analyser. // easier to read on a logic analyser.
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket)); memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket); transmitLength = sizeof(idlePacket);

View File

@ -3,7 +3,7 @@
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2024 Harald Barth * © 2020-2024 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2025 Chris Harlow
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -23,11 +23,8 @@
*/ */
#ifndef DCCWaveform_h #ifndef DCCWaveform_h
#define DCCWaveform_h #define DCCWaveform_h
#include "MotorDriver.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
#include "DCCRMT.h" #include "DCCRMT.h"
#include "TrackManager.h"
#endif #endif
@ -86,7 +83,29 @@ class DCCWaveform {
bool isReminderWindowOpen(); bool isReminderWindowOpen();
void promotePendingPacket(); void promotePendingPacket();
static bool setRailcom(bool on, bool debug); static bool setRailcom(bool on, bool debug);
static bool isRailcom() {return railcomActive;} inline static bool isRailcom() {
return railcomActive;
};
inline static byte getRailcomCutoutCounter() {
return railcomCutoutCounter;
};
inline static bool isRailcomSampleWindow() {
return railcomSampleWindow;
};
inline static bool isRailcomPossible() {
return railcomPossible;
};
inline static void setRailcomPossible(bool yes) {
railcomPossible=yes;
if (!yes) setRailcom(false,false);
};
inline static uint16_t getRailcomLastLocoAddress() {
// first 2 bits 00=short loco, 11=long loco , 01/10 = accessory
byte addressType=railcomLastAddressHigh & 0xC0;
if (addressType==0xC0) return ((railcomLastAddressHigh & 0x3f)<<8) | railcomLastAddressLow;
if (addressType==0x00) return railcomLastAddressHigh & 0x3F;
return 0;
}
private: private:
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
@ -112,9 +131,13 @@ class DCCWaveform {
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte pendingLength; byte pendingLength;
byte pendingRepeats; byte pendingRepeats;
static bool railcomPossible; // High accuracy mode only
static volatile bool railcomActive; // switched on by user static volatile bool railcomActive; // switched on by user
static volatile bool railcomDebug; // switched on by user static volatile bool railcomDebug; // switched on by user
static volatile bool railcomSampleWindow; // when safe to sample
static volatile byte railcomCutoutCounter; // incremented for each cutout
static volatile byte railcomLastAddressHigh,railcomLastAddressLow;
static bool cutoutNextTime; // railcom
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
static RMTChannel *rmtMainChannel; static RMTChannel *rmtMainChannel;
static RMTChannel *rmtProgChannel; static RMTChannel *rmtProgChannel;

View File

@ -25,6 +25,7 @@
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "DCCACK.h" #include "DCCACK.h"
#include "TrackManager.h"
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);

View File

@ -1,5 +1,5 @@
/* /*
* © 2020, Chris Harlow. All rights reserved. * © 2020=2025, Chris Harlow. All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
@ -27,6 +27,8 @@ bool Diag::WIFI=false;
bool Diag::WITHROTTLE=false; bool Diag::WITHROTTLE=false;
bool Diag::ETHERNET=false; bool Diag::ETHERNET=false;
bool Diag::LCN=false; bool Diag::LCN=false;
bool Diag::RAILCOM=false;
void StringFormatter::diag( const FSH* input...) { void StringFormatter::diag( const FSH* input...) {

View File

@ -1,5 +1,5 @@
/* /*
* © 2020, Chris Harlow. All rights reserved. * © 2020-2025, Chris Harlow. All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
@ -30,6 +30,7 @@ class Diag {
static bool WITHROTTLE; static bool WITHROTTLE;
static bool ETHERNET; static bool ETHERNET;
static bool LCN; static bool LCN;
static bool RAILCOM;
}; };

View File

@ -332,7 +332,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
canDo &= track[t]->trackPWM; canDo &= track[t]->trackPWM;
} }
} }
if (!canDo) { if (canDo) DIAG(F("HA mode"));
else {
// if we discover that HA mode was globally impossible // if we discover that HA mode was globally impossible
// we must adjust the trackPWM capabilities // we must adjust the trackPWM capabilities
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
@ -341,6 +342,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} }
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
} }
DCCWaveform::setRailcomPossible(canDo);
#else #else
// For ESP32 we just reinitialize the DCC Waveform // For ESP32 we just reinitialize the DCC Waveform
DCCWaveform::begin(); DCCWaveform::begin();