1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 02:13:45 +02:00

Compare commits

...

50 Commits

Author SHA1 Message Date
Asbelos
3fc90c916c Merge branch 'devel' into devel_chris 2024-04-12 15:08:49 +01:00
Asbelos
132e2d0de2 Revert "Merge branch 'master' into devel_chris"
This reverts commit 23845f2df2, reversing
changes made to 76755993f1.
2024-04-12 15:07:31 +01:00
Asbelos
23845f2df2 Merge branch 'master' into devel_chris 2024-04-12 14:38:22 +01:00
Asbelos
76755993f1 ONSENSOR/ONBUTTON 2024-04-12 14:37:34 +01:00
pmantoine
91e60b3716 HALDisplay bug fix 2024-04-12 17:25:00 +08:00
Asbelos
8a5a832b1d Reduced EXRAIL diag noise 2024-04-09 20:59:57 +01:00
Asbelos
5ea6feb11a Squashed commit of the following:
commit 8987d622e6
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Apr 9 20:44:47 2024 +0100

    doc note

commit 8f0a5c1ec0
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:45:58 2024 +0100

    Exrail notes

commit 94083b9ab8
Merge: 72ef199 02bf50b
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:08:26 2024 +0100

    Merge branch 'devel' into devel_chris

commit 72ef199315
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:06:50 2024 +0100

    TOGGLE_TURNOUT

commit e69b777a2f
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Apr 3 15:17:40 2024 +0100

    BLINK command

commit c7ed47400d
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Apr 2 10:12:45 2024 +0100

    FTOGGLE,XFTOGGLE

commit 7a93cf7be8
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Mar 29 13:21:35 2024 +0000

    EXRAIL STEALTH_GLOBAL
2024-04-09 20:45:28 +01:00
Asbelos
8987d622e6 doc note 2024-04-09 20:44:47 +01:00
Harald Barth
263c3d01e3 DISABLE_DIAG by default for Uno and Nano 2024-04-07 09:26:32 +02:00
Asbelos
182479c07b Consist version. 2024-04-06 23:49:26 +01:00
Asbelos
3317b4666e Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2024-04-06 23:41:33 +01:00
Asbelos
f41f61dd5f <W CONSIST cmd 2024-04-06 23:41:25 +01:00
Harald Barth
6b713bf57c version 5.2.45 2024-04-06 19:48:02 +02:00
Harald Barth
38a9585a41 ESP32 Trackmanager reset cab number to 0 when track is not DC 2024-04-06 19:46:23 +02:00
Asbelos
1a307eea3d Extended consist <R> and <W> 2024-04-06 13:19:56 +01:00
Harald Barth
e4a3aa9f1e tag 2024-04-05 20:31:05 +02:00
Harald Barth
f581d56bdc ESP32 set frequency after DC speed 2024-04-05 20:30:26 +02:00
Harald Barth
7b77d4ce1e STM32 fix inverted pin mode 2024-04-05 14:08:39 +02:00
Harald Barth
d367f5dc81 version 5.2.44 2024-04-05 14:06:36 +02:00
Harald Barth
dc5f5e05b9 ESP32 fix PWM LEDC inverted pin mode 2024-04-05 14:05:12 +02:00
Harald Barth
cff4075937 version 5.2.43 2024-04-05 01:12:08 +02:00
Harald Barth
84b90ae757 Booster mode inrush throttle, too 2024-04-05 01:11:12 +02:00
Harald Barth
6d7d2325da ESP32 rewrite PWM LEDC inrush duty fix 2024-04-05 01:10:10 +02:00
Harald Barth
fdc956576b ESP32 rewrite PWM LEDC to use pin mux 2024-04-05 01:02:49 +02:00
Asbelos
8f0a5c1ec0 Exrail notes 2024-04-04 09:45:58 +01:00
Asbelos
94083b9ab8 Merge branch 'devel' into devel_chris 2024-04-04 09:08:26 +01:00
Asbelos
72ef199315 TOGGLE_TURNOUT 2024-04-04 09:06:50 +01:00
Asbelos
e69b777a2f BLINK command 2024-04-03 15:17:40 +01:00
Asbelos
c7ed47400d FTOGGLE,XFTOGGLE 2024-04-02 10:12:45 +01:00
Harald Barth
02bf50b909 version 2024-04-02 00:05:30 +02:00
Harald Barth
c8f18e4d67 ESP32 Bugfix: Uninitialized stack variable. Will bite you with infinite loop if no tracks are defined 2024-04-02 00:03:51 +02:00
Asbelos
7a93cf7be8 EXRAIL STEALTH_GLOBAL 2024-03-29 13:21:35 +00:00
peteGSX
87073b0d36 Rotary Encoder address 0x67 2024-03-23 13:31:34 +10:00
Harald Barth
0587e6fc09 version 5.2.40 2024-03-18 21:18:57 +01:00
Harald Barth
3cda869c6e Allow no shield 2024-03-18 21:17:51 +01:00
Harald Barth
59d855549e version tag 2024-03-12 11:47:25 +01:00
Harald Barth
e3081a7e56 Functions for DC frequency: Use func up to F31 part 2 2024-03-12 11:45:28 +01:00
Harald Barth
8eec85edcf version 5.2.39 2024-03-11 14:54:18 +01:00
Harald Barth
d753eb43e3 Functions for DC frequency: Use func up to F31 2024-03-11 14:52:55 +01:00
Asbelos
9aac34b403 comments 2024-03-11 12:26:28 +00:00
Peter Akers
28d60d4984 Update README.md 2024-02-16 18:02:40 +10:00
peteGSX
3b162996ad EX-IO fixes in version 2024-01-21 07:13:53 +10:00
Harald Barth
fb414a7a50 Bugfix: allocate enough bytes for digital pins. Add more sanity checks when allocating memory 2024-01-20 21:45:09 +01:00
Harald Barth
818e05b425 version 5.0.8 2024-01-10 08:37:54 +01:00
Harald Barth
c5168f030f Do not crash on turnouts without description 2024-01-10 08:25:34 +01:00
Harald Barth
387ea019bd version 5.0.7 2023-11-06 22:11:56 +01:00
Harald Barth
a981f83bb9 Only flag 2.2.0.0-dev as broken, not 2.2.0.0 2023-11-06 22:11:31 +01:00
Asbelos
749a859db5 Bugfix TURNOUTL 2023-11-01 20:13:05 +00:00
Harald Barth
659c58b307 version 5.0.5 2023-10-28 19:20:33 +02:00
Harald Barth
0b9ec7460b Bugfix version detection logic and better message 2023-10-28 19:18:59 +02:00
33 changed files with 777 additions and 100 deletions

