mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-30 03:26:13 +01:00
Merge branch 'devel_railcom_Mega' into devel
This commit is contained in:
commit
6f1df6ce8e
|
@ -1035,7 +1035,30 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||||
DCC::setGlobalSpeedsteps(128);
|
DCC::setGlobalSpeedsteps(128);
|
||||||
DIAG(F("128 Speedsteps"));
|
DIAG(F("128 Speedsteps"));
|
||||||
return true;
|
return true;
|
||||||
|
case "RAILCOM"_hk:
|
||||||
|
{ // <C RAILCOM ON|OFF|DEBUG >
|
||||||
|
if (params<2) return false;
|
||||||
|
bool on=false;
|
||||||
|
bool debug=false;
|
||||||
|
switch (p[1]) {
|
||||||
|
case "ON"_hk:
|
||||||
|
case 1:
|
||||||
|
on=true;
|
||||||
|
break;
|
||||||
|
case "DEBUG"_hk:
|
||||||
|
on=true;
|
||||||
|
debug=true;
|
||||||
|
break;
|
||||||
|
case "OFF"_hk:
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DIAG(F("Railcom %S")
|
||||||
|
,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||||
if (params >= 3) {
|
if (params >= 3) {
|
||||||
|
|
|
@ -62,6 +62,8 @@ class DCCTimer {
|
||||||
static bool isPWMPin(byte pin);
|
static bool isPWMPin(byte pin);
|
||||||
static void setPWM(byte pin, bool high);
|
static void setPWM(byte pin, bool high);
|
||||||
static void clearPWM();
|
static void clearPWM();
|
||||||
|
static void startRailcomTimer(byte brakePin);
|
||||||
|
static void ackRailcomTimer();
|
||||||
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||||
static void DCCEXanalogWrite(uint8_t pin, int value);
|
static void DCCEXanalogWrite(uint8_t pin, int value);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
#define TIMER1_A_PIN 11
|
#define TIMER1_A_PIN 11
|
||||||
#define TIMER1_B_PIN 12
|
#define TIMER1_B_PIN 12
|
||||||
#define TIMER1_C_PIN 13
|
#define TIMER1_C_PIN 13
|
||||||
|
#define TIMER2_A_PIN 10
|
||||||
|
#define TIMER2_B_PIN 9
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define TIMER1_A_PIN 9
|
#define TIMER1_A_PIN 9
|
||||||
#define TIMER1_B_PIN 10
|
#define TIMER1_B_PIN 10
|
||||||
|
@ -55,6 +58,66 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
|
/* The Railcom timer is started in such a way that it
|
||||||
|
- First triggers 28uS after the last 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.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
(there will be 7 DCC timer1 ticks in which to do this.)
|
||||||
|
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Enable Timer2 output on pin 9 (OC2B)
|
||||||
|
DDRB |= (1 << DDB1);
|
||||||
|
// TODO Fudge TCNT2 to sync with last tcnt1 tick + 28uS
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ISR called by timer interrupt every 58uS
|
// ISR called by timer interrupt every 58uS
|
||||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,14 @@ extern char *__malloc_heap_start;
|
||||||
interruptHandler();
|
interruptHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::ackRailcomTimer() {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
bool DCCTimer::isPWMPin(byte pin) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
(void) pin;
|
(void) pin;
|
||||||
return false; // TODO what are the relevant pins?
|
return false; // TODO what are the relevant pins?
|
||||||
|
|
|
@ -76,6 +76,14 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::ackRailcomTimer() {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||||
// copied from rf24 branch
|
// copied from rf24 branch
|
||||||
void TCC0_Handler() {
|
void TCC0_Handler() {
|
||||||
|
|
|
@ -201,6 +201,14 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::ackRailcomTimer() {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
bool DCCTimer::isPWMPin(byte pin) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||||
// there's no support yet for High Accuracy, so for now return false
|
// there's no support yet for High Accuracy, so for now return false
|
||||||
|
|
|
@ -39,6 +39,14 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::ackRailcomTimer() {
|
||||||
|
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||||
|
}
|
||||||
|
|
||||||
bool DCCTimer::isPWMPin(byte pin) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
//Teensy: digitalPinHasPWM, todo
|
//Teensy: digitalPinHasPWM, todo
|
||||||
(void) pin;
|
(void) pin;
|
||||||
|
|
|
@ -115,8 +115,22 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
bytes_sent = 0;
|
bytes_sent = 0;
|
||||||
bits_sent = 0;
|
bits_sent = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
||||||
|
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
||||||
|
|
||||||
|
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||||
|
if (on) {
|
||||||
|
// TODO check possible
|
||||||
|
railcomActive=true;
|
||||||
|
railcomDebug=debug;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
railcomActive=false;
|
||||||
|
railcomDebug=false;
|
||||||
|
}
|
||||||
|
return railcomActive;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
|
@ -124,16 +138,16 @@ void DCCWaveform::interrupt2() {
|
||||||
// calculate the next bit to be sent:
|
// calculate the next bit to be sent:
|
||||||
// set state WAVE_MID_1 for a 1=bit
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
// 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<4 && remainingPreambles>1;
|
||||||
if (remainingPreambles==1) promotePendingPacket();
|
if (remainingPreambles==1) promotePendingPacket();
|
||||||
|
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
|
||||||
// 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);
|
||||||
|
@ -157,6 +171,12 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +228,11 @@ void DCCWaveform::promotePendingPacket() {
|
||||||
|
|
||||||
// nothing to do, just send idles or resets
|
// nothing to do, just send idles or resets
|
||||||
// Fortunately reset and idle packets are the same length
|
// Fortunately reset and idle packets are the same length
|
||||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
// Note: If railcomDebug is on, then we send resets to the main
|
||||||
|
// track instead of idles. This means that all data will be zeros
|
||||||
|
// and only the porersets will be ones, making it much
|
||||||
|
// easier to read on a logic analyser.
|
||||||
|
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||||
transmitLength = sizeof(idlePacket);
|
transmitLength = sizeof(idlePacket);
|
||||||
transmitRepeats = 0;
|
transmitRepeats = 0;
|
||||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||||
|
@ -297,4 +321,10 @@ bool DCCWaveform::isReminderWindowOpen() {
|
||||||
void IRAM_ATTR DCCWaveform::loop() {
|
void IRAM_ATTR DCCWaveform::loop() {
|
||||||
DCCACK::checkAck(progTrack.getResets());
|
DCCACK::checkAck(progTrack.getResets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||||
|
// TODO... ESP32 railcom waveform
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -40,7 +40,14 @@ const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload s
|
||||||
|
|
||||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||||
// to the transform array.
|
// to the transform array.
|
||||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
enum WAVE_STATE : byte {
|
||||||
|
WAVE_START=0, // wave going high at start of bit
|
||||||
|
WAVE_MID_1=1, // middle of 1 bit
|
||||||
|
WAVE_HIGH_0=2, // first part of 0 bit high
|
||||||
|
WAVE_MID_0=3, // middle of 0 bit
|
||||||
|
WAVE_LOW_0=4, // first part of 0 bit low
|
||||||
|
WAVE_PENDING=5 // next bit not yet known
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: static functions are used for the overall controller, then
|
// NOTE: static functions are used for the overall controller, then
|
||||||
// one instance is created for each track.
|
// one instance is created for each track.
|
||||||
|
@ -78,6 +85,8 @@ class DCCWaveform {
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
bool isReminderWindowOpen();
|
bool isReminderWindowOpen();
|
||||||
void promotePendingPacket();
|
void promotePendingPacket();
|
||||||
|
static bool setRailcom(bool on, bool debug);
|
||||||
|
static bool isRailcom() {return railcomActive;}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
|
@ -103,6 +112,9 @@ 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 volatile bool railcomActive; // switched on by user
|
||||||
|
static volatile bool railcomDebug; // switched on by user
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
static RMTChannel *rmtMainChannel;
|
static RMTChannel *rmtMainChannel;
|
||||||
static RMTChannel *rmtProgChannel;
|
static RMTChannel *rmtProgChannel;
|
||||||
|
|
|
@ -204,7 +204,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MotorDriver::isPWMCapable() {
|
bool MotorDriver::isPWMCapable() {
|
||||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -157,12 +157,6 @@ void TrackManager::setDCCSignal( bool on) {
|
||||||
HAVE_PORTF(PORTF=shadowPORTF);
|
HAVE_PORTF(PORTF=shadowPORTF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::setCutout( bool on) {
|
|
||||||
(void) on;
|
|
||||||
// TODO Cutout needs fake ports as well
|
|
||||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
|
||||||
}
|
|
||||||
|
|
||||||
// setPROGSignal(), called from interrupt context
|
// setPROGSignal(), called from interrupt context
|
||||||
// does assume ports are shadowed if they can be
|
// does assume ports are shadowed if they can be
|
||||||
void TrackManager::setPROGSignal( bool on) {
|
void TrackManager::setPROGSignal( bool on) {
|
||||||
|
|
|
@ -57,7 +57,6 @@ class TrackManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
static void setDCCSignal( bool on);
|
static void setDCCSignal( bool on);
|
||||||
static void setCutout( bool on);
|
|
||||||
static void setPROGSignal( bool on);
|
static void setPROGSignal( bool on);
|
||||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||||
static MotorDriver * getProgDriver();
|
static MotorDriver * getProgDriver();
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.2.31"
|
#define VERSION "5.2.32"
|
||||||
|
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
|
||||||
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
||||||
// 5.2.30 - Bugfix: WiThrottle sendIntro after initial N message as well
|
// 5.2.30 - Bugfix: WiThrottle sendIntro after initial N message as well
|
||||||
// 5.2.29 - Added IO_I2CDFPlayer.h to support DFPLayer over I2C connected to NXP SC16IS750/SC16IS752 (currently only single UART for SC16IS752)
|
// 5.2.29 - Added IO_I2CDFPlayer.h to support DFPLayer over I2C connected to NXP SC16IS750/SC16IS752 (currently only single UART for SC16IS752)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user