mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-13 08:58:55 +02:00
Compare commits
76 Commits
v5.2.93-De
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
78c3b44683 | ||
|
eabc3eaef0 | ||
|
7b1e396d96 | ||
|
2797ca0d1b | ||
|
a6c86bc294 | ||
|
113a01de43 | ||
|
48908c99a8 | ||
|
b8e1583b71 | ||
|
3b15491608 | ||
|
23073231ee | ||
|
3fc3c2329a | ||
|
c79e01056e | ||
|
946a784661 | ||
|
90ee8ea7d8 | ||
|
b63703a365 | ||
|
97f50910f6 | ||
|
08b8e43cd0 | ||
|
8fcc2b0083 | ||
|
978671a688 | ||
|
0effaaba87 | ||
|
8ac61b88d4 | ||
|
8aabede3ee | ||
|
839ea582a4 | ||
|
795d0edad9 | ||
|
f48f755608 | ||
|
40c30822f1 | ||
|
c7c9159a99 | ||
|
4df7df7be5 | ||
|
64f470a130 | ||
|
4125e73318 | ||
|
911bbd63be | ||
|
393b0bbd16 | ||
|
d9bd1e75f2 | ||
|
d1daf41f12 | ||
|
6bfa7028c4 | ||
|
a5d1d04882 | ||
|
bd6e426499 | ||
|
09bae44cc0 | ||
|
9f3354c687 | ||
|
fb495985f4 | ||
|
f868604ca9 | ||
|
41168a9dd8 | ||
|
0154e7fd78 | ||
|
9054d8d9f5 | ||
|
865f75dda4 | ||
|
b40fa779a6 | ||
|
2115ada2a1 | ||
|
830de850a9 | ||
|
c28965c58d | ||
|
0476b9c1d8 | ||
|
ba9ca1ccad | ||
|
c389fe9d3b | ||
|
79c30ec516 | ||
|
147fe15e04 | ||
|
b5491f9b52 | ||
|
6f1c7a9e98 | ||
|
42986c3b2d | ||
|
c1046ddcc0 | ||
|
818240b349 | ||
|
3c725afab4 | ||
|
ece2ac3ccf | ||
|
ea2e5ab8e9 | ||
|
480eb1bfde | ||
|
21dca05257 | ||
|
4e491a1e56 | ||
|
430161ef60 | ||
|
28d60d4984 | ||
|
3b162996ad | ||
|
fb414a7a50 | ||
|
818e05b425 | ||
|
c5168f030f | ||
|
387ea019bd | ||
|
a981f83bb9 | ||
|
749a859db5 | ||
|
659c58b307 | ||
|
0b9ec7460b |
@ -280,6 +280,9 @@ void CommandDistributor::broadcastPower() {
|
||||
state = '1';
|
||||
}
|
||||
|
||||
if (state != '2')
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||
|
||||
// additional info about MAIN, PROG and JOIN
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
@ -288,7 +291,7 @@ void CommandDistributor::broadcastPower() {
|
||||
const FSH * reason=F("");
|
||||
if (join) {
|
||||
reason = F(" JOIN"); // with space at start so we can append without space
|
||||
broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
|
||||
broadcastReply(COMMAND_TYPE, F("<p1%S>\n"),reason);
|
||||
} else {
|
||||
if (main) {
|
||||
//reason = F("MAIN");
|
||||
@ -299,9 +302,6 @@ void CommandDistributor::broadcastPower() {
|
||||
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (state != '2')
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||
#ifdef CD_HANDLE_RING
|
||||
// send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
|
||||
@ -323,11 +323,11 @@ void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter
|
||||
broadcastReply(COMMAND_TYPE, format, trackLetter, modename, dcAddr);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
|
||||
void CommandDistributor::broadcastRouteState(int16_t routeId, byte state ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
|
||||
void CommandDistributor::broadcastRouteCaption(int16_t routeId, const FSH* caption ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
|
||||
}
|
||||
|
||||
|
@ -59,8 +59,8 @@ public :
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter, const FSH* modename, int16_t dcAddr);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
static void broadcastRouteState(uint16_t routeId,byte state);
|
||||
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
|
||||
static void broadcastRouteState(int16_t routeId,byte state);
|
||||
static void broadcastRouteCaption(int16_t routeId,const FSH * caption);
|
||||
static void broadcastMessage(char * message);
|
||||
|
||||
// Handling code for virtual LCD receiver.
|
||||
|
@ -51,6 +51,12 @@
|
||||
|
||||
#include "DCCEX.h"
|
||||
#include "Display_Implementation.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "Sniffer.h"
|
||||
#include "DCCDecoder.h"
|
||||
Sniffer *dccSniffer = NULL;
|
||||
bool DCCDecoder::active = false;
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#ifdef CPU_TYPE_ERROR
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
|
||||
@ -124,6 +130,11 @@ void setup()
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef BOOSTER_INPUT
|
||||
dccSniffer = new Sniffer(BOOSTER_INPUT);
|
||||
#endif // BOOSTER_INPUT
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
@ -141,25 +152,28 @@ void setup()
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
/**************** for future reference
|
||||
void looptimer(unsigned long timeout, const FSH* message)
|
||||
{
|
||||
static unsigned long lasttimestamp = 0;
|
||||
unsigned long now = micros();
|
||||
if (timeout != 0) {
|
||||
unsigned long diff = now - lasttimestamp;
|
||||
if (diff > timeout) {
|
||||
DIAG(message);
|
||||
DIAG(F("DeltaT=%L"), diff);
|
||||
lasttimestamp = micros();
|
||||
return;
|
||||
}
|
||||
}
|
||||
lasttimestamp = now;
|
||||
}
|
||||
*********************************************/
|
||||
void loop()
|
||||
{
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef BOOSTER_INPUT
|
||||
static bool oldactive = false;
|
||||
if (dccSniffer) {
|
||||
bool newactive = dccSniffer->inputActive();
|
||||
if (oldactive != newactive) {
|
||||
RMFT2::railsyncEvent(newactive);
|
||||
oldactive = newactive;
|
||||
}
|
||||
DCCPacket p = dccSniffer->fetchPacket();
|
||||
if (p.len() != 0) {
|
||||
if (DCCDecoder::parse(p)) {
|
||||
if (Diag::SNIFFER)
|
||||
p.print();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // BOOSTER_INPUT
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
// The main sketch has responsibilities during loop()
|
||||
|
||||
// Responsibility 1: Handle DCC background processes
|
||||
|
50
DCC.cpp
50
DCC.cpp
@ -229,15 +229,9 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
|
||||
// Flip function state (used from withrottle protocol)
|
||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>31) return;
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
speedTable[reg].functions ^= funcmask;
|
||||
if (functionNumber <= 28) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
}
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
auto currentValue=getFn(cab,functionNumber);
|
||||
if (currentValue<0) return; // function not valid for change
|
||||
setFn(cab,functionNumber, currentValue?false:true);
|
||||
}
|
||||
|
||||
// Report function state (used from withrottle protocol)
|
||||
@ -295,7 +289,7 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
// the initial decoders were orgnized and that influenced how the DCC
|
||||
// standard was made.
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate);
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d,%d)"), address, port, gate, onoff);
|
||||
#endif
|
||||
// use masks to detect wrong values and do nothing
|
||||
if(address != (address & 511))
|
||||
@ -529,6 +523,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
||||
BAD20SKIP, // detect invalid cv20 value and ignore
|
||||
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
||||
SETCV, (ackOp)19,
|
||||
STARTMERGE, // Setup to read cv 19
|
||||
@ -634,7 +629,9 @@ const ackOp FLASH CONSIST_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV,(ackOp)20,
|
||||
SETBYTEH, // high byte to CV 20
|
||||
WB,WACK, // ignore dedcoder without cv20 support
|
||||
WB,WACK,ITSKIP,
|
||||
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||
SKIPTARGET,
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
@ -765,7 +762,15 @@ void DCC::issueReminders() {
|
||||
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
||||
// Move to next loco slot. If occupied, send a reminder.
|
||||
int reg = lastLocoReminder+1;
|
||||
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||
if (reg > highestUsedReg) {
|
||||
if (loopStatus == 0 /*only needed if numLocos == 1 but we do not have a counter*/) {
|
||||
// insert idle packet in the speed packet loop to fullfill the *censored*
|
||||
// >5ms between packets to same decoder rule
|
||||
const byte idlepacket[] = {0xFF, 0x00};
|
||||
DCCWaveform::mainTrack.schedulePacket(idlepacket, 2, 0);
|
||||
}
|
||||
reg = 0; // Go to start of table
|
||||
}
|
||||
if (speedTable[reg].loco > 0) {
|
||||
// have found loco to remind
|
||||
if (issueReminder(reg))
|
||||
@ -786,40 +791,23 @@ bool DCC::issueReminder(int reg) {
|
||||
break;
|
||||
case 1: // remind function group 1 (F0-F4)
|
||||
if (flags & FN_GROUP_1)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
|
||||
flags&= ~FN_GROUP_1; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 2: // remind function group 2 F5-F8
|
||||
if (flags & FN_GROUP_2)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_2; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 3: // remind function group 3 F9-F12
|
||||
if (flags & FN_GROUP_3)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_3; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 4: // remind function group 4 F13-F20
|
||||
if (flags & FN_GROUP_4)
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_4; // dont send them again
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),0);
|
||||
break;
|
||||
case 5: // remind function group 5 F21-F28
|
||||
if (flags & FN_GROUP_5)
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_5; // dont send them again
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),0);
|
||||
break;
|
||||
}
|
||||
loopStatus++;
|
||||
|
14
DCCACK.cpp
14
DCCACK.cpp
@ -347,6 +347,20 @@ void DCCACK::loop() {
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case BAD20SKIP:
|
||||
if (ackManagerByte > 120) {
|
||||
// skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||
if (Diag::ACK) DIAG(F("XX cv20=%d "),ackManagerByte);
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FAIL_IF_NONZERO_NAK: // fail if writing long address to decoder that cant support it
|
||||
if (ackManagerByte==0) break;
|
||||
callback(-4);
|
||||
return;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
|
2
DCCACK.h
2
DCCACK.h
@ -58,6 +58,8 @@ enum ackOp : byte
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
NAKSKIP, // skip to SKIPTARGET if ack false
|
||||
COMBINE1920, // combine cvs 19 and 20 and callback
|
||||
BAD20SKIP, // skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
|
178
DCCDecoder.cpp
Normal file
178
DCCDecoder.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCDecoder.h"
|
||||
#include "LocoTable.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
|
||||
bool DCCDecoder::parse(DCCPacket &p) {
|
||||
if (!active)
|
||||
return false;
|
||||
const byte DECODER_MOBILE = 1;
|
||||
const byte DECODER_ACCESSORY = 2;
|
||||
byte decoderType = 0; // use 0 as none
|
||||
byte *d = p.data();
|
||||
byte *instr = 0; // will be set to point to the instruction part of the DCC packet (instr[0] to instr[n])
|
||||
uint16_t addr; // will be set to decoder addr (long/shor mobile or accessory)
|
||||
bool locoInfoChanged = false;
|
||||
|
||||
if (d[0] == 0B11111111) { // Idle packet
|
||||
return false;
|
||||
}
|
||||
// CRC verification here
|
||||
byte checksum = 0;
|
||||
for (byte n = 0; n < p.len(); n++)
|
||||
checksum ^= d[n];
|
||||
if (checksum) { // Result should be zero, if not it's an error!
|
||||
if (Diag::SNIFFER) {
|
||||
DIAG(F("Checksum error"));
|
||||
p.print();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Serial.print("< ");
|
||||
for(int n=0; n<8; n++) {
|
||||
Serial.print(d[0]&(1<<n)?"1":"0");
|
||||
}
|
||||
Serial.println(" >");
|
||||
*/
|
||||
if (bitRead(d[0],7) == 0) { // bit7 == 0 => loco short addr
|
||||
decoderType = DECODER_MOBILE;
|
||||
instr = d+1;
|
||||
addr = d[0];
|
||||
} else {
|
||||
if (bitRead(d[0],6) == 1) { // bit7 == 1 and bit6 == 1 => loco long addr
|
||||
decoderType = DECODER_MOBILE;
|
||||
instr = d+2;
|
||||
addr = 256 * (d[0] & 0B00111111) + d[1];
|
||||
} else { // bit7 == 1 and bit 6 == 0
|
||||
decoderType = DECODER_ACCESSORY;
|
||||
instr = d+1;
|
||||
addr = d[0] & 0B00111111;
|
||||
}
|
||||
}
|
||||
if (decoderType == DECODER_MOBILE) {
|
||||
switch (instr[0] & 0xE0) {
|
||||
case 0x20: // 001x-xxxx Extended commands
|
||||
if (instr[0] == 0B00111111) { // 128 speed steps
|
||||
if ((locoInfoChanged = LocoTable::updateLoco(addr, instr[1])) == true) {
|
||||
byte speed = instr[1] & 0B01111111;
|
||||
byte direction = instr[1] & 0B10000000;
|
||||
DCC::setThrottle(addr, speed, direction);
|
||||
//DIAG(F("UPDATE"));
|
||||
// send speed change to DCCEX here
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x40: // 010x-xxxx 28 (or 14 step) speed we assume 28
|
||||
case 0x60: // 011x-xxxx
|
||||
if ((locoInfoChanged = LocoTable::updateLoco(addr, instr[0] & 0B00111111)) == true) {
|
||||
byte speed = instr[0] & 0B00001111; // first only look at 4 bits
|
||||
if (speed > 1) { // neither stop nor emergency stop, recalculate speed
|
||||
speed = ((instr[0] & 0B00001111) << 1) + bitRead(instr[0], 4); // reshuffle bits
|
||||
speed = (speed - 3) * 9/2;
|
||||
}
|
||||
byte direction = instr[0] & 0B00100000;
|
||||
DCC::setThrottle(addr, speed, direction);
|
||||
}
|
||||
break;
|
||||
case 0x80: // 100x-xxxx Function group 1
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 1)) == true) {
|
||||
byte normalized = (instr[0] << 1 & 0x1e) | (instr[0] >> 4 & 0x01);
|
||||
DCCEXParser::funcmap(addr, normalized, 0, 4);
|
||||
}
|
||||
break;
|
||||
case 0xA0: // 101x-xxxx Function group 3 and 2
|
||||
{
|
||||
byte low, high;
|
||||
if (bitRead(instr[0], 4)) {
|
||||
low = 5;
|
||||
high = 8;
|
||||
} else {
|
||||
low = 9;
|
||||
high = 12;
|
||||
}
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], low)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[0], low, high);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xC0: // 110x-xxxx Extended (here are functions F13 and up
|
||||
switch (instr[0] & 0B00011111) {
|
||||
case 0B00011110: // F13-F20 Function Control
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 13)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 13, 20);
|
||||
}
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[0], 17)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 13, 20);
|
||||
}
|
||||
break;
|
||||
case 0B00011111: // F21-F28 Function Control
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[1], 21)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 21, 28);
|
||||
} // updateFunc handles only the 4 low bits as that is the most common case
|
||||
if ((locoInfoChanged = LocoTable::updateFunc(addr, instr[1]>>4, 25)) == true) {
|
||||
DCCEXParser::funcmap(addr, instr[1], 21, 28);
|
||||
}
|
||||
break;
|
||||
/* do that later
|
||||
case 0B00011000: // F29-F36 Function Control
|
||||
break;
|
||||
case 0B00011001: // F37-F44 Function Control
|
||||
break;
|
||||
case 0B00011010: // F45-F52 Function Control
|
||||
break;
|
||||
case 0B00011011: // F53-F60 Function Control
|
||||
break;
|
||||
case 0B00011100: // F61-F68 Function Control
|
||||
break;
|
||||
*/
|
||||
}
|
||||
break;
|
||||
case 0xE0: // 111x-xxxx Config vars
|
||||
break;
|
||||
}
|
||||
return locoInfoChanged;
|
||||
}
|
||||
if (decoderType == DECODER_ACCESSORY) {
|
||||
if (instr[0] & 0B10000000) { // Basic Accessory
|
||||
addr = (((~instr[0]) & 0B01110000) << 2) + addr;
|
||||
byte port = (instr[0] & 0B00000110) >> 1;
|
||||
byte activate = (instr[0] & 0B00001000) >> 3;
|
||||
byte coil = (instr[0] & 0B00000001);
|
||||
locoInfoChanged = true;
|
||||
//(void)addr; (void)port; (void)coil; (void)activate;
|
||||
//DIAG(F("HL=%d LL=%d C=%d A=%d"), addr, port, coil, activate);
|
||||
DCC::setAccessory(addr, port, coil, activate);
|
||||
} else { // Accessory Extended NMRA spec, do we need to decode this?
|
||||
/*
|
||||
addr = (addr << 5) +
|
||||
((instr[0] & 0B01110000) >> 2) +
|
||||
((instr[0] & 0B00000110) >> 1);
|
||||
*/
|
||||
}
|
||||
return locoInfoChanged;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // ARDUINO_ARCH_ESP32
|
30
DCCDecoder.h
Normal file
30
DCCDecoder.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include "DCCPacket.h"
|
||||
|
||||
class DCCDecoder {
|
||||
public:
|
||||
static bool parse(DCCPacket &p);
|
||||
static inline void onoff(bool on) {active = on;};
|
||||
private:
|
||||
static bool active;
|
||||
};
|
||||
#endif // ARDUINO_ARCH_ESP32
|
@ -3,7 +3,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021-2024 Herb Morton
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2025 Harald Barth
|
||||
* © 2020-2021 M Steve Todd
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
@ -120,6 +120,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
#include "CamParser.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "WifiESP32.h"
|
||||
#include "DCCDecoder.h"
|
||||
#endif
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
@ -167,8 +168,10 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
|
||||
break;
|
||||
if (hot == '\0')
|
||||
return -1;
|
||||
if (hot == '>')
|
||||
if (hot == '>') {
|
||||
*remainingCmd = '\0'; // terminate the cmd string with 0 instead of '>'
|
||||
return parameterCount;
|
||||
}
|
||||
state = 2;
|
||||
continue;
|
||||
|
||||
@ -265,17 +268,22 @@ void DCCEXParser::parse(const FSH * cmd) {
|
||||
// See documentation on DCC class for info on this section
|
||||
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
|
||||
// found is true first after the leading "<" has been passed
|
||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND>"
|
||||
// found is true first after the leading "<" has been passed which results
|
||||
// in parseOne() getting c="C OMM AND>"
|
||||
byte *cForLater = NULL;
|
||||
bool found = (com[0] != '<');
|
||||
for (byte *c=com; c[0] != '\0'; c++) {
|
||||
if (found) {
|
||||
parseOne(stream, c, ringStream);
|
||||
cForLater = c;
|
||||
found=false;
|
||||
}
|
||||
if (c[0] == '<')
|
||||
if (c[0] == '<') {
|
||||
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||
}
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
@ -402,7 +410,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|| (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1
|
||||
) break;
|
||||
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
// Because of earlier confusion we need to do the same thing under both defines
|
||||
#if defined(DCC_ACCESSORY_COMMAND_REVERSE)
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
|
||||
#else
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1,onoff);
|
||||
@ -675,6 +684,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case 'C': // CONFIG <C [params]>
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// currently this only works on ESP32
|
||||
if (p[0] == "SNIFFER"_hk) { // <C SNIFFER ON|OFF>
|
||||
bool on = false;
|
||||
if (params>1 && p[1] == "ON"_hk) {
|
||||
on = true;
|
||||
}
|
||||
DCCDecoder::onoff(on);
|
||||
return;
|
||||
}
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
if (p[0] == "WIFI"_hk) { // <C WIFI SSID PASSWORD>
|
||||
if (params != 5) // the 5 params 0 to 4 are (kinda): WIFI_hk 0x7777 &SSID 0x7777 &PASSWORD
|
||||
@ -1226,6 +1243,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
case "LCN"_hk: // <D LCN ON/OFF>
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
case "SNIFFER"_hk: // <D SNIFFER ON/OFF>
|
||||
Diag::SNIFFER = onOff;
|
||||
return true;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
case "EEPROM"_hk: // <D EEPROM NumEntries>
|
||||
@ -1444,6 +1464,7 @@ void DCCEXParser::callback_Wloco(int16_t result)
|
||||
|
||||
void DCCEXParser::callback_Wconsist(int16_t result)
|
||||
{
|
||||
if (result==-4) DIAG(F("Long Consist %d not supported by decoder"),stashP[1]);
|
||||
if (result==1) result=stashP[1]; // pick up original requested id from command
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
||||
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
||||
|
@ -39,6 +39,7 @@ struct DCCEXParser
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
|
||||
private:
|
||||
|
||||
@ -77,7 +78,6 @@ struct DCCEXParser
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
|
||||
};
|
||||
|
83
DCCPacket.h
Normal file
83
DCCPacket.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#ifndef DCCPacket_h
|
||||
#define DCCPacket_h
|
||||
#include <strings.h>
|
||||
#include "defines.h"
|
||||
|
||||
class DCCPacket {
|
||||
public:
|
||||
DCCPacket() {
|
||||
_len = 0;
|
||||
_data = NULL;
|
||||
};
|
||||
DCCPacket(byte *d, byte l) {
|
||||
_len = l;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = d[n];
|
||||
};
|
||||
DCCPacket(const DCCPacket &old) {
|
||||
_len = old._len;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = old._data[n];
|
||||
};
|
||||
DCCPacket &operator=(const DCCPacket &rhs) {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
delete[]_data;
|
||||
_len = rhs._len;
|
||||
_data = new byte[_len];
|
||||
for (byte n = 0; n<_len; n++)
|
||||
_data[n] = rhs._data[n];
|
||||
return *this;
|
||||
};
|
||||
~DCCPacket() {
|
||||
if (_len) {
|
||||
delete[]_data;
|
||||
_len = 0;
|
||||
_data = NULL;
|
||||
}
|
||||
};
|
||||
inline bool operator==(const DCCPacket &right) {
|
||||
if (_len != right._len)
|
||||
return false;
|
||||
if (_len == 0)
|
||||
return true;
|
||||
return (bcmp(_data, right._data, _len) == 0);
|
||||
};
|
||||
void print() {
|
||||
static const char hexchars[]="0123456789ABCDEF";
|
||||
USB_SERIAL.print(F("<* DCCPACKET "));
|
||||
for (byte n = 0; n< _len; n++) {
|
||||
USB_SERIAL.print(hexchars[_data[n]>>4]);
|
||||
USB_SERIAL.print(hexchars[_data[n] & 0x0f]);
|
||||
USB_SERIAL.print(' ');
|
||||
}
|
||||
USB_SERIAL.print(F("*>\n"));
|
||||
};
|
||||
inline byte len() {return _len;};
|
||||
inline byte *data() {return _data;};
|
||||
private:
|
||||
byte _len = 0;
|
||||
byte *_data = NULL;
|
||||
};
|
||||
#endif
|
6
DCCRMT.h
6
DCCRMT.h
@ -44,6 +44,12 @@ class RMTChannel {
|
||||
return true;
|
||||
return dataReady;
|
||||
};
|
||||
inline void waitForDataCopy() {
|
||||
while(1) { // do nothing and wait for interrupt clearing dataReady to happen
|
||||
if (dataReady == false)
|
||||
break;
|
||||
}
|
||||
};
|
||||
inline uint32_t packetCount() { return packetCounter; };
|
||||
|
||||
private:
|
||||
|
@ -61,6 +61,7 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
/* 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)
|
||||
@ -113,10 +114,13 @@ void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// it does seems to get a good answer.
|
||||
|
||||
TCNT2=193 + (ICR1 - TCNT1)/8;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
OCR2B= 0x00; // brake pin pwm duty cycle 0 at next tick
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -78,11 +78,17 @@ int DCCTimer::freeMemory() {
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#if __has_include("esp_idf_version.h")
|
||||
#include "esp_idf_version.h"
|
||||
#if ESP_IDF_VERSION_MAJOR > 4
|
||||
#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDE version 4. Arduino ESP32 library 3.0.0 is too new. Downgrade to one of 2.0.9 to 2.0.17"
|
||||
#endif
|
||||
#if ESP_IDF_VERSION_MAJOR == 4
|
||||
// all well correct IDF version
|
||||
#else
|
||||
#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDF version 4. Arduino ESP32 library 3.0.0 is too new. Downgrade to one of 2.0.9 to 2.0.17"
|
||||
#endif
|
||||
|
||||
// protect all the rest of the code from IDF version 5
|
||||
#if ESP_IDF_VERSION_MAJOR == 4
|
||||
#include "DIAG.h"
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
@ -322,5 +328,5 @@ void ADCee::scan() {
|
||||
|
||||
void ADCee::begin() {
|
||||
}
|
||||
|
||||
#endif //IDF v4
|
||||
#endif //ESP32
|
||||
|
@ -145,7 +145,7 @@ void DCCWaveform::interrupt2() {
|
||||
// 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;
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<12 && 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.
|
||||
@ -278,7 +278,11 @@ void DCCWaveform::begin() {
|
||||
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
|
||||
RMTChannel *rmtchannel = (isMainTrack ? rmtMainChannel : rmtProgChannel);
|
||||
if (rmtchannel == NULL)
|
||||
return; // no idea to prepare packet if we can not send it anyway
|
||||
|
||||
rmtchannel->waitForDataCopy(); // blocking wait so we can write into buffer
|
||||
byte checksum = 0;
|
||||
for (byte b = 0; b < byteCount; b++) {
|
||||
checksum ^= buffer[b];
|
||||
@ -296,13 +300,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
{
|
||||
int ret = 0;
|
||||
do {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel != NULL)
|
||||
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
} else {
|
||||
if (rmtProgChannel != NULL)
|
||||
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
}
|
||||
ret = rmtchannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
} while(ret > 0);
|
||||
}
|
||||
}
|
||||
|
20
EXRAIL.h
20
EXRAIL.h
@ -1,3 +1,23 @@
|
||||
/*
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EXRAIL_H
|
||||
#define EXRAIL_H
|
||||
|
||||
|
44
EXRAIL2.cpp
44
EXRAIL2.cpp
@ -4,6 +4,7 @@
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2025 Morten Nielsen
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@ -87,6 +88,10 @@ LookList * RMFT2::onClockLookup=NULL;
|
||||
LookList * RMFT2::onRotateLookup=NULL;
|
||||
#endif
|
||||
LookList * RMFT2::onOverloadLookup=NULL;
|
||||
#ifdef BOOSTER_INPUT
|
||||
LookList * RMFT2::onRailSyncOnLookup=NULL;
|
||||
LookList * RMFT2::onRailSyncOffLookup=NULL;
|
||||
#endif
|
||||
byte * RMFT2::routeStateArray=nullptr;
|
||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||
int16_t * RMFT2::stashArray=nullptr;
|
||||
@ -203,6 +208,10 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
||||
#endif
|
||||
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
||||
#ifdef BOOSTER_INPUT
|
||||
onRailSyncOnLookup=LookListLoader(OPCODE_ONRAILSYNCON);
|
||||
onRailSyncOffLookup=LookListLoader(OPCODE_ONRAILSYNCOFF);
|
||||
#endif
|
||||
// onLCCLookup is not the same so not loaded here.
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
@ -871,6 +880,14 @@ void RMFT2::loop2() {
|
||||
DCC::changeFn(operand,getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_XFWD:
|
||||
DCC::setThrottle(operand,getOperand(1), true);
|
||||
break;
|
||||
|
||||
case OPCODE_XREV:
|
||||
DCC::setThrottle(operand,getOperand(1), false);
|
||||
break;
|
||||
|
||||
case OPCODE_DCCACTIVATE: {
|
||||
// operand is address<<3 | subaddr<<1 | active
|
||||
int16_t addr=operand>>3;
|
||||
@ -922,8 +939,9 @@ void RMFT2::loop2() {
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case OPCODE_JOIN:
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(POWERMODE::ON);
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
@ -1111,7 +1129,11 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONROTATE:
|
||||
#endif
|
||||
case OPCODE_ONOVERLOAD:
|
||||
|
||||
#ifdef BOOSTER_INPUT
|
||||
case OPCODE_ONRAILSYNCON:
|
||||
case OPCODE_ONRAILSYNCOFF:
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1333,7 +1355,19 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||
onOverloadLookup->handleEvent(F("POWER"),track);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BOOSTER_INPUT
|
||||
void RMFT2::railsyncEvent(bool on) {
|
||||
if (Diag::CMD)
|
||||
DIAG(F("railsyncEvent : %d"), on);
|
||||
if (on) {
|
||||
if (onRailSyncOnLookup)
|
||||
onRailSyncOnLookup->handleEvent(F("RAILSYNCON"), 0);
|
||||
} else {
|
||||
if (onRailSyncOffLookup)
|
||||
onRailSyncOffLookup->handleEvent(F("RAILSYNCOFF"), 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// This function is used when setting pins so that a SET or RESET
|
||||
// will cause any blink task on that pin to terminate.
|
||||
// It will be compiled out of existence if no BLINK feature is used.
|
||||
@ -1479,7 +1513,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::manageRouteState(uint16_t id, byte state) {
|
||||
void RMFT2::manageRouteState(int16_t id, byte state) {
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
// Route state must be maintained for when new throttles connect.
|
||||
// locate route id in the Routes lookup
|
||||
@ -1491,7 +1525,7 @@ void RMFT2::manageRouteState(uint16_t id, byte state) {
|
||||
CommandDistributor::broadcastRouteState(id,state);
|
||||
}
|
||||
}
|
||||
void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) {
|
||||
void RMFT2::manageRouteCaption(int16_t id,const FSH* caption) {
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
// Route state must be maintained for when new throttles connect.
|
||||
// locate route id in the Routes lookup
|
||||
|
16
EXRAIL2.h
16
EXRAIL2.h
@ -3,6 +3,7 @@
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* © 2025 Morten Nielsen
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@ -45,7 +46,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_ENDIF,OPCODE_ELSE,
|
||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,
|
||||
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,OPCODE_XFWD,OPCODE_XREV,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
@ -72,6 +73,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_ACON, OPCODE_ACOF,
|
||||
OPCODE_ONACON, OPCODE_ONACOF,
|
||||
OPCODE_ONOVERLOAD,
|
||||
OPCODE_ONRAILSYNCON,OPCODE_ONRAILSYNCOFF,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
OPCODE_ROUTE_DISABLED,
|
||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||
@ -187,6 +189,9 @@ class LookList {
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static void rotateEvent(int16_t id, bool change);
|
||||
static void powerEvent(int16_t track, bool overload);
|
||||
#ifdef BOOSTER_INPUT
|
||||
static void railsyncEvent(bool on);
|
||||
#endif
|
||||
static bool signalAspectEvent(int16_t address, byte aspect );
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
@ -254,12 +259,15 @@ private:
|
||||
static LookList * onRotateLookup;
|
||||
#endif
|
||||
static LookList * onOverloadLookup;
|
||||
|
||||
#ifdef BOOSTER_INPUT
|
||||
static LookList * onRailSyncOnLookup;
|
||||
static LookList * onRailSyncOffLookup;
|
||||
#endif
|
||||
static const int countLCCLookup;
|
||||
static int onLCCLookup[];
|
||||
static const byte compileFeatures;
|
||||
static void manageRouteState(uint16_t id, byte state);
|
||||
static void manageRouteCaption(uint16_t id, const FSH* caption);
|
||||
static void manageRouteState(int16_t id, byte state);
|
||||
static void manageRouteCaption(int16_t id, const FSH* caption);
|
||||
static byte * routeStateArray;
|
||||
static const FSH ** routeCaptionArray;
|
||||
static int16_t * stashArray;
|
||||
|
@ -2,6 +2,7 @@
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* © 2025 Morten Nielsen
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@ -118,6 +119,8 @@
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
#undef ONOVERLOAD
|
||||
#undef ONRAILSYNCON
|
||||
#undef ONRAILSYNCOFF
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONROTATE
|
||||
@ -194,6 +197,8 @@
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
#undef XFTOGGLE
|
||||
#undef XREV
|
||||
#undef XFWD
|
||||
|
||||
#ifndef RMFT2_UNDEF_ONLY
|
||||
#define ACTIVATE(addr,subaddr)
|
||||
@ -214,7 +219,7 @@
|
||||
#define BROADCAST(msg)
|
||||
#define CALL(route)
|
||||
#define CLEAR_STASH(id)
|
||||
#define CLEAR_ALL_STASH(id)
|
||||
#define CLEAR_ALL_STASH
|
||||
#define CLOSE(id)
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
@ -285,6 +290,8 @@
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONCLOCKMINS(mins)
|
||||
#define ONOVERLOAD(track_id)
|
||||
#define ONRAILSYNCON
|
||||
#define ONRAILSYNCOFF
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
@ -365,5 +372,7 @@
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
#define XFTOGGLE(cab,func)
|
||||
#define XFWD(cab,speed)
|
||||
#define XREV(cab,speed)
|
||||
|
||||
#endif
|
||||
|
@ -3,6 +3,7 @@
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* © 2025 Morten Nielsen
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@ -583,6 +584,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||
#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(TRACK_NUMBER_##track_id),
|
||||
#define ONRAILSYNCON OPCODE_ONRAILSYNCON,0,0,
|
||||
#define ONRAILSYNCOFF OPCODE_ONRAILSYNCOFF,0,0,
|
||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||
@ -665,6 +668,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFWD(cab,speed) OPCODE_XFWD,V(cab),OPCODE_PAD,V(speed),
|
||||
#define XREV(cab,speed) OPCODE_XREV,V(cab),OPCODE_PAD,V(speed),
|
||||
|
||||
// Build RouteCode
|
||||
const int StringMacroTracker2=__COUNTER__;
|
||||
|
@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202412222121Z"
|
||||
#define GITHUB_SHA "devel-202504182148Z"
|
||||
|
130
LocoTable.cpp
Normal file
130
LocoTable.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/* Copyright (c) 2023 Harald Barth
|
||||
*
|
||||
* This source is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "LocoTable.h"
|
||||
|
||||
LocoTable::LOCO LocoTable::speedTable[MAX_LOCOS] = { {0,0,0,0,0,0} };
|
||||
int LocoTable::highestUsedReg = 0;
|
||||
|
||||
int LocoTable::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
// determine speed reg for this loco
|
||||
int firstEmpty = MAX_LOCOS;
|
||||
int reg;
|
||||
for (reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
if (speedTable[reg].loco == locoId) break;
|
||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||
}
|
||||
|
||||
// return -1 if not found and not auto creating
|
||||
if (reg == MAX_LOCOS && !autoCreate) return -1;
|
||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||
if (reg >= MAX_LOCOS) {
|
||||
//DIAG(F("Too many locos"));
|
||||
return -1;
|
||||
}
|
||||
if (reg==firstEmpty){
|
||||
speedTable[reg].loco = locoId;
|
||||
speedTable[reg].speedCode=128; // default direction forward
|
||||
speedTable[reg].groupFlags=0;
|
||||
speedTable[reg].functions=0;
|
||||
}
|
||||
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
// returns false only if loco existed but nothing was changed
|
||||
bool LocoTable::updateLoco(int loco, byte speedCode) {
|
||||
if (loco==0) {
|
||||
/*
|
||||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco==0) continue;
|
||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
if (speedTable[reg].speedCode != newspeed) {
|
||||
speedTable[reg].speedCode = newspeed;
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// determine speed reg for this loco
|
||||
int reg=lookupSpeedTable(loco, false);
|
||||
if (reg>=0) {
|
||||
speedTable[reg].speedcounter++;
|
||||
if (speedTable[reg].speedCode!=speedCode) {
|
||||
speedTable[reg].speedCode = speedCode;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// new
|
||||
reg=lookupSpeedTable(loco, true);
|
||||
if(reg >=0) speedTable[reg].speedCode = speedCode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool LocoTable::updateFunc(int loco, byte func, int shift) {
|
||||
unsigned long previous;
|
||||
unsigned long newfunc;
|
||||
bool retval = false; // nothing was touched
|
||||
int reg = lookupSpeedTable(loco, false);
|
||||
if (reg < 0) { // not found
|
||||
retval = true;
|
||||
reg = lookupSpeedTable(loco, true);
|
||||
newfunc = previous = 0;
|
||||
} else {
|
||||
newfunc = previous = speedTable[reg].functions;
|
||||
}
|
||||
|
||||
speedTable[reg].funccounter++;
|
||||
|
||||
if(shift == 1) { // special case for light
|
||||
newfunc &= ~1UL;
|
||||
newfunc |= ((func & 0B10000) >> 4);
|
||||
}
|
||||
newfunc &= ~(0B1111UL << shift);
|
||||
newfunc |= ((func & 0B1111) << shift);
|
||||
|
||||
if (newfunc != previous) {
|
||||
speedTable[reg].functions = newfunc;
|
||||
retval = true;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void LocoTable::dumpTable(Stream *output) {
|
||||
output->print("\n-----------Table---------\n");
|
||||
for (byte reg = 0; reg <= highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco != 0) {
|
||||
output->print(speedTable[reg].loco);
|
||||
output->print(' ');
|
||||
output->print(speedTable[reg].speedCode);
|
||||
output->print(' ');
|
||||
output->print(speedTable[reg].functions);
|
||||
output->print(" #funcpacks:");
|
||||
output->print(speedTable[reg].funccounter);
|
||||
output->print(" #speedpacks:");
|
||||
output->print(speedTable[reg].speedcounter);
|
||||
speedTable[reg].funccounter = 0;
|
||||
speedTable[reg].speedcounter = 0;
|
||||
output->print('\n');
|
||||
}
|
||||
}
|
||||
}
|
44
LocoTable.h
Normal file
44
LocoTable.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* Copyright (c) 2023 Harald Barth
|
||||
*
|
||||
* This source is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCC.h" // fetch MAX_LOCOS from there
|
||||
|
||||
class LocoTable {
|
||||
public:
|
||||
void forgetLoco(int cab) {
|
||||
int reg=lookupSpeedTable(cab, false);
|
||||
if (reg>=0) speedTable[reg].loco=0;
|
||||
}
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate);
|
||||
static bool updateLoco(int loco, byte speedCode);
|
||||
static bool updateFunc(int loco, byte func, int shift);
|
||||
static void dumpTable(Stream *output);
|
||||
|
||||
private:
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
unsigned int funccounter;
|
||||
unsigned int speedcounter;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int highestUsedReg;
|
||||
};
|
82
README.md
82
README.md
@ -1,77 +1,39 @@
|
||||
# What is DCC++ EX?
|
||||
DCC++ EX is the organization maintaining several codebases that together represent a fully open source DCC system. Currently, this includes the following:
|
||||
# What is DCC-EX?
|
||||
DCC-EX is a team of dedicated enthusiasts producing open source DCC & DC solutions for you to run your complete model railroad layout. Our easy to use, do-it-yourself, and free open source products run on off-the-shelf Arduino technology and are supported by numerous third party hardware and apps like JMRI, Engine Driver, wiThrottle, Rocrail and more.
|
||||
|
||||
* [CommandStation-EX](https://github.com/DCC-EX/CommandStation-EX/releases) - the latest take on the DCC++ command station for controlling your trains. Runs on an Arduino board, and includes advanced features such as a WiThrottle server implementation, turnout operation, general purpose inputs and outputs (I/O), and JMRI integration.
|
||||
* [exWebThrottle](https://github.com/DCC-EX/exWebThrottle) - a simple web based controller for your DCC++ command station.
|
||||
* [BaseStation-installer](https://github.com/DCC-EX/BaseStation-Installer) - an installer executable that takes care of downloading and installing DCC++ firmware onto your hardware setup.
|
||||
* [BaseStation-Classic](https://github.com/DCC-EX/BaseStation-Classic) - the original DCC++ software, packaged in a stable release. No active development, bug fixes only.
|
||||
Currently, our products include the following:
|
||||
|
||||
A basic DCC++ EX hardware setup can use easy to find, widely avalable Arduino boards that you can assemble yourself.
|
||||
|
||||
Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital Command Control (DCC) [standards](http://www.nmra.org/dcc-working-group "NMRA DCC Working Group"), including:
|
||||
|
||||
* simultaneous control of multiple locomotives
|
||||
* 2-byte and 4-byte locomotive addressing
|
||||
* 28 or 128-step speed throttling
|
||||
* Activate/de-activate all accessory function addresses 0-2048
|
||||
* Control of all cab functions F0-F28 and F29-F68
|
||||
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
|
||||
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
||||
* And many more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
|
||||
* [EX-CommandStation](https://github.com/DCC-EX/CommandStation-EX/releases)
|
||||
* [EX-WebThrottle](https://github.com/DCC-EX/exWebThrottle)
|
||||
* [EX-Installer](https://github.com/DCC-EX/EX-Installer)
|
||||
* [EX-MotoShield8874](https://dcc-ex.com/reference/hardware/motorboards/ex-motor-shield-8874.html#gsc.tab=0)
|
||||
* [EX-DCCInspector](https://github.com/DCC-EX/DCCInspector-EX)
|
||||
* [EX-Toolbox](https://github.com/DCC-EX/EX-Toolbox)
|
||||
* [EX-Turntable](https://github.com/DCC-EX/EX-Turntable)
|
||||
* [EX-IOExpander](https://github.com/DCC-EX/EX-IOExpander)
|
||||
* [EX-FastClock](https://github.com/DCC-EX/EX-FastClock)
|
||||
* [DCCEXProtocol](https://github.com/DCC-EX/DCCEXProtocol)
|
||||
|
||||
Details of these projects can be found on [our web site](https://dcc-ex.com/).
|
||||
|
||||
# What’s in this Repository?
|
||||
|
||||
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
|
||||
This repository, CommandStation-EX, contains a complete DCC-EX *EX-CommmandStation* sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
|
||||
|
||||
To utilize this sketch, you can use the following:
|
||||
|
||||
1. (beginner) our [automated installer](https://github.com/DCC-EX/BaseStation-Installer)
|
||||
1. (recommended for all levels of user) our [automated installer](https://github.com/DCC-EX/EX-Installer)
|
||||
2. (intermediate) download the latest version from the [releases page](https://github.com/DCC-EX/CommandStation-EX/releases)
|
||||
3. (advanced) use git clone on this repository
|
||||
|
||||
Not using the installer? Open the file "CommandStation-EX.ino" in the
|
||||
Arduino IDE. Please do not rename the folder containing the sketch
|
||||
code, nor add any files in that folder. The Arduino IDE relies on the
|
||||
structure and name of the folder to properly display and compile the
|
||||
code. Rename or copy config.example.h to config.h. If you do not have
|
||||
the standard setup, you must edit config.h according to the help texts
|
||||
in config.h.
|
||||
Refer to [our web site](https://https://dcc-ex.com/ex-commandstation/get-started/index.html#/) for the hardware required for this project.
|
||||
|
||||
## What's new in CommandStation-EX?
|
||||
**We seriously recommend using the EX-Installer**, however if you choose not to use the installer...
|
||||
|
||||
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station (or through JMRI as before)
|
||||
* WiFi and Ethernet shield support
|
||||
* No more jumpers or soldering!
|
||||
* Direct support for all the most popular motor control boards including single pin (Arduino) or dual pin (IBT_2) type PWM inputs without the need for an adapter circuit
|
||||
* I2C Display support (LCD and OLED)
|
||||
* Improved short circuit detection and automatic reset from an overload
|
||||
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
|
||||
* Improved adherence to the NMRA DCC specification
|
||||
* Complete support for all the old commands and front ends like JMRI
|
||||
* Railcom cutout (beta)
|
||||
* Simpler, modular, faster code with an API Library for developers for easy expansion
|
||||
* New features and functions in JMRI
|
||||
* Ability to join MAIN and PROG tracks into one MAIN track to run your locos
|
||||
* "Drive-Away" feature - Throttles with support, like Engine Driver, can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
* Diagnostic commands to test decoders that aren't reading or writing correctly
|
||||
* Support for Uno, Nano, Mega, Nano Every and Teensy microcontrollers
|
||||
* User Functions: Filter regular commands (like a turnout or output command) and pass it to your own function or accessory
|
||||
* Support for LCN (layout control nodes)
|
||||
* mySetup.h file that acts like an Autoexec.Bat command to send startup commands to the CS
|
||||
* High Accuracty Waveform option for rock steady DCC signals
|
||||
* New current response outputs current in mA, overlimit current, and maximum board capable current. Support for new current meter in JMRI
|
||||
* USB Browser based EX-WebThrottle
|
||||
* New, simpler, function control command
|
||||
* Number of locos discovery command `<#>`
|
||||
* Emergency stop command <!>
|
||||
* Release cabs from memory command <-> all cabs, <- CAB> for just one loco address
|
||||
* Automatic slot (register) management
|
||||
* Automation (coming soon)
|
||||
|
||||
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means, you can read [HERE](https://dcc-ex.com/about/rewrite.html).
|
||||
* Open the file ``CommandStation-EX.ino`` in the Arduino IDE or Visual Studio Code (VSC). Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code.
|
||||
* Rename or copy ``config.example.h`` to ``config.h``.
|
||||
* You must edit ``config.h`` according to the help texts in ``config.h``.
|
||||
|
||||
# More information
|
||||
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
|
||||
You can learn more at the [DCC-EX website](https://dcc-ex.com/)
|
||||
|
||||
- November 14, 2020
|
||||
|
BIN
Release_Notes/DCC-EX v5.4 Release Notes.xlsx
Normal file
BIN
Release_Notes/DCC-EX v5.4 Release Notes.xlsx
Normal file
Binary file not shown.
@ -126,29 +126,33 @@ void SerialManager::loop2() {
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
} else { // if (inCommandPayload)
|
||||
if (bufferLength < (COMMAND_BUFFER_SIZE-1))
|
||||
buffer[bufferLength++] = ch;
|
||||
if (inCommandPayload > PAYLOAD_NORMAL) {
|
||||
if (inCommandPayload > 32 + 2) { // String way too long
|
||||
ch = '>'; // we end this nonsense
|
||||
inCommandPayload = PAYLOAD_NORMAL;
|
||||
DIAG(F("Parse error: Unbalanced string"));
|
||||
// fall through to ending parsing below
|
||||
} else if (ch == '"') { // String end
|
||||
inCommandPayload = PAYLOAD_NORMAL;
|
||||
continue; // do not fall through
|
||||
} else
|
||||
inCommandPayload++;
|
||||
}
|
||||
if (inCommandPayload == PAYLOAD_NORMAL) {
|
||||
if (ch == '>') {
|
||||
buffer[bufferLength] = '\0';
|
||||
DCCEXParser::parse(serial, buffer, NULL);
|
||||
inCommandPayload = PAYLOAD_FALSE;
|
||||
break;
|
||||
} else if (ch == '"') {
|
||||
inCommandPayload = PAYLOAD_STRING;
|
||||
}
|
||||
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) {
|
||||
buffer[bufferLength++] = ch; // advance bufferLength
|
||||
if (inCommandPayload > PAYLOAD_NORMAL) {
|
||||
if (inCommandPayload > 32 + 2) { // String way too long
|
||||
ch = '>'; // we end this nonsense
|
||||
inCommandPayload = PAYLOAD_NORMAL;
|
||||
DIAG(F("Parse error: Unbalanced string"));
|
||||
// fall through to ending parsing below
|
||||
} else if (ch == '"') { // String end
|
||||
inCommandPayload = PAYLOAD_NORMAL;
|
||||
continue; // do not fall through
|
||||
} else
|
||||
inCommandPayload++;
|
||||
}
|
||||
if (inCommandPayload == PAYLOAD_NORMAL) {
|
||||
if (ch == '>') {
|
||||
buffer[bufferLength] = '\0'; // This \0 is after the '>'
|
||||
DCCEXParser::parse(serial, buffer, NULL); // buffer parsed with trailing '>'
|
||||
inCommandPayload = PAYLOAD_FALSE;
|
||||
break;
|
||||
} else if (ch == '"') {
|
||||
inCommandPayload = PAYLOAD_STRING;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DIAG(F("Parse error: input buffer overflow"));
|
||||
inCommandPayload = PAYLOAD_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
241
Sniffer.cpp
Normal file
241
Sniffer.cpp
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "Sniffer.h"
|
||||
#include "DIAG.h"
|
||||
//extern Sniffer *DCCSniffer;
|
||||
|
||||
static void packeterror() {
|
||||
#ifndef WIFI_LED
|
||||
#ifdef SNIFFER_LED
|
||||
digitalWrite(SNIFFER_LED,HIGH);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static void clear_packeterror() {
|
||||
#ifndef WIFI_LED
|
||||
#ifdef SNIFFER_LED
|
||||
digitalWrite(SNIFFER_LED,LOW);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool halfbits2byte(uint16_t b, byte *dccbyte) {
|
||||
/*
|
||||
if (b!=0 && b!=0xFFFF) {
|
||||
Serial.print("[ ");
|
||||
for(int n=0; n<16; n++) {
|
||||
Serial.print(b&(1<<n)?"1":"0");
|
||||
}
|
||||
Serial.println(" ]");
|
||||
}
|
||||
*/
|
||||
for(byte n=0; n<8; n++) {
|
||||
switch (b & 0x03) {
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
// broken bits
|
||||
packeterror();
|
||||
return false;
|
||||
break;
|
||||
case 0x00:
|
||||
bitClear(*dccbyte, n);
|
||||
break;
|
||||
case 0x03:
|
||||
bitSet(*dccbyte, n);
|
||||
break;
|
||||
}
|
||||
b = b>>2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR blink_diag(int limit) {
|
||||
#ifndef WIFI_LED
|
||||
#ifdef SNIFFER_LED
|
||||
delay(500);
|
||||
for (int n=0 ; n<limit; n++) {
|
||||
digitalWrite(SNIFFER_LED,HIGH);
|
||||
delay(200);
|
||||
digitalWrite(SNIFFER_LED,LOW);
|
||||
delay(200);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR cap_ISR_cb(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,void *user_data) {
|
||||
if (edata->cap_edge == MCPWM_BOTH_EDGE) {
|
||||
// should not happen at all
|
||||
// delays here might crash sketch
|
||||
blink_diag(2);
|
||||
return 0;
|
||||
}
|
||||
if (user_data) ((Sniffer *)user_data)->processInterrupt(edata->cap_value, edata->cap_edge == MCPWM_POS_EDGE);
|
||||
//if (DCCSniffer) DCCSniffer->processInterrupt(edata->cap_value, edata->cap_edge == MCPWM_POS_EDGE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sniffer::Sniffer(byte snifferpin) {
|
||||
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, snifferpin);
|
||||
// set capture edge, BIT(0) - negative edge, BIT(1) - positive edge
|
||||
// MCPWM_POS_EDGE|MCPWM_NEG_EDGE should be 3.
|
||||
//mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE|MCPWM_NEG_EDGE, 0);
|
||||
//mcpwm_isr_register(MCPWM_UNIT_0, sniffer_isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
//MCPWM0.int_ena.cap0_int_ena = 1; // Enable interrupt on CAP0 signal
|
||||
|
||||
mcpwm_capture_config_t MCPWM_cap_config = { //Capture channel configuration
|
||||
.cap_edge = MCPWM_BOTH_EDGE, // according to mcpwm.h
|
||||
.cap_prescale = 1, // 1 to 256 (see .h file)
|
||||
.capture_cb = cap_ISR_cb, // user defined ISR/callback
|
||||
.user_data = (void *)this // user defined argument to callback
|
||||
};
|
||||
#ifndef WIFI_LED
|
||||
#ifdef SNIFFER_LED
|
||||
pinMode(SNIFFER_LED ,OUTPUT);
|
||||
#endif
|
||||
#endif
|
||||
blink_diag(3); // so that we know we have SNIFFER_LED
|
||||
DIAG(F("Init sniffer on pin %d"), snifferpin);
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, &MCPWM_cap_config));
|
||||
}
|
||||
|
||||
#define SNIFFER_TIMEOUT 100L // 100 Milliseconds
|
||||
bool Sniffer::inputActive(){
|
||||
unsigned long now = millis();
|
||||
return ((now - lastendofpacket) < SNIFFER_TIMEOUT);
|
||||
}
|
||||
|
||||
#define DCC_TOO_SHORT 4000L // 4000 ticks are 50usec
|
||||
#define DCC_ONE_LIMIT 6400L // 6400 ticks are 80usec
|
||||
|
||||
void IRAM_ATTR Sniffer::processInterrupt(int32_t capticks, bool posedge) {
|
||||
byte bit = 0;
|
||||
diffticks = capticks - lastticks;
|
||||
if (lastedge != posedge) {
|
||||
if (diffticks < DCC_TOO_SHORT) {
|
||||
return;
|
||||
}
|
||||
if (diffticks < DCC_ONE_LIMIT) {
|
||||
bit = 1;
|
||||
} else {
|
||||
bit = 0;
|
||||
}
|
||||
// update state variables for next round
|
||||
lastticks = capticks;
|
||||
lastedge = posedge;
|
||||
bitfield = bitfield << (uint64_t)1;
|
||||
bitfield = bitfield + (uint64_t)bit;
|
||||
|
||||
// now the halfbit is in the bitfield. Analyze...
|
||||
|
||||
if ((bitfield & 0xFFFFFF) == 0xFFFFFC){
|
||||
// This looks at the 24 last halfbits
|
||||
// and detects a preamble if
|
||||
// 22 are ONE and 2 are ZERO which is a
|
||||
// preabmle of 11 ONES and one ZERO
|
||||
if (inpacket) {
|
||||
// if we are already inpacket here we
|
||||
// got a preamble in the middle of a
|
||||
// packet
|
||||
packeterror();
|
||||
} else {
|
||||
clear_packeterror(); // everything fine again at end of preable after good packet
|
||||
}
|
||||
currentbyte = 0;
|
||||
dcclen = 0;
|
||||
inpacket = true;
|
||||
halfbitcounter = 18; // count 18 steps from 17 to 0 and then look at the byte
|
||||
return;
|
||||
}
|
||||
if (inpacket) {
|
||||
halfbitcounter--;
|
||||
if (halfbitcounter) {
|
||||
return; // wait until we have full byte
|
||||
} else {
|
||||
// have reached end of byte
|
||||
//if (currentbyte == 2) debugfield = bitfield;
|
||||
byte twohalfbits = bitfield & 0x03;
|
||||
switch (twohalfbits) {
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
// broken bits
|
||||
inpacket = false;
|
||||
packeterror();
|
||||
return;
|
||||
break;
|
||||
case 0x00:
|
||||
case 0x03:
|
||||
// byte end
|
||||
uint16_t b = (bitfield & 0x3FFFF)>>2; // take 18 halfbits and use 16 of them
|
||||
if (!halfbits2byte(b, dccbytes + currentbyte)) {
|
||||
// broken halfbits
|
||||
inpacket = false;
|
||||
packeterror();
|
||||
return;
|
||||
}
|
||||
if (twohalfbits == 0x03) { // end of packet marker
|
||||
inpacket = false;
|
||||
dcclen = currentbyte+1;
|
||||
debugfield = bitfield;
|
||||
// We have something we want to give to the outpacket queue
|
||||
// Check length of outpacket queue
|
||||
if (outpacket.size() > 3) {
|
||||
// not good, these should have been fetched
|
||||
// the arbitraty number to check is THREE (see the holy grail)
|
||||
// blink_diag(1); DO NOT DO THIS HERE -> will crash
|
||||
packeterror(); // or what to do better?
|
||||
// take emergency action:
|
||||
while (!outpacket.empty()) {
|
||||
outpacket.pop_front();
|
||||
}
|
||||
}
|
||||
lastendofpacket = millis();
|
||||
DCCPacket temppacket(dccbytes, dcclen);
|
||||
if (!(temppacket == prevpacket)) {
|
||||
// we have something new to offer to the fetch routine
|
||||
// put it into the outpacket queue
|
||||
outpacket.push_back(temppacket);
|
||||
prevpacket = temppacket;
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
halfbitcounter = 18;
|
||||
currentbyte++; // everything done for this end of byte
|
||||
if (currentbyte >= MAXDCCPACKETLEN) {
|
||||
inpacket = false; // this is an error because we should have retured above
|
||||
packeterror(); // when endof packet marker was active
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // lastedge == posedge
|
||||
// this should not happen, check later
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static void IRAM_ATTR sniffer_isr_handler(void *) {
|
||||
DCCSniffer.processInterrupt();
|
||||
}
|
||||
*/
|
||||
#endif // ESP32
|
76
Sniffer.h
Normal file
76
Sniffer.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* © 2025 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#include "driver/mcpwm.h"
|
||||
#include "soc/mcpwm_struct.h"
|
||||
#include "soc/mcpwm_reg.h"
|
||||
|
||||
#define MAXDCCPACKETLEN 8
|
||||
#include "DCCPacket.h"
|
||||
|
||||
class Sniffer {
|
||||
public:
|
||||
Sniffer(byte snifferpin);
|
||||
void IRAM_ATTR processInterrupt(int32_t capticks, bool posedge);
|
||||
inline int32_t getTicks() {
|
||||
noInterrupts();
|
||||
int32_t i = diffticks;
|
||||
interrupts();
|
||||
return i;
|
||||
};
|
||||
inline int64_t getDebug() {
|
||||
noInterrupts();
|
||||
int64_t i = debugfield;
|
||||
interrupts();
|
||||
return i;
|
||||
};
|
||||
inline DCCPacket fetchPacket() {
|
||||
// if there is no new data, this will create a
|
||||
// packet with length 0 (which is no packet)
|
||||
DCCPacket p;
|
||||
noInterrupts();
|
||||
if (!outpacket.empty()) {
|
||||
p = outpacket.front();
|
||||
outpacket.pop_front();
|
||||
}
|
||||
interrupts();
|
||||
return p;
|
||||
};
|
||||
bool inputActive();
|
||||
private:
|
||||
// keep these vars in processInterrupt only
|
||||
uint64_t bitfield = 0;
|
||||
uint64_t debugfield = 0;
|
||||
int32_t diffticks;
|
||||
int32_t lastticks;
|
||||
bool lastedge;
|
||||
byte currentbyte = 0;
|
||||
byte dccbytes[MAXDCCPACKETLEN];
|
||||
byte dcclen = 0;
|
||||
bool inpacket = false;
|
||||
// these vars are used as interface to other parts of sniffer
|
||||
byte halfbitcounter = 0;
|
||||
std::list<DCCPacket> outpacket;
|
||||
DCCPacket prevpacket;
|
||||
volatile unsigned long lastendofpacket = 0; // timestamp millis
|
||||
|
||||
};
|
||||
#endif
|
@ -27,6 +27,7 @@ bool Diag::WIFI=false;
|
||||
bool Diag::WITHROTTLE=false;
|
||||
bool Diag::ETHERNET=false;
|
||||
bool Diag::LCN=false;
|
||||
bool Diag::SNIFFER=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
|
@ -30,7 +30,7 @@ class Diag {
|
||||
static bool WITHROTTLE;
|
||||
static bool ETHERNET;
|
||||
static bool LCN;
|
||||
|
||||
static bool SNIFFER;
|
||||
};
|
||||
|
||||
class StringFormatter
|
||||
|
@ -668,7 +668,8 @@ void TrackManager::setJoin(bool joined) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
|
||||
tempProgTrack = t; // remember PROG track
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
track[t]->setPower(POWERMODE::ON); // if joined, always on
|
||||
// setPower() of the track called after
|
||||
// seperately after setJoin() instead
|
||||
break; // there is only one prog track, done
|
||||
}
|
||||
}
|
||||
@ -680,8 +681,6 @@ void TrackManager::setJoin(bool joined) {
|
||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
|
||||
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
|
||||
tempProgTrack = MAX_TRACKS+1;
|
||||
} else {
|
||||
DIAG(F("Unjoin but no remembered prog track"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
11
Turnouts.cpp
11
Turnouts.cpp
@ -312,12 +312,6 @@
|
||||
*
|
||||
*************************************************************************************/
|
||||
|
||||
#if defined(DCC_TURNOUTS_RCN_213)
|
||||
const bool DCCTurnout::rcn213Compliant = true;
|
||||
#else
|
||||
const bool DCCTurnout::rcn213Compliant = false;
|
||||
#endif
|
||||
|
||||
// DCCTurnoutData contains data specific to this subclass that is
|
||||
// written to EEPROM when the turnout is saved.
|
||||
struct DCCTurnoutData {
|
||||
@ -385,7 +379,10 @@
|
||||
// DCC++ Classic behaviour is that Throw writes a 1 in the packet,
|
||||
// and Close writes a 0.
|
||||
// RCN-213 specifies that Throw is 0 and Close is 1.
|
||||
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
||||
#ifndef DCC_TURNOUTS_RCN_213
|
||||
close = !close;
|
||||
#endif
|
||||
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -245,8 +245,6 @@ public:
|
||||
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
||||
static Turnout *load(struct TurnoutData *turnoutData);
|
||||
void print(Print *stream) override;
|
||||
// Flag whether DCC Accessory packets are to contain 1=close/0=throw(RCN-213) or 1=throw/0-close (DCC++ Classic)
|
||||
static const bool rcn213Compliant;
|
||||
|
||||
protected:
|
||||
bool setClosedInternal(bool close) override;
|
||||
|
@ -500,9 +500,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2025 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
@ -45,15 +45,14 @@ The configuration file for DCC-EX Command Station
|
||||
// the correct resistor could damage the sense pin on your Arduino or destroy
|
||||
// the device.
|
||||
//
|
||||
// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h
|
||||
// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. Full list in MotorDrivers.h
|
||||
//
|
||||
// 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)
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
|
||||
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
|
||||
// EXCSB1 : DCC-EX CSB-1 hardware
|
||||
// EXCSB1_WITH_EX8874 : DCC-EX CSB-1 hardware with DCC-EX TI DRV8874 shield
|
||||
// NO_SHIELD : CS without any motor shield (as an accessory only CS)
|
||||
// |
|
||||
// +-----------------------v
|
||||
//
|
||||
@ -81,7 +80,7 @@ The configuration file for DCC-EX Command Station
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NOTE: Only supported on Arduino Mega
|
||||
// NOTE: Not supported on Arduino Uno or Nano
|
||||
// Set to false if you not even want it on the Arduino Mega
|
||||
//
|
||||
#define ENABLE_WIFI true
|
||||
@ -116,13 +115,13 @@ The configuration file for DCC-EX Command Station
|
||||
// Your password may not contain ``"'' (double quote, ASCII 0x22).
|
||||
#define WIFI_PASSWORD "Your network passwd"
|
||||
//
|
||||
// WIFI_HOSTNAME: You probably don't need to change this
|
||||
// WIFI_HOSTNAME: You can change this if you have more than one
|
||||
// CS to make them show up with different names on the network.
|
||||
// Otherwise do not touch.
|
||||
#define WIFI_HOSTNAME "dccex"
|
||||
//
|
||||
// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented,
|
||||
// WiFi will be enabled (Mega only). The default channel is set to "1" whether
|
||||
// this line exists or not. If you need to use an alternate channel (we recommend
|
||||
// using only 1,6, or 11) you may change it here.
|
||||
// WIFI_CHANNEL: The default channel is set to "1". If you need to use an
|
||||
// alternate channel (we recommend using only 1,6, or 11) you may change it here.
|
||||
#define WIFI_CHANNEL 1
|
||||
//
|
||||
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
|
||||
@ -132,8 +131,9 @@ The configuration file for DCC-EX Command Station
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
|
||||
// is not for Wifi. You will then need the Arduino Ethernet library as well
|
||||
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired) based
|
||||
// on the W5100/W5500 ethernet chip or an STM32 CS with builin ethernet like the F429ZI.
|
||||
// This is not for Wifi. You will then need the Arduino Ethernet library as well.
|
||||
//
|
||||
//#define ENABLE_ETHERNET true
|
||||
|
||||
@ -270,8 +270,9 @@ The configuration file for DCC-EX Command Station
|
||||
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
||||
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||
// (throw turnout).
|
||||
//#define DCC_ACCESSORY_RCN_213
|
||||
//
|
||||
//#define DCC_ACCESSORY_COMMAND_REVERSE
|
||||
|
||||
|
||||
// HANDLING MULTIPLE SERIAL THROTTLES
|
||||
// The command station always operates with the default Serial port.
|
||||
// Diagnostics are only emitted on the default serial port and not broadcast.
|
||||
@ -333,7 +334,7 @@ The configuration file for DCC-EX Command Station
|
||||
// to the sabertooth controller _as_well_. Default: Undefined.
|
||||
//
|
||||
//#define SABERTOOTH 1
|
||||
//
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SENSORCAM
|
||||
@ -345,7 +346,7 @@ The configuration file for DCC-EX Command Station
|
||||
//#define SENSORCAM2_VPIN 600 //define other CAM's if installed.
|
||||
//#define CAM2 SENSORCAM2_VPIN+ //for EX-RAIL commands e.g. IFLT(CAM2 020,1)
|
||||
//
|
||||
// For smoother power-up, define a STARTUP_DELAY to allow CAM to initialise ref images
|
||||
//#define STARTUP_DELAY 5000 // up to 20sec. CS delay
|
||||
// For smoother power-up, when using the CAM, you may need a STARTUP_DELAY.
|
||||
// That is described further above.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -12,18 +12,10 @@
|
||||
default_envs =
|
||||
mega2560
|
||||
uno
|
||||
unowifiR2
|
||||
nano
|
||||
samd21-dev-usb
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3_2
|
||||
Teensy3_5
|
||||
Teensy3_6
|
||||
Teensy4_0
|
||||
Teensy4_1
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
|
20
version.h
20
version.h
@ -3,7 +3,25 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.2.93"
|
||||
#define VERSION "5.4.12"
|
||||
// 5.4.12 - Bugfix: Negative route Ids
|
||||
// 5.4.11 - Feature: Enable sniffer on CSB-1
|
||||
// 5.4.10 - Bugfix: MEGA DCC waveform starvation (sends too many idles)
|
||||
// 5.4.9 - Handle non-compliant decoders returning 255 for cv 20 and confusing <R> with bad consist addresses.
|
||||
// - <W CONSIST longaddr> handles non-compliant decoders which NAK cv 20 writes.
|
||||
// 5.4.8 - Bugfix: Insert idle packet at end of speed reminder loop; treat all function groups equal
|
||||
// 5.4.7 - Bugfix: EXRAIL fix CLEAR_ALL_STASH
|
||||
// 5.4.6 - Bugfix: Do not drop further commands in same packet
|
||||
// 5.4.5 - ESP32: Better detection of correct IDF version
|
||||
// - track power is always turned on after setJoin() not by setJoin()
|
||||
// 5.4.4 - bugfix in parser, input buffer overrun and trailing > that did break <+>
|
||||
// 5.4.3 - bugfix changeFn for functions 29..31
|
||||
// 5.4.2 - Reversed turnout bugfix
|
||||
// 5.4.1 - ESP32 bugfix packet buffer race
|
||||
// 5.4.0 - New version on master
|
||||
// 5.2.96 - EXRAIL additions XFWD() and XREV()
|
||||
// 5.2.95 - Release candidate for 5.4
|
||||
// 5.2.94 - Bugfix: Less confusion and simpler code around the RCN213 defines
|
||||
// 5.2.93 - Bugfix ESP32: clear progTrackSyncMain (join flag) when prog track is removed
|
||||
// 5.2.92 - Bugfix: FADE power off fix, EXRAIL power diagnostic fix.
|
||||
// 5.2.91 - Bugfix: Neopixel I2C overlap check
|
||||
|
Loading…
x
Reference in New Issue
Block a user