View File

@@ -209,9 +209,7 @@ int16_t CommandDistributor::retClockTime() {
void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot];
uint32_t func = sp->functions;
func = func & 0x1fffffff; // mask out bits 0-28
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,func);
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
#ifdef SABERTOOTH
if (Serial2 && sp->loco == SABERTOOTH) {
static uint8_t rampingmode = 0;

85
DCC.cpp
View File

@@ -219,8 +219,9 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
} else {
speedTable[reg].functions &= ~funcmask;
}
if (speedTable[reg].functions != previous && functionNumber <= 28) {
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
if (speedTable[reg].functions != previous) {
if (functionNumber <= 28)
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
CommandDistributor::broadcastLoco(reg);
}
return true;
@@ -235,14 +236,14 @@ void DCC::changeFn( int cab, int16_t functionNumber) {
speedTable[reg].functions ^= funcmask;
if (functionNumber <= 28) {
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
CommandDistributor::broadcastLoco(reg);
}
CommandDistributor::broadcastLoco(reg);
}
// Report function state (used from withrottle protocol)
// returns 0 false, 1 true or -1 for do not know
int8_t DCC::getFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28)
if (cab<=0 || functionNumber>31)
return -1; // unknown
int reg = lookupSpeedTable(cab);
if (reg<0)
@@ -324,8 +325,8 @@ 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<65>rdecoder ist wie bei den einfachen
Zubeh<EFBFBD>rdecodern die Adresse 4 = 1000-0001 0111-0001 . Diese Adresse wird in
Die Adresse f<>r den ersten erweiterten Zubeh<65>rdecoder ist wie bei den einfachen
Zubeh<EFBFBD>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
@@ -499,6 +500,36 @@ const ackOp FLASH READ_CV_PROG[] = {
const ackOp FLASH LOCO_ID_PROG[] = {
BASELINE,
// first check cv20 for extended addressing
SETCV, (ackOp)20, // CV 19 is extended
SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // skip past extended section if cv20 is zero
// read cv20 and 19 and merge
STARTMERGE, // Setup to read cv 20
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
STASHLOCOID, // keep cv 20 until we have cv19 as well.
SETCV, (ackOp)19,
STARTMERGE, // Setup to read cv 19
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, NAKFAIL, // cant recover if cv 19 unreadable
COMBINE1920, // Combile byte with stash and callback
// end of advanced 20,19 check
SKIPTARGET,
SETCV, (ackOp)19, // CV 19 is consist setting
SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
@@ -565,6 +596,10 @@ const ackOp FLASH LOCO_ID_PROG[] = {
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19,20
SETCV,(ackOp)20,
SETBYTE, (ackOp)0,
WB,WACK, // ignore dedcoder without cv20 support
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore dedcoder without cv19 support
@@ -580,9 +615,25 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
CALLFAIL
};
// for CONSIST_ID_PROG the 20,19 values are already calculated
const ackOp FLASH CONSIST_ID_PROG[] = {
BASELINE,
SETCV,(ackOp)20,
SETBYTEH, // high byte to CV 20
WB,WACK, // ignore dedcoder without cv20 support
SETCV,(ackOp)19,
SETBYTEL, // low byte of word
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
VB,WACK,ITC1, // Some decoders do not ack and need verify
CALLFAIL
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19
// Clear consist CV 19,20
SETCV,(ackOp)20,
SETBYTE, (ackOp)0,
WB,WACK, // ignore dedcoder without cv20 support
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore decoder without cv19 support
@@ -651,6 +702,26 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
}
void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) {
if (id<0 || id>10239) { //0x27FF according to standard
callback(-1);
return;
}
byte cv20;
byte cv19;
if (id<=HIGHEST_SHORT_ADDR) {
cv19=id;
cv20=0;
}
else {
cv20=id/100;
cv19=id%100;
}
if (reverse) cv19|=0x80;
DCCACK::Setup((cv20<<8)|cv19, CONSIST_ID_PROG, callback);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab, false);

2
DCC.h
View File

@@ -85,7 +85,7 @@ public:
static void getLocoId(ACK_CALLBACK callback);
static void setLocoId(int id,ACK_CALLBACK callback);
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders

View File

@@ -314,6 +314,14 @@ void DCCACK::loop() {
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return;
case COMBINE1920:
// ackManagerStash is cv20, ackManagerByte is CV 19
// This will not be called if cv20==0
ackManagerByte &= 0x7F; // ignore direction marker
ackManagerByte %=100; // take last 2 decimal digits
callback( ackManagerStash*100+ackManagerByte);
return;
case ITSKIP:
if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found
@@ -322,6 +330,15 @@ void DCCACK::loop() {
opcode=GETFLASH(ackManagerProg);
}
break;
case NAKSKIP:
if (ackReceived) break;
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:

View File

@@ -56,6 +56,8 @@ enum ackOp : byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
NAKSKIP, // skip to SKIPTARGET if ack false
COMBINE1920, // combine cvs 19 and 20 and callback
SKIPTARGET = 0xFF // jump to target
};

View File

@@ -68,7 +68,7 @@ Once a new OPCODE is decided upon, update this list.
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L, Reserved for LCC interface (implemented in EXRAIL)
m,
m, message to throttles broadcast
M, Write DCC packet
n,
N,
@@ -283,25 +283,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; // filterCallback asked us to ignore
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
{
if (params==1) { // <t cab> display state
int16_t slot=DCC::lookupSpeedTable(p[0],false);
if (slot>=0) {
DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
sp->loco,slot,sp->speedCode,sp->functions);
}
else // send dummy state speed 0 fwd no functions.
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
return;
}
int16_t cab;
int16_t tspeed;
int16_t direction;
if (params==1) { // <t cab> display state
int16_t slot=DCC::lookupSpeedTable(p[0],false);
if (slot>=0)
CommandDistributor::broadcastLoco(slot);
else // send dummy state speed 0 fwd no functions.
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
return;
}
if (params == 4)
{ // <t REGISTER CAB SPEED DIRECTION>
// ignore register p[0]
cab = p[1];
tspeed = p[2];
direction = p[3];
@@ -461,6 +458,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
DCC::setLocoId(p[0],callback_Wloco);
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W4);
else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) {
DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist);
}
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
DCC::writeCVByte(p[0], p[1], callback_W);
else
@@ -800,6 +800,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
break;
#endif
case '/': // implemented in EXRAIL parser
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL
@@ -1350,3 +1351,11 @@ void DCCEXParser::callback_Wloco(int16_t result)
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Wconsist(int16_t result)
{
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(""));
commitAsyncReplyStream();
}

