mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-28 18:03:45 +02:00
Compare commits
13 Commits
v5.5.37-De
...
RailCom
Author | SHA1 | Date | |
---|---|---|---|
|
292e51afd3 | ||
|
42cda59109 | ||
|
ce892974ab | ||
|
3f57c1210d | ||
|
c9195f8035 | ||
|
7fc5c48efa | ||
|
c245c27f5d | ||
|
67adf1e6c6 | ||
|
8e71dd8926 | ||
|
955362a033 | ||
|
4391b049d8 | ||
|
7e58165db9 | ||
|
945af43500 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ Release/*
|
||||
config.h
|
||||
.vscode/extensions.json
|
||||
mySetup.h
|
||||
myFilter.cpp
|
||||
|
@@ -32,7 +32,7 @@
|
||||
#include "DIAG.h"
|
||||
#include <avr/wdt.h>
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// These keywords are used in various commands. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
@@ -56,6 +56,7 @@ const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int16_t HASH_KEYWORD_RAILCOM = -29097;
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
@@ -776,6 +777,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAILCOM: // <D RAILCOM ON/OFF>
|
||||
return DCCWaveform::setUseRailcom(onOff);
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
|
140
DCCTimer.cpp
140
DCCTimer.cpp
@@ -75,6 +75,11 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
@@ -90,24 +95,65 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
#elif defined(TEENSYDUINO)
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
bool interruptFlipflop=false;
|
||||
byte railcomPin[2]={0,0];
|
||||
enum RAILCOM_NEXT:byte {SKIP,CUT_OUT,CUT_IN);
|
||||
RAILCOM_NEXT railcom1Next[]={SKIP,SKIP};
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
|
||||
myDCCTimer.begin(interruptFast, DCC_SIGNAL_TIME/2);
|
||||
}
|
||||
|
||||
// This interrupt happens every 29uS, and alternately calls the DCC waveform
|
||||
// or handles any pending Railcom cutout pins.
|
||||
void interruptFast() {
|
||||
nterruptFlipflop=!interruptFlipflop;
|
||||
if (interruptFiliflop) {
|
||||
interruptHandler();
|
||||
return;
|
||||
}
|
||||
|
||||
// Railcom interrupt, half way between DCC interruots
|
||||
for (byte channel=0;channel<2;channel++) {
|
||||
byte pin=railcomPin[channel;
|
||||
if (pin) {
|
||||
switch (railcomNext[channel]) {
|
||||
case CUT_OUT:
|
||||
digitalWrite(pin,HIGH);
|
||||
break;
|
||||
case CUT_IN:
|
||||
digitalWrite(pin,HIGH);
|
||||
break;
|
||||
case IGNORE: break;
|
||||
}
|
||||
railcomNext[channel]=IGNORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
return true; // We are so fast we can pretend we do support this
|
||||
}
|
||||
|
||||
bool DCCTimer::isRailcomPin(byte pin) {
|
||||
(void) pin;
|
||||
if (railcomPin[0]==0) railcomPin[0]=pin;
|
||||
else if (railcomPin[1]==0) railcomPin[1]=pin;
|
||||
else return false;
|
||||
return true; // We are so fast we can pretend we do support this
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// setting pwm on a railcom pin is deferred to the next railcom interruyupt.
|
||||
for (byte channel=0;channel<2;channel++) {
|
||||
if (pin==railcomPin[channel]) {
|
||||
railcomNext[channel]=high?CUT_OUT:CUT_IN;
|
||||
return;
|
||||
}
|
||||
}
|
||||
digitalWrite(pin,high?HIGH:LOW);
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
@@ -152,7 +198,11 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
//railcom timer facility
|
||||
#define TIMER4_A_PIN 6
|
||||
#define TIMER4_B_PIN 7
|
||||
#define TIMER4_C_PIN 8
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
@@ -163,9 +213,19 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||
TCNT1 = 0;
|
||||
|
||||
#if defined(TIMER4_A_PIN)
|
||||
//railcom timer facility
|
||||
TCCR4A = 0;
|
||||
ICR4 = CLOCK_CYCLES;
|
||||
TCCR4B = _BV(WGM43) | _BV(CS40); // Mode 8, clock select 1
|
||||
TIMSK4 = 0; // Disable Software interrupt
|
||||
delayMicroseconds(DCC_SIGNAL_TIME/2);
|
||||
TCNT4 = 0; // this timer fires half cycle after Timer 1 (no idea why /4 !)
|
||||
#endif
|
||||
interrupts();
|
||||
}
|
||||
|
||||
@@ -181,20 +241,64 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
#endif
|
||||
;
|
||||
}
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isRailcomPin(byte pin) {
|
||||
return
|
||||
#ifdef TIMER4_A_PIN
|
||||
pin==TIMER4_A_PIN ||
|
||||
pin==TIMER4_B_PIN ||
|
||||
pin==TIMER4_C_PIN ||
|
||||
#endif
|
||||
false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
void DCCTimer::onoffPWM(byte pin, bool on) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
if (on)
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
else
|
||||
TCCR1A &= ~(_BV(COM1A1));
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
if (on)
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
else
|
||||
TCCR1A &= ~(_BV(COM1B1));
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
if (on)
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
else
|
||||
TCCR1A &= ~(_BV(COM1C1));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
uint16_t val=high?1024:0;
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
OCR1A= val;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
OCR1B= val;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
OCR1C= val;
|
||||
}
|
||||
#endif
|
||||
#ifdef TIMER4_A_PIN
|
||||
else if (pin==TIMER4_A_PIN) {
|
||||
TCCR4A |= _BV(COM4A1);
|
||||
OCR4A= val;
|
||||
}
|
||||
else if (pin==TIMER4_B_PIN) {
|
||||
TCCR4A |= _BV(COM4B1);
|
||||
OCR4B= val;
|
||||
}
|
||||
else if (pin==TIMER4_C_PIN) {
|
||||
TCCR4A |= _BV(COM4C1);
|
||||
OCR4C= val;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -9,7 +9,9 @@ class DCCTimer {
|
||||
static void begin(INTERRUPT_CALLBACK interrupt);
|
||||
static void getSimulatedMacAddress(byte mac[6]);
|
||||
static bool isPWMPin(byte pin);
|
||||
static bool isRailcomPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
static void onoffPWM(byte pin, bool on);
|
||||
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||
static void read_mac(byte mac[6]);
|
||||
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||
|
@@ -28,6 +28,8 @@
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::useRailcom=false;
|
||||
bool DCCWaveform::supportsRailcom=false;
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
@@ -46,8 +48,16 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||
if (MotorDriver::usePWM)
|
||||
supportsRailcom= MotorDriver::usePWM && mainDriver->isRailcomCapable() && progDriver->isRailcomCapable();
|
||||
|
||||
// supportsRailcom depends on hardware caopability
|
||||
// useRailcom is user switchable at run time.
|
||||
useRailcom=supportsRailcom;
|
||||
|
||||
if (MotorDriver::usePWM){
|
||||
DIAG(F("Signal pin config: high accuracy waveform"));
|
||||
if (supportsRailcom) DIAG(F("Railcom cutout enabled"));
|
||||
}
|
||||
else
|
||||
DIAG(F("Signal pin config: normal accuracy waveform"));
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
@@ -73,8 +83,16 @@ void DCCWaveform::interruptHandler() {
|
||||
progTrack.state=stateTransform[progTrack.state];
|
||||
|
||||
|
||||
// WAVE_START is at start of bit where we need to find
|
||||
// out if this is an railcom start or stop time
|
||||
if (useRailcom) {
|
||||
if (mainTrack.state==WAVE_START) mainTrack.railcom2();
|
||||
if (progTrack.state==WAVE_START) progTrack.railcom2();
|
||||
}
|
||||
|
||||
// WAVE_PENDING means we dont yet know what the next bit is
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
// so call interrupt2 to set it
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||
else if (progTrack.ackPending) progTrack.checkAck();
|
||||
|
||||
@@ -116,6 +134,16 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
motorDriver->setPower( ison);
|
||||
}
|
||||
|
||||
bool DCCWaveform::setUseRailcom(bool on) {
|
||||
if (!supportsRailcom) return false;
|
||||
useRailcom=on;
|
||||
if (!on) {
|
||||
// turn off any existing cutout
|
||||
mainTrack.motorDriver->setRailcomCutout(false);
|
||||
progTrack.motorDriver->setRailcomCutout(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
@@ -196,7 +224,27 @@ const bool DCCWaveform::signalTransform[]={
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
|
||||
void DCCWaveform::railcom2() {
|
||||
bool cutout;
|
||||
if (remainingPreambles==(requiredPreambles-2)) {
|
||||
cutout=true;
|
||||
} else if (remainingPreambles==(requiredPreambles-6)) {
|
||||
cutout=false;
|
||||
} else {
|
||||
return; // neiter start or end of cutout, do nothing
|
||||
}
|
||||
|
||||
if (isMainTrack) {
|
||||
if (progTrackSyncMain) // we are main track and synced so we take care of prog track as well
|
||||
progTrack.motorDriver->setRailcomCutout(cutout);
|
||||
mainTrack.motorDriver->setRailcomCutout(cutout);
|
||||
} else {
|
||||
if (!progTrackSyncMain) // we are prog track and not synced so we take care of ourselves
|
||||
progTrack.motorDriver->setRailcomCutout(cutout);
|
||||
}
|
||||
}
|
||||
|
||||
void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
// set state WAVE_MID_1 for a 1=bit
|
||||
@@ -205,9 +253,12 @@ void DCCWaveform::interrupt2() {
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
|
||||
// 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.
|
||||
updateMinimumFreeMemory(22);
|
||||
// Don't need to do that more than once per packet
|
||||
if (remainingPreambles == 3)
|
||||
updateMinimumFreeMemory(22);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -53,10 +53,14 @@ class DCCWaveform {
|
||||
static void loop(bool ackManagerActive);
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
static bool supportsRailcom;
|
||||
static bool useRailcom;
|
||||
|
||||
void beginTrack();
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
static bool setUseRailcom(bool on);
|
||||
|
||||
void checkPowerOverload(bool ackManagerActive);
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
@@ -118,6 +122,7 @@ class DCCWaveform {
|
||||
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
void railcom2();
|
||||
void checkAck();
|
||||
|
||||
bool isMainTrack;
|
||||
|
@@ -56,6 +56,10 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
setBrake(false);
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
// Initiate state of railcom cutout as off. This must be done
|
||||
// independent of if railcom is used later or not.
|
||||
setRailcomCutout(false);
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
@@ -84,6 +88,10 @@ bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
bool MotorDriver::isRailcomCapable() {
|
||||
return (!dualSignal) && DCCTimer::isRailcomPin(brakePin);
|
||||
}
|
||||
|
||||
|
||||
void MotorDriver::setPower(bool on) {
|
||||
if (on) {
|
||||
@@ -110,6 +118,11 @@ void MotorDriver::setBrake(bool on) {
|
||||
else setLOW(fastBrakePin);
|
||||
}
|
||||
|
||||
void MotorDriver::setRailcomCutout(bool on) {
|
||||
DCCTimer::onoffPWM(signalPin,!on);
|
||||
DCCTimer::setPWM(brakePin,on ^ invertBrake);
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
|
@@ -54,7 +54,9 @@ class MotorDriver {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool isRailcomCapable();
|
||||
bool canMeasureCurrent();
|
||||
void setRailcomCutout(bool on);
|
||||
static bool usePWM;
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte getFaultPin() {
|
||||
|
@@ -27,6 +27,11 @@
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Arduino standard Motor Shield with railcom (mega brakes on 6,7 require jumpers )
|
||||
#define STANDARD_WITH_RAILCOM F("STANDARD_WITH_RAILCOM"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 6, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 7, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
|
||||
@@ -42,6 +47,13 @@
|
||||
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
|
||||
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
||||
|
||||
// Pololu Dual TB9051FTG Motor Shield
|
||||
// This is the shield without modifications which means
|
||||
// no HA waveform and no RailCom on an Arduino Mega 2560
|
||||
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
|
||||
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
|
||||
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
|
@@ -18,6 +18,7 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
|
Reference in New Issue
Block a user