Skip to content

Commit

Permalink
Merge pull request #399 from DCC-EX/devel-Ash
Browse files Browse the repository at this point in the history
Update recent changes from devel branch
  • Loading branch information
Ash-4 authored Mar 8, 2024
2 parents d591309 + e972267 commit ab1a137
Show file tree
Hide file tree
Showing 28 changed files with 398 additions and 51 deletions.
5 changes: 5 additions & 0 deletions CommandDistributor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(type, F("%s"),msg);
}

void CommandDistributor::broadcastMessage(char * message) {
broadcastReply(COMMAND_TYPE, F("<m \"%s\">\n"),message);
broadcastReply(WITHROTTLE_TYPE, F("Hm%s\n"),message);
}

void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter, const FSH *modename, int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format, trackLetter, modename, dcAddr);
}
Expand Down
1 change: 1 addition & 0 deletions CommandDistributor.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public :
static void forget(byte clientId);
static void broadcastRouteState(uint16_t routeId,byte state);
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
static void broadcastMessage(char * message);

// Handling code for virtual LCD receiver.
static Print * getVirtualLCDSerial(byte screen, byte row);
Expand Down
5 changes: 5 additions & 0 deletions CommandStation-EX.ino
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
#ifdef EXRAIL_WARNING
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#endif
// compile time check, passwords 1 to 7 chars do not work, so do not try to compile with them at all
// remember trailing '\0', sizeof("") == 1.
#define PASSWDCHECK(S) static_assert(sizeof(S) == 1 || sizeof(S) > 8, "Password shorter than 8 chars")

void setup()
{
Expand Down Expand Up @@ -102,10 +105,12 @@ void setup()
// Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON
PASSWDCHECK(WIFI_PASSWORD); // compile time check
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // WIFI_ON
#else
// ESP32 needs wifi on always
PASSWDCHECK(WIFI_PASSWORD); // compile time check
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // ARDUINO_ARCH_ESP32

Expand Down
51 changes: 51 additions & 0 deletions DCC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,57 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
}
}

bool DCC::setExtendedAccessory(int16_t address, int16_t value, byte repeats) {

/* From https://www.nmra.org/sites/default/files/s-9.2.1_2012_07.pdf
The Extended Accessory Decoder Control Packet is included for the purpose of transmitting aspect control to signal
decoders or data bytes to more complex accessory decoders. Each signal head can display one aspect at a time.
{preamble} 0 10AAAAAA 0 0AAA0AA1 0 000XXXXX 0 EEEEEEEE 1
XXXXX is for a single head. A value of 00000 for XXXXX indicates the absolute stop aspect. All other aspects
represented by the values for XXXXX are determined by the signaling system used and the prototype being
modeled.
From https://normen.railcommunity.de/RCN-213.pdf:
More information is in RCN-213 about how the address bits are organized.
preamble -0- 1 0 A7 A6 A5 A4 A3 A2 -0- 0 ^A10 ^A9 ^A8 0 A1 A0 1 -0- ....
Thus in byte packet form the format is 10AAAAAA, 0AAA0AA1, 000XXXXX
Die Adresse für den ersten erweiterten Zubehördecoder ist wie bei den einfachen
Zubehördecodern die Adresse 4 = 1000-0001 0111-0001 . Diese Adresse wird in
Anwenderdialogen als Adresse 1 dargestellt.
This means that the first address shown to the user as "1" is mapped
to internal address 4.
Note that the Basic accessory format mentions "By convention these
bits (bits 4-6 of the second data byte) are in ones complement" but
this note is absent from the advanced packet description. The
english translation does not mention that the address format for
the advanced packet follows the one for the basic packet but
according to the RCN-213 this is the case.
We allow for addresses from -3 to 2047-3 as that allows to address the
whole range of the 11 bits sent to track.
*/
if ((address > 2044) || (address < -3)) return false; // 2047-3, 11 bits but offset 3
if (value != (value & 0x1F)) return false; // 5 bits

address+=3; // +3 offset according to RCN-213
byte b[3];
b[0]= 0x80 // bits always on
| ((address>>2) & 0x3F); // shift out 2, mask out used bits
b[1]= 0x01 // bits always on
| (((~(address>>8)) & 0x07)<<4) // shift out 8, invert, mask 3 bits, shift up 4
| ((address & 0x03)<<1); // mask 2 bits, shift up 1
b[2]=value;
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), repeats);
return true;
}