View File

@@ -71,6 +71,7 @@ struct DCCEXParser
static void callback_R(int16_t result);
static void callback_Rloco(int16_t result);
static void callback_Wloco(int16_t result);
static void callback_Wconsist(int16_t result);
static void callback_Vbit(int16_t result);
static void callback_Vbyte(int16_t result);
static FILTER_CALLBACK filterCallback;

View File

@@ -65,7 +65,11 @@ class DCCTimer {
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);
static void DCCEXanalogWrite(uint8_t pin, int value, bool invert);
static void DCCEXledcDetachPin(uint8_t pin);
static void DCCEXanalogCopyChannel(int8_t frompin, int8_t topin);
static void DCCEXInrushControlOn(uint8_t pin, int duty, bool invert);
static void DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted);
// Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other

View File

@@ -78,6 +78,7 @@ int DCCTimer::freeMemory() {
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#include "DIAG.h"
#include <driver/adc.h>
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
@@ -154,8 +155,10 @@ void DCCTimer::reset() {
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
if (f >= 16)
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
else if (f == 7)
/*
else if (f == 7) // not used on ESP32
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
*/
else if (f >= 4)
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
else if (f >= 3)
@@ -188,23 +191,104 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
}
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
void DCCTimer::DCCEXledcDetachPin(uint8_t pin) {
DIAG(F("Clear pin %d channel"), pin);
pin_to_channel[pin] = 0;
pinMatrixOutDetach(pin, false, false);
}
static byte LEDCToMux[] = {
LEDC_HS_SIG_OUT0_IDX,
LEDC_HS_SIG_OUT1_IDX,
LEDC_HS_SIG_OUT2_IDX,
LEDC_HS_SIG_OUT3_IDX,
LEDC_HS_SIG_OUT4_IDX,
LEDC_HS_SIG_OUT5_IDX,
LEDC_HS_SIG_OUT6_IDX,
LEDC_HS_SIG_OUT7_IDX,
LEDC_LS_SIG_OUT0_IDX,
LEDC_LS_SIG_OUT1_IDX,
LEDC_LS_SIG_OUT2_IDX,
LEDC_LS_SIG_OUT3_IDX,
LEDC_LS_SIG_OUT4_IDX,
LEDC_LS_SIG_OUT5_IDX,
LEDC_LS_SIG_OUT6_IDX,
LEDC_LS_SIG_OUT7_IDX,
};
void DCCTimer::DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted) {
DIAG(F("Attaching pin %d to channel %d %c"), pin, channel, inverted ? 'I' : ' ');
ledcAttachPin(pin, channel);
if (inverted) // we attach again but with inversion
gpio_matrix_out(pin, LEDCToMux[channel], inverted, 0);
}
void DCCTimer::DCCEXanalogCopyChannel(int8_t frompin, int8_t topin) {
// arguments are signed depending on inversion of pins
DIAG(F("Pin %d copied to %d"), frompin, topin);
bool inverted = false;
if (frompin<0)
frompin = -frompin;
if (topin<0) {
inverted = true;
topin = -topin;
}
int channel = pin_to_channel[frompin]; // after abs(frompin)
pin_to_channel[topin] = channel;
DCCTimer::DCCEXledcAttachPin(topin, channel, inverted);
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
// This allocates channels 15, 13, 11, ....
// so each channel gets its own timer.
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
int search_channel;
int n;
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
// search for free channels top down
for (search_channel=LEDC_CHANNELS-1; search_channel >=cnt_channel; search_channel -= 2) {
bool chanused = false;
for (n=0; n < SOC_GPIO_PIN_COUNT; n++) {
if (pin_to_channel[n] == search_channel) { // current search_channel used
chanused = true;
break;
}
}
if (chanused)
continue;
if (n == SOC_GPIO_PIN_COUNT) // current search_channel unused
break;
}
if (search_channel >= cnt_channel) {
pin_to_channel[pin] = search_channel;
DIAG(F("Pin %d assigned to search channel %d"), pin, search_channel);
} else {
pin_to_channel[pin] = --cnt_channel; // This sets 15, 13, ...
DIAG(F("Pin %d assigned to new channel %d"), pin, cnt_channel);
--cnt_channel; // Now we are at 14, 12, ...
}
ledcSetup(pin_to_channel[pin], 1000, 8);
DCCEXledcAttachPin(pin, pin_to_channel[pin], invert);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
// This else is only here so we can enable diag
// Pin should be already attached to channel
// DIAG(F("Pin %d assigned to old channel %d"), pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
void DCCTimer::DCCEXInrushControlOn(uint8_t pin, int duty, bool inverted) {
// this uses hardcoded channel 0
ledcSetup(0, 62500, 8);
DCCEXledcAttachPin(pin, 0, inverted);
ledcWrite(0, duty);
}
int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);

View File

