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

reasonable start

This commit is contained in:
Asbelos 2024-02-06 20:03:52 +00:00
parent 6d0740eab4
commit cd47782052
7 changed files with 97 additions and 10 deletions

View File

@ -1035,7 +1035,13 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
case "RAILCOM"_hk:
{
bool onOff = (params > 1) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
DIAG(F("Railcom %S")
,DCCWaveform::setRailcom(onOff)?F("ON"):F("OFF"));
return true;
}
#ifndef DISABLE_PROG
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) {

View File

@ -62,6 +62,8 @@ class DCCTimer {
static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high);
static void clearPWM();
static void startRailcomTimer(byte brakePin);
static void ackRailcomTimer();
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
static void DCCEXanalogWrite(uint8_t pin, int value);

View File

@ -39,6 +39,9 @@ INTERRUPT_CALLBACK interruptHandler=0;
#define TIMER1_A_PIN 11
#define TIMER1_B_PIN 12
#define TIMER1_C_PIN 13
#define TIMER2_A_PIN 10
#define TIMER2_B_PIN 9
#else
#define TIMER1_A_PIN 9
#define TIMER1_B_PIN 10
@ -55,6 +58,53 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
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 = 436; // 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) | (1 << WGM20) | (1 << WGM21);
// Set Fast PWM mode with non-inverted output on OC2B (pin 9)
TCCR2A = (1 << WGM21) | (1 << WGM20) | (1 << COM2B1);
// 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 / 32 - 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
}
void DCCTimer::ackRailcomTimer() {
OCR2B= 0x00; // brfake pin pwm duty cycle 0 at next tick
}
// ISR called by timer interrupt every 58uS
ISR(TIMER1_OVF_vect){ interruptHandler(); }

View File

@ -115,8 +115,19 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
bytes_sent = 0;
bits_sent = 0;
}
volatile bool DCCWaveform::railcomActive=false; // switched on by user
bool DCCWaveform::setRailcom(bool on) {
if (on) {
// TODO check possible
railcomActive=true;
}
else {
railcomActive=false;
}
return railcomActive;
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
@ -124,16 +135,16 @@ void DCCWaveform::interrupt2() {
// calculate the next bit to be sent:
// set state WAVE_MID_1 for a 1=bit
// or WAVE_HIGH_0 for a 0 bit.
if (remainingPreambles > 0 ) {
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
remainingPreambles--;
// As we get to the end of the preambles, open the reminder window.
// This delays any reminder insertion until the last moment so
// 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();
// 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);
@ -157,6 +168,12 @@ 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);
}
}
}
@ -208,7 +225,9 @@ void DCCWaveform::promotePendingPacket() {
// nothing to do, just send idles or resets
// Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
// TEMPORARY DEBUG FOR RAILCOM
// memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
memcpy( transmitPacket, resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket);
transmitRepeats = 0;
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)

View File

@ -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
// 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
// one instance is created for each track.
@ -78,6 +85,8 @@ class DCCWaveform {
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
bool isReminderWindowOpen();
void promotePendingPacket();
static bool setRailcom(bool on);
static bool isRailcom() {return railcomActive;}
private:
#ifndef ARDUINO_ARCH_ESP32
@ -103,6 +112,8 @@ class DCCWaveform {
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte pendingLength;
byte pendingRepeats;
static volatile bool railcomActive; // switched on by user
#ifdef ARDUINO_ARCH_ESP32
static RMTChannel *rmtMainChannel;
static RMTChannel *rmtProgChannel;

View File

@ -204,7 +204,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
}
bool MotorDriver::isPWMCapable() {
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
}

View File

@ -157,10 +157,9 @@ void TrackManager::setDCCSignal( bool on) {
HAVE_PORTF(PORTF=shadowPORTF);
}
// Called by interrupt context
void TrackManager::setCutout( bool on) {
(void) on;
// TODO Cutout needs fake ports as well
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
APPLY_BY_MODE(TRACK_MODE_MAIN,setBrake(on,true));
}
// setPROGSignal(), called from interrupt context