//
// writeCVByteMain: Write a byte with PoM on main. This writes
// the 5 byte sized packet to implement this DCC function
Expand Down
1 change: 1 addition & 0 deletions DCC.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class DCC
static uint32_t getFunctionMap(int cab);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
static bool setExtendedAccessory(int16_t address, int16_t value, byte repeats=3);
static bool writeTextPacket(byte *b, int nBytes);

// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
Expand Down
36 changes: 34 additions & 2 deletions DCCEXParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Once a new OPCODE is decided upon, update this list.
0, Track power off
1, Track power on
a, DCC accessory control
A,
A, DCC extended accessory control
b, Write CV bit on main
B, Write CV bit
c, Request current command
Expand Down Expand Up @@ -384,6 +384,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#endif
}
return;

case 'A': // EXTENDED ACCESSORY <A address value>
// Note: if this happens to match a defined EXRAIL
// DCCX_SIGNAL, then EXRAIL will have intercepted
// this command alrerady.
if (params==2 && DCC::setExtendedAccessory(p[0],p[1])) return;
break;

case 'T': // TURNOUT <T ...>
if (parseT(stream, params, p))
Expand Down Expand Up @@ -1036,7 +1043,32 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;

#if defined(HAS_ENOUGH_MEMORY) && !defined(ARDUINO_ARCH_UNO)
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;
}
#endif
#ifndef DISABLE_PROG
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) {
Expand Down
2 changes: 2 additions & 0 deletions DCCTimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
64 changes: 64 additions & 0 deletions DCCTimerAVR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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
Expand All @@ -56,6 +59,67 @@ 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.)
*/
(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);


// 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(TIMER1_OVF_vect){ interruptHandler(); }

Expand Down
9 changes: 9 additions & 0 deletions DCCTimerMEGAAVR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ extern char *__malloc_heap_start;
interruptHandler();
}

void DCCTimer::startRailcomTimer(byte brakePin) {
// TODO: for intended operation see DCCTimerAVR.cpp
(void) brakePin;
}

void DCCTimer::ackRailcomTimer() {
// TODO: for intended operation see DCCTimerAVR.cpp
}

bool DCCTimer::isPWMPin(byte pin) {
(void) pin;
return false; // TODO what are the relevant pins?
Expand Down
9 changes: 9 additions & 0 deletions DCCTimerSAMD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interrupts();
}

void DCCTimer::startRailcomTimer(byte brakePin) {
// TODO: for intended operation see DCCTimerAVR.cpp
(void) brakePin;
}

void DCCTimer::ackRailcomTimer() {
// TODO: for intended operation see DCCTimerAVR.cpp
}

// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
// copied from rf24 branch
void TCC0_Handler() {
Expand Down
9 changes: 9 additions & 0 deletions DCCTimerSTM32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interrupts();
}

void DCCTimer::startRailcomTimer(byte brakePin) {
// TODO: for intended operation see DCCTimerAVR.cpp
(void) brakePin;
}

void DCCTimer::ackRailcomTimer() {
// TODO: for intended operation see DCCTimerAVR.cpp
}

bool DCCTimer::isPWMPin(byte pin) {
//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
Expand Down
9 changes: 9 additions & 0 deletions DCCTimerTEENSY.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
}

void DCCTimer::startRailcomTimer(byte brakePin) {
// TODO: for intended operation see DCCTimerAVR.cpp
(void) brakePin;
}

void DCCTimer::ackRailcomTimer() {
// TODO: for intended operation see DCCTimerAVR.cpp
}

bool DCCTimer::isPWMPin(byte pin) {
//Teensy: digitalPinHasPWM, todo
(void) pin;
Expand Down
40 changes: 35 additions & 5 deletions DCCWaveform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,39 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
bytes_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 optimize ("-O3")
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);
Expand All @@ -157,6 +171,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);
}
}
}
Expand Down Expand Up @@ -208,7 +228,11 @@ 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));
// 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);
transmitRepeats = 0;
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
Expand Down Expand Up @@ -297,4 +321,10 @@ bool DCCWaveform::isReminderWindowOpen() {
void IRAM_ATTR DCCWaveform::loop() {
DCCACK::checkAck(progTrack.getResets());
}

bool DCCWaveform::setRailcom(bool on, bool debug) {
// TODO... ESP32 railcom waveform
return false;
}

#endif
Loading

0 comments on commit ab1a137

Please sign in to comment.