@@ -333,7 +333,9 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
return;
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
if (invert)
value = 255-value;
// Calculate percentage duty cycle from value given
uint32_t duty_cycle = (value * 100 / 256) + 1;
if (pin_timer[pin] != NULL) {

View File

@@ -294,7 +294,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
// The resets will be zero not only now but as well repeats packets into the future
clearResets(repeats+1);
{
int ret;
int ret = 0;
do {
if(isMainTrack) {
if (rmtMainChannel != NULL)

View File

@@ -54,6 +54,7 @@
#include "TrackManager.h"
#include "Turntables.h"
#include "IODevice.h"
#include "EXRAILSensor.h"
// One instance of RMFT clas is used for each "thread" in the automation.
@@ -176,7 +177,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
//DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
@@ -251,6 +252,12 @@ if (compileFeatures & FEATURE_SIGNAL) {
break;
}
case OPCODE_ONSENSOR:
new EXRAILSensor(operand,progCounter+3,true );
break;
case OPCODE_ONBUTTON:
new EXRAILSensor(operand,progCounter+3,false );
break;
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=getOperand(progCounter,1);
@@ -373,7 +380,7 @@ RMFT2::RMFT2(int progCtr) {
speedo=0;
forward=true;
invert=false;
timeoutFlag=false;
blinkState=not_blink_task;
stackDepth=0;
onEventStartPosition=-1; // Not handling an ONxxx
@@ -411,7 +418,7 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
void RMFT2::driveLoco(byte speed) {
if (loco<=0) return; // Prevent broadcast!
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
//if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
/* TODO.....
power on appropriate track if DC or main if dcc
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
@@ -480,6 +487,7 @@ bool RMFT2::skipIfBlock() {
}
void RMFT2::loop() {
EXRAILSensor::checkAll();
// Round Robin call to a RMFT task each time
if (loopTask==NULL) return;
@@ -491,6 +499,23 @@ void RMFT2::loop() {
void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
// special stand alone blink task
if (compileFeatures & FEATURE_BLINK) {
if (blinkState==blink_low) {
IODevice::write(blinkPin,HIGH);
blinkState=blink_high;
delayMe(getOperand(1));
return;
}
if (blinkState==blink_high) {
IODevice::write(blinkPin,LOW);
blinkState=blink_low;
delayMe(getOperand(2));
return;
}
}
// Normal progstep following tasks continue here.
byte opcode = GET_OPCODE;
int16_t operand = getOperand(0);
@@ -511,6 +536,10 @@ void RMFT2::loop2() {
Turnout::setClosed(operand, true);
break;
case OPCODE_TOGGLE_TURNOUT:
Turnout::setClosed(operand, Turnout::isThrown(operand));
break;
#ifndef IO_NO_HAL
case OPCODE_ROTATE:
uint8_t activity;
@@ -560,39 +589,39 @@ void RMFT2::loop2() {
break;
case OPCODE_AT:
timeoutFlag=false;
blinkState=not_blink_task;
if (readSensor(operand)) break;
delayMe(50);
return;
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
blinkState=not_blink_task;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
blinkState=not_blink_task;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
timeoutStart=millis();
timeoutFlag=false;
blinkState=not_blink_task;
break;
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
blinkState=at_timeout;
break; // and drop through
}
delayMe(50);
return;
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
skipIf=!timeoutFlag;
skipIf=blinkState!=at_timeout;
break;
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
@@ -624,13 +653,25 @@ void RMFT2::loop2() {
break;
case OPCODE_SET:
killBlinkOnVpin(operand);
IODevice::write(operand,true);
break;
case OPCODE_RESET:
killBlinkOnVpin(operand);
IODevice::write(operand,false);
break;
case OPCODE_BLINK:
// Start a new task to blink this vpin
killBlinkOnVpin(operand);
{
auto newtask=new RMFT2(progCounter);
newtask->blinkPin=operand;
newtask->blinkState=blink_low; // will go high on first call
}
break;
case OPCODE_PAUSE:
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=this;
@@ -815,6 +856,10 @@ void RMFT2::loop2() {
case OPCODE_FOFF:
if (loco) DCC::setFn(loco,operand,false);
break;
case OPCODE_FTOGGLE:
if (loco) DCC::changeFn(loco,operand);
break;
case OPCODE_DRIVE:
{
@@ -830,6 +875,10 @@ void RMFT2::loop2() {
case OPCODE_XFOFF:
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_XFTOGGLE:
DCC::changeFn(operand,getOperand(1));
break;
case OPCODE_DCCACTIVATE: {
// operand is address<<3 | subaddr<<1 | active
@@ -1025,7 +1074,7 @@ void RMFT2::loop2() {
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
//if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
break;
case OPCODE_AUTOSTART: // Handled only during begin process
@@ -1043,6 +1092,8 @@ void RMFT2::loop2() {
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
case OPCODE_ONBUTTON:
case OPCODE_ONSENSOR:
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
@@ -1105,7 +1156,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
//if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
// This will work even without a signal definition.
@@ -1125,7 +1176,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
//if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@@ -1133,7 +1184,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// A servo signal, the pin numbers are actually servo positions
// Note, setting a signal to a zero position has no effect.
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
if (diag) DIAG(F("sigA %d %d"),id,servopos);
//if (diag) DIAG(F("sigA %d %d"),id,servopos);
if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
return;
}
@@ -1167,16 +1218,19 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
if (redpin) {
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (!aHigh) redval=!redval;
killBlinkOnVpin(redpin);
IODevice::write(redpin,redval);
}
if (amberpin) {
bool amberval=(rag==SIGNAL_AMBER);
if (!aHigh) amberval=!amberval;
killBlinkOnVpin(amberpin);
IODevice::write(amberpin,amberval);
}
if (greenpin) {
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
if (!aHigh) greenval=!greenval;
killBlinkOnVpin(greenpin);
IODevice::write(greenpin,greenval);
}
}
@@ -1248,7 +1302,7 @@ void RMFT2::rotateEvent(int16_t turntableId, bool change) {
void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
DIAG(F("clockEvent at : %d"), clocktime);
if (change) {
onClockLookup->handleEvent(F("CLOCK"),clocktime);
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
@@ -1258,12 +1312,31 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
void RMFT2::powerEvent(int16_t track, bool overload) {
// Hunt for an ONOVERLOAD for this item
if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track);
DIAG(F("powerEvent : %c"), track);
if (overload) {
onOverloadLookup->handleEvent(F("POWER"),track);
}
}
// 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.
void RMFT2::killBlinkOnVpin(VPIN pin) {
if (!(compileFeatures & FEATURE_BLINK)) return;
RMFT2 * task=loopTask;
while(task) {
if (
(task->blinkState==blink_high || task->blinkState==blink_low)
&& task->blinkPin==pin) {
task->kill();
return;
}
task=task->next;
if (task==loopTask) return;
}
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;

View File

@@ -33,7 +33,7 @@
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
// searching easier as a parameter can never be confused with an opcode.
//
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,
@@ -41,9 +41,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
OPCODE_BLINK,
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_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
@@ -71,7 +73,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
OPCODE_ROUTE_DISABLED,
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
// see skipIfBlock()
@@ -98,12 +100,21 @@ enum thrunger: byte {
thrunge_lcd, // Must be last!!
};
enum BlinkState: byte {
not_blink_task,
blink_low, // blink task running with pin LOW
blink_high, // blink task running with pin high
at_timeout // ATTIMEOUT timed out flag
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20;
static const byte FEATURE_ROUTESTATE= 0x10;
static const byte FEATURE_STASH = 0x08;
static const byte FEATURE_BLINK = 0x04;
// Flag bits for status of hardware and TPL
@@ -192,6 +203,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static uint16_t getOperand(int progCounter,byte n);
static void killBlinkOnVpin(VPIN pin);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -244,10 +256,10 @@ private:
union {
unsigned long waitAfter; // Used by OPCODE_AFTER
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
VPIN blinkPin; // Used by blink tasks
};
bool timeoutFlag;
byte taskId;
BlinkState blinkState; // includes AT_TIMEOUT flag.
uint16_t loco;
bool forward;
bool invert;

View File

@@ -38,6 +38,7 @@
#undef ATTIMEOUT
#undef AUTOMATION
#undef AUTOSTART
#undef BLINK
#undef BROADCAST
#undef CALL
#undef CLEAR_STASH
@@ -66,6 +67,7 @@
#undef FOLLOW
#undef FON
#undef FORGET
#undef FTOGGLE
#undef FREE
#undef FWD
#undef GREEN
@@ -112,6 +114,8 @@
#undef ONGREEN
#undef ONRED
#undef ONROTATE
#undef ONBUTTON
#undef ONSENSOR
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
@@ -164,8 +168,10 @@
#undef START
#undef STASH
#undef STEALTH
#undef STEALTH_GLOBAL
#undef STOP
#undef THROW
#undef TOGGLE_TURNOUT
#undef TT_ADDPOSITION
#undef TURNOUT
#undef TURNOUTL
@@ -180,6 +186,7 @@
#undef WITHROTTLE
#undef XFOFF
#undef XFON
#undef XFTOGGLE
#ifndef RMFT2_UNDEF_ONLY
#define ACTIVATE(addr,subaddr)
@@ -196,6 +203,7 @@
#define ATTIMEOUT(sensor_id,timeout_ms)
#define AUTOMATION(id,description)
#define AUTOSTART
#define BLINK(vpin,onDuty,offDuty)
#define BROADCAST(msg)
#define CALL(route)
#define CLEAR_STASH(id)
@@ -225,6 +233,7 @@
#define FON(func)
#define FORGET
#define FREE(blockid)
#define FTOGGLE(func)
#define FWD(speed)
#define GREEN(signal_id)
#define HAL(haltype,params...)
@@ -272,6 +281,8 @@
#define ONROTATE(turntable_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define ONSENSOR(sensor_id)
#define ONBUTTON(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
@@ -322,8 +333,10 @@
#define START(route)
#define STASH(id)
#define STEALTH(code...)
#define STEALTH_GLOBAL(code...)
#define STOP
#define THROW(id)
#define TOGGLE_TURNOUT(id)
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
#define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
@@ -338,4 +351,6 @@
#define WITHROTTLE(msg)
#define XFOFF(cab,func)
#define XFON(cab,func)
#define XFTOGGLE(cab,func)
#endif

View File

@@ -36,7 +36,7 @@
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
bool reject=false;
switch(opcode) {
case 'D':
@@ -47,8 +47,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
break;
case '/': // New EXRAIL command
reject=!parseSlash(stream,paramCount,p);
opcode=0;
if (parseSlash(stream,paramCount,p)) opcode=0;
break;
case 'A': // <A address aspect>
@@ -106,9 +105,11 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
reject=eventid<0 || eventid>=countLCCLookup;
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
bool reject = eventid<0 || eventid>=countLCCLookup;
if (!reject) {
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
}
}
break;
@@ -182,12 +183,20 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
RMFT2 * task=loopTask;
while(task) {
if ((compileFeatures & FEATURE_BLINK)
&& (task->blinkState==blink_high || task->blinkState==blink_low)) {
StringFormatter::send(stream,F("\nID=%d,PC=%d,BLINK=%d"),
(int)(task->taskId),task->progCounter,task->blinkPin
);
}
else {
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
(int)(task->taskId),task->progCounter,task->loco,
task->invert?'I':' ',
task->speedo,
task->forward?'F':'R'
);
}
task=task->next;
if (task==loopTask) break;
}

View File

@@ -145,6 +145,12 @@ static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AU
#include "myAutomation.h"
// Pass 1g Implants STEALTH_GLOBAL in correct place
#include "EXRAIL2MacroReset.h"
#undef STEALTH_GLOBAL
#define STEALTH_GLOBAL(code...) code
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
// Also allows creating EXTurntable object
#include "EXRAIL2MacroReset.h"
@@ -202,6 +208,8 @@ bool exrailHalSetup() {
#define PICKUP_STASH(id) | FEATURE_STASH
#undef STASH
#define STASH(id) | FEATURE_STASH
#undef BLINK
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
const byte RMFT2::compileFeatures = 0
#include "myAutomation.h"
@@ -451,6 +459,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define AUTOSTART OPCODE_AUTOSTART,0,0,
#define BLINK(vpin,onDuty,offDuty) OPCODE_BLINK,V(vpin),OPCODE_PAD,V(onDuty),OPCODE_PAD,V(offDuty),
#define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
@@ -484,6 +493,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define FON(func) OPCODE_FON,V(func),
#define FORGET OPCODE_FORGET,0,0,
#define FREE(blockid) OPCODE_FREE,V(blockid),
#define FTOGGLE(func) OPCODE_FTOGGLE,V(func),
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
@@ -518,6 +528,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define STEALTH(code...) PRINT(dummy)
#define STEALTH_GLOBAL(code...)
#define LCN(msg) PRINT(msg)
#define MESSAGE(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
@@ -542,6 +553,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#endif
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define ONSENSOR(sensor_id) OPCODE_ONSENSOR,V(sensor_id),
#define ONBUTTON(sensor_id) OPCODE_ONBUTTON,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
@@ -595,6 +608,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define STASH(id) OPCODE_STASH,V(id),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#define TOGGLE_TURNOUT(id) OPCODE_TOGGLE_TURNOUT,V(id),
#ifndef IO_NO_HAL
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
#endif
@@ -611,6 +625,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#endif
#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),
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;

104
EXRAILSensor.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* © 2024 Chris Harlow
* 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/>.
*/
/**********************************************************************
EXRAILSensor represents a sensor that should be monitored in order
to call an exrail ONBUTTON or ONCHANGE handler.
These are created at EXRAIL startup and thus need no delete or listing
capability.
The basic logic is similar to that found in the Sensor class
except that on the relevant change an EXRAIL thread is started.
**********************************************************************/
#include "EXRAILSensor.h"
#include "EXRAIL2.h"
void EXRAILSensor::checkAll() {
if (firstSensor == NULL) return; // No sensors to be scanned
if (readingSensor == NULL) {
// Not currently scanning sensor list
unsigned long thisTime = micros();
if (thisTime - lastReadCycle < cycleInterval) return;
// Required time has elapsed since last read cycle started,
// so initiate new scan through the sensor list
readingSensor = firstSensor;
lastReadCycle = thisTime;
}
// Loop until either end of list is encountered or we pause for some reason
byte sensorCount = 0;
while (readingSensor != NULL) {
bool pause=readingSensor->check();
// Move to next sensor in list.
readingSensor = readingSensor->nextSensor;
// Currently process max of 16 sensors per entry.
// Performance measurements taken during development indicate that, with 128 sensors configured
// on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices
// within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond.
if (pause || (++sensorCount)>=16) return;
}
}
bool EXRAILSensor::check() {
// check for debounced change in this sensor
inputState = IODevice::read(pin);
// Check if changed since last time, and process changes.
if (inputState == active) {// no change
latchDelay = minReadCount; // Reset counter
return false; // no change
}
// Change detected ... has it stayed changed for long enough
if (latchDelay > 0) {
latchDelay--;
return false;
}
// change validated, act on it.
active = inputState;
latchDelay = minReadCount; // Reset debounce counter
if (onChange || active) {
new RMFT2(progCounter);
return true; // Don't check any more sensors on this entry
}
return false;
}
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) {
// Add to the start of the list
//DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter);
nextSensor = firstSensor;
firstSensor = this;
pin=_pin;
progCounter=_progCounter;
onChange=_onChange;
IODevice::configureInput(pin, true);
active = IODevice::read(pin);
inputState = active;
latchDelay = minReadCount;
}
EXRAILSensor *EXRAILSensor::firstSensor=NULL;
EXRAILSensor *EXRAILSensor::readingSensor=NULL;
unsigned long EXRAILSensor::lastReadCycle=0;

50
EXRAILSensor.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* © 2024 Chris Harlow
* 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 EXRAILSensor_h
#define EXRAILSensor_h
#include "IODevice.h"
class EXRAILSensor {
static EXRAILSensor * firstSensor;
static EXRAILSensor * readingSensor;
static unsigned long lastReadCycle;
public:
static void checkAll();
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange);
bool check();
private:
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
// should not be less than device scan cycle time.
static const byte minReadCount = 4; // number of additional scans before acting on change
// E.g. 1 means that a change is ignored for one scan and actioned on the next.
// Max value is 63
EXRAILSensor* nextSensor;
VPIN pin;
int progCounter;
bool active;
bool inputState;
bool onChange;
byte latchDelay;
};
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202402241957Z"
#define GITHUB_SHA "devel-202404061747Z"

View File

@@ -1,7 +1,9 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
* © 2024, Paul Antoine
* © 2023, Neil McKechnie
* All rights reserved.
*
* This file is part of DCC++EX API
* This file is part of DCC-EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -112,13 +114,14 @@ protected:
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Add device to list of HAL devices (not necessary but allows
// status to be displayed using <D HAL SHOW> and device to be
// reinitialised using <D HAL RESET>).
IODevice::addDevice(this);
// Moved after addDevice() to ensure I2CManager.begin() has been called fisrt
_displayDriver->clearNative();
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);

View File

@@ -42,9 +42,9 @@
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
*
* #include "IO_RotaryEncoder.h"
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
* HAL(RotaryEncoder, 700, 1, 0x67) // Define single Vpin, no feedback or position sent to rotary encoder software
* HAL(RotaryEncoder, 700, 2, 0x67) // Define two Vpins, feedback only sent to rotary encoder software
* HAL(RotaryEncoder, 700, 3, 0x67) // Define three Vpins, can send feedback and position update to rotary encoder software
*
* Refer to the documentation for further information including the valid activities and examples.
*/

View File

@@ -336,8 +336,6 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
if (tSpeed <= 1) brake = 255;
else if (tSpeed >= 127) brake = 0;
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
{ // new block because of variable f
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
@@ -349,12 +347,12 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
}
}
#endif
//DIAG(F("Brake pin %d freqency %d"), brakePin, f);
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else // all AVR here
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
analogWrite(brakePin,brake);
analogWrite(brakePin, invertBrake ? 255-brake : brake);
#endif
}
@@ -404,26 +402,26 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
void MotorDriver::throttleInrush(bool on) {
if (brakePin == UNUSED_PIN)
return;
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT | TRACK_MODE_BOOST)))
return;
byte duty = on ? 207 : 0; // duty of 81% at 62500Hz this gives pauses of 3usec
if (invertBrake)
duty = 255-duty;
#if defined(ARDUINO_ARCH_ESP32)
if(on) {
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
DCCTimer::DCCEXInrushControlOn(brakePin, duty, invertBrake);
} else {
ledcDetachPin(brakePin);
ledcDetachPin(brakePin); // not DCCTimer::DCCEXledcDetachPin() as we have not
// registered the pin in the pin to channel array
}
#elif defined(ARDUINO_ARCH_STM32)
if(on) {
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWrite(brakePin,duty,invertBrake);
} else {
pinMode(brakePin, OUTPUT);
}
#else // all AVR here
if (invertBrake)
duty = 255-duty;
if(on){
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
}

View File

@@ -193,13 +193,14 @@ class MotorDriver {
}
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
inline int8_t getBrakePinSigned() { return invertBrake ? -brakePin : brakePin; };
void setDCSignal(byte speedByte, uint8_t frequency=0);
void throttleInrush(bool on);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
#elif defined(ARDUINO_ARCH_ESP32)
ledcDetachPin(brakePin);
DCCTimer::DCCEXledcDetachPin(brakePin);
#else
setDCSignal(128);
#endif

View File

@@ -1,7 +1,7 @@
/*
* © 2022-2023 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
* © 2020-2024 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
@@ -57,6 +57,10 @@
// of the brake pin on the motor bridge is inverted
// (HIGH == release brake)
// You can have a CS wihout any possibility to do any track signal.
// That's strange but possible.
#define NO_SHIELD F("No shield at all")
// Arduino STANDARD Motor Shield, used on different architectures:
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)

View File

@@ -0,0 +1,38 @@
BLINK(vpin, onMs,offMs)
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
BLINK returns immediately, the blinking is autonomous.
This means a signal that always blinks amber could be done like this:
```
SIGNAL(30,31,32)
ONAMBER(30) BLINK(31,500,500) DONE
```
The RED or GREEN calls will turn off the amber blink automatically.
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
#define FLASHAMBER(signal) \
AMBER(signal) \
BLINK(signal+1,500,500)
(Caution: this issumes that the amber pin is redpin+1)
==
FTOGGLE(function)
Toggles the current loco function (see FON and FOFF)
XFTOGGLE(loco,function)
Toggles the function on given loco. (See XFON, XFOFF)
TOGGLE_TURNOUT(id)
Toggles the turnout (see CLOSE, THROW)
STEALTH_GLOBAL(code)
ADVANCED C++ users only.
Inserts code such as static variables and functions that
may be utilised by multiple STEALTH operations.

View File

@@ -0,0 +1,98 @@
BLINK(vpin, onMs,offMs)
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
BLINK returns immediately, the blinking is autonomous.
This means a signal that always blinks amber could be done like this:
SIGNAL(30,31,32)
ONAMBER(30) BLINK(31,500,500) DONE
The RED or GREEN calls will turn off the amber blink automatically.
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
#define FLASHAMBER(signal) \
AMBER(signal) \
BLINK(signal+1,500,500)
(Caution: this assumes that the amber pin is redpin+1)
==
FTOGGLE(function)
Toggles the current loco function (see FON and FOFF)
XFTOGGLE(loco,function)
Toggles the function on given loco. (See XFON, XFOFF)
TOGGLE_TURNOUT(id)
Toggles the turnout (see CLOSE, THROW)
STEALTH_GLOBAL(code)
ADVANCED C++ users only.
Inserts code such as static variables and functions that
may be utilised by multiple STEALTH operations.
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories.
This command sends an extended accessory packet to the track, Normally used to set
a signal aspect. Aspect numbers are undefined as sdtandards except for 0 which is
always considered a stop.
// - Exrail ASPECT(address,aspect) for above.
The ASPECT command sents an aspect to a DCC accessory using the same logic as
<A aspect address>.
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
This defines a signal (with id same as dcc address) that can be operated
by the RED/AMBER/GREEN commands. In each case the command uses the signal
address to refer to the signal and the aspect chosen depends on the use of the RED
AMBER or GREEN command sent. Other aspects may be sent but will require the
direct use of the ASPECT command.
The IFRED/IFAMBER/IFGREEN and ONRED/ONAMBER/ONGREEN commands contunue to operate
as for any other signal type. It is important to be aware that use of the ASPECT
or <A> commands will correctly set the IF flags and call the ON handlers if ASPECT
is used to set one of the three aspects defined in the DCCX_SIGNAL command.
Direct use of other aspects does not affect the signal flags.
ASPECT and <A> can be used withput defining any signal if tyhe flag management or
ON event handlers are not required.
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
This macro offsers a more convenient way of performing the HAL call in halSetup.h
In halSetup.h --- IODevice::configureServo(101,300,400,PCA9685::slow);
In myAutomation.h --- CONFIGURE_SERVO(101,300,400,slow)
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
This cutout will only work on a Mega2560 with a single EX8874 motor shield
configured in the normal way with the main track brake pin on pin 9.
<C RAILCOM ON> Turns on the cutout mechanism.
<C RAILCOM OFF> Tirns off the cutout. (This is the default)
<C RAILCOM DEBUG> ONLY to be used by developers used for waveform diagnostics.
(In DEBUG mode the main track idle packets are replaced with reset packets, This
makes it far easier to see the preambles and cutouts on a logic analyser or scope.)
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
This Macro causes the creation of JMRI <S> type sensors in a way that is
simpler than repeating lines of <S> commands.
JMRI_SENSOR(100) is equenvelant to <S 100 100 1>
JMRI_SENSOR(100,16) will create <S> type sensors for vpins 100-115.
// 5.2.26 - Silently ignore overridden HAL defaults
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
The HAL_IGNORE_DEFAULTS command, anywhere in myAutomation.h will
prevent the startup code from trying the default I2C sensors/servos.
// 5.2.24 - Exrail macro asserts to catch
// : duplicate/missing automation/route/sequence/call ids
// : latches and reserves out of range
// : speeds out of range
Causes compiler time messages for EXRAIL issues that would normally
only be discovered by things going wrong at run time.
// 5.2.13 - EXRAIL STEALTH
Permits a certain level of C++ code to be embedded as a single step in
an exrail sequence. Serious engineers only.
// 5.2.9 - EXRAIL STASH feature
// - Added ROUTE_DISABLED macro in EXRAIL

View File

@@ -1,6 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022,2023 Harald Barth
* © 2022-2024 Harald Barth
* © 2023 Colin Murdoch
* All rights reserved.
*
@@ -38,10 +38,10 @@
if (track[t]->getMode()==findmode) \
track[t]->function;
MotorDriver * TrackManager::track[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
byte TrackManager::lastTrack=0;
int8_t TrackManager::lastTrack=-1;
bool TrackManager::progTrackSyncMain=false;
bool TrackManager::progTrackBoosted=false;
int16_t TrackManager::joinRelay=UNUSED_PIN;
@@ -251,18 +251,47 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} else {
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
}
track[trackToSet]->setMode(mode);
trackDCAddr[trackToSet]=dcAddr;
// When a track is switched, we must clear any side effects of its previous
// state, otherwise trains run away or just dont move.
// This can be done BEFORE the PWM-Timer evaluation (methinks)
if (!(mode & TRACK_MODE_DC)) {
if (mode & TRACK_MODE_DC) {
if (trackDCAddr[trackToSet] != dcAddr) {
// new or changed DC Addr, run the new setup
if (trackDCAddr[trackToSet] != 0) {
// if we change dcAddr and not only
// change from another mode,
// first detach old DC signal
track[trackToSet]->detachDCSignal();
}
#ifdef ARDUINO_ARCH_ESP32
int trackfound = -1;
FOR_EACH_TRACK(t) {
//DIAG(F("Checking track %c mode %x dcAddr %d"), 'A'+t, track[t]->getMode(), trackDCAddr[t]);
if (t != trackToSet // not our track
&& (track[t]->getMode() & TRACK_MODE_DC) // right mode
&& trackDCAddr[t] == dcAddr) { // right addr
//DIAG(F("Found track %c"), 'A'+t);
trackfound = t;
break;
}
}
if (trackfound > -1) {
DCCTimer::DCCEXanalogCopyChannel(track[trackfound]->getBrakePinSigned(),
track[trackToSet]->getBrakePinSigned());
}
#endif
}
// set future DC Addr;
trackDCAddr[trackToSet]=dcAddr;
} else {
// DCC tracks need to have set the PWM to zero or they will not work.
track[trackToSet]->detachDCSignal();
track[trackToSet]->setBrake(false);
trackDCAddr[trackToSet]=0; // clear that an addr is set for DC as this is not a DC track
}
track[trackToSet]->setMode(mode);
// BOOST:
// Leave it as is
@@ -498,7 +527,11 @@ void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermod
// Set track power for this track, inependent of mode
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
MotorDriver *driver=track[t];
MotorDriver *driver=track[t];
if (driver == NULL) { // track is not defined at all
DIAG(F("Error: Track %c does not exist"), t+'A');
return;
}
TRACK_MODE trackmode = driver->getMode();
POWERMODE oldpower = driver->getPower();
if (trackmode & TRACK_MODE_NONE) {

View File

@@ -1,6 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2022-2024 Harald Barth
* © 2023 Colin Murdoch
*
* All rights reserved.
@@ -46,7 +46,7 @@ const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
class TrackManager {
public:
static void Setup(const FSH * shieldName,
MotorDriver * track0,
MotorDriver * track0=NULL,
MotorDriver * track1=NULL,
MotorDriver * track2=NULL,
MotorDriver * track3=NULL,
@@ -108,7 +108,7 @@ class TrackManager {
private:
static void addTrack(byte t, MotorDriver* driver);
static byte lastTrack;
static int8_t lastTrack;
static byte nextCycleTrack;
static void applyDCSpeed(byte t);

View File

@@ -571,7 +571,7 @@ void WiThrottle::sendRoutes(Print* stream) {
void WiThrottle::sendFunctions(Print* stream, byte loco) {
int16_t locoid=myLocos[loco].cab;
int fkeys=29;
int fkeys=32; // upper limit (send functions 0 to 31)
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE

View File

@@ -211,6 +211,19 @@ The configuration file for DCC-EX Command Station
// #define DISABLE_VDPY
// #define ENABLE_VDPY
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE / ENABLE DIAG
//
// To diagose different errors, you can turn on differnet messages. This costs
// program memory which we do not have enough on the Uno and Nano, so it is
// by default DISABLED on those. If you think you can fit it (for example
// having disabled some of the features above) you can enable it with
// ENABLE_DIAG. You can even disable it on all other CPUs with
// DISABLE_DIAG
//
// #define DISABLE_DIAG
// #define ENABLE_DIAG
/////////////////////////////////////////////////////////////////////////////////////
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
// is 127 and the first long address is 128. There are manufacturers which have

View File

@@ -220,9 +220,15 @@
//
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#define IO_NO_HAL // HAL too big whatever you disable otherwise
#ifndef ENABLE_VDPY
#define DISABLE_VDPY
#endif
#ifndef ENABLE_DIAG
#define DISABLE_DIAG
#endif
#endif
#if __has_include ( "myAutomation.h")

View File

@@ -311,12 +311,13 @@ void halSetup() {
//=======================================================================
// The parameters are:
// firstVpin = First available Vpin to allocate
// numPins= Number of Vpins to allocate, can be either 1 or 2
// i2cAddress = Available I2C address (default 0x70)
// numPins= Number of Vpins to allocate, can be either 1 to 3
// i2cAddress = Available I2C address (default 0x67)
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
//RotaryEncoder::create(700, 1, 0x70);
//RotaryEncoder::create(701, 2, 0x71);
//RotaryEncoder::create(700, 1, 0x67);
//RotaryEncoder::create(700, 2, 0x67);
//RotaryEncoder::create(700, 3, 0x67);
//=======================================================================
// The following directive defines an EX-FastClock instance.

View File

@@ -3,7 +3,23 @@
#include "StringFormatter.h"
#define VERSION "5.2.38"
#define VERSION "5.2.48"
// 5.2.48 - Bugfix: HALDisplay was generating I2C traffic prior to I2C being initialised
// 5.2.47 - EXRAIL additions:
// STEALTH_GLOBAL
// BLINK
// TOGGLE_TURNOUT
// FTOGGLE, XFTOGGLE
// Reduced code-developmenmt DIAG noise
// 5.2.46 - Support for extended consist CV20 in <R> and <W id>
// - New cmd <W CONSIST id [REVERSE]> to handle long/short consist ids
// 5.2.45 - ESP32 Trackmanager reset cab number to 0 when track is not DC
// ESP32 fix PWM LEDC inverted pin mode
// ESP32 rewrite PWM LEDC to use pin mux
// 5.2.42 - ESP32 Bugfix: Uninitialized stack variable
// 5.2.41 - Update rotary encoder default address to 0x67
// 5.2.40 - Allow no shield
// 5.2.39 - Functions for DC frequency: Use func up to F31
// 5.2.38 - Exrail MESSAGE("text") to send a user message to all
// connected throttles (uses <m "text"> and withrottle Hmtext.
// 5.2.37 - Bugfix ESP32: Use BOOSTER_INPUT define