mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
67 Commits
v5.1.10-De
...
devel-esp3
Author | SHA1 | Date | |
---|---|---|---|
|
a97afde72a | ||
|
503378f1bb | ||
|
582ff890f4 | ||
|
86ed8ff8a6 | ||
|
b3ba647b09 | ||
|
4c89b26c79 | ||
|
e8b9f80c8c | ||
|
befcfebec7 | ||
|
9ce95c07aa | ||
|
d8cc0c632a | ||
|
d877fc315e | ||
|
6c18226cb5 | ||
|
1c5f299b0e | ||
|
fb14fbd81b | ||
|
2f3d489f18 | ||
|
d2d7a5cd16 | ||
|
337af77a03 | ||
|
670645db4b | ||
|
a4eabf235e | ||
|
2cbcecf9e6 | ||
|
26cf28dff7 | ||
|
44351b83ae | ||
|
4e08177b7b | ||
|
f2ff1ba22a | ||
|
043e6fdb26 | ||
|
24e0f189e1 | ||
|
33b2820095 | ||
|
7b3b16b211 | ||
|
27a5f76a8d | ||
|
754bd99381 | ||
|
650e411a4f | ||
|
0978bb0c11 | ||
|
6eb7051fd6 | ||
|
5726844c83 | ||
|
0214a55b23 | ||
|
7db4a9575a | ||
|
8b8e9e4919 | ||
|
ce84974967 | ||
|
034c441c34 | ||
|
d5978b1578 | ||
|
ea4f90d5fc | ||
|
1181fd855d | ||
|
a092e06a6f | ||
|
68fd56e7fc | ||
|
370dae0ab8 | ||
|
bef4b2ec35 | ||
|
fe618d0b85 | ||
|
2ff1619ad1 | ||
|
7afd4443d6 | ||
|
52cfc18754 | ||
|
25bbfa4c68 | ||
|
2a46b96083 | ||
|
17c004aecf | ||
|
9e3ae21bb8 | ||
|
9333beda49 | ||
|
aacb980dc8 | ||
|
8052090e0f | ||
|
46289fa78c | ||
|
b3cafd126e | ||
|
c55fa9f9d2 | ||
|
210d96a3e3 | ||
|
42f3c7c128 | ||
|
6cd7002e91 | ||
|
085762e800 | ||
|
2db2b0ecc6 | ||
|
fd58a749ef | ||
|
3bddf4dfd1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@ myFilter.cpp
|
||||
my*.h
|
||||
!my*.example.h
|
||||
compile_commands.json
|
||||
newcode.txt.old
|
||||
UserAddin.txt
|
||||
|
@@ -162,7 +162,7 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
|
||||
broadcastReply(COMMAND_TYPE, F("<i %d %d %d>\n"), id, position, moving);
|
||||
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
@@ -248,6 +248,11 @@ void CommandDistributor::broadcastLoco(byte slot) {
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
char pstr[] = "? x";
|
||||
for(byte t=0; t<8; t++)
|
||||
if (TrackManager::getPower(t, pstr))
|
||||
broadcastReply(COMMAND_TYPE, F("<p%s>\n"),pstr);
|
||||
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
bool join=TrackManager::isJoined();
|
||||
@@ -269,6 +274,14 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
|
||||
}
|
||||
|
@@ -55,9 +55,12 @@ public :
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastRaw(clientType type,char * msg);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter, 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);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
246
DCCEXParser.cpp
246
DCCEXParser.cpp
@@ -49,7 +49,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
C,
|
||||
C, configure the CS
|
||||
d,
|
||||
D, Diagnostic commands
|
||||
e, Erase EEPROM
|
||||
@@ -60,14 +60,14 @@ Once a new OPCODE is decided upon, update this list.
|
||||
G,
|
||||
h,
|
||||
H, Turnout state broadcast
|
||||
i, Reserved for future use - Turntable object broadcast
|
||||
I, Reserved for future use - Turntable object command and control
|
||||
i, Server details string
|
||||
I, Turntable object command, control, and broadcast
|
||||
j, Throttle responses
|
||||
J, Throttle queries
|
||||
k, Reserved for future use - Potentially Railcom
|
||||
K, Reserved for future use - Potentially Railcom
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L,
|
||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||
m,
|
||||
M, Write DCC packet
|
||||
n,
|
||||
@@ -122,7 +122,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==INT16_MAX) break; \
|
||||
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_G='G';
|
||||
const int16_t HASH_KEYWORD_H='H';
|
||||
const int16_t HASH_KEYWORD_I='I';
|
||||
const int16_t HASH_KEYWORD_O='O';
|
||||
const int16_t HASH_KEYWORD_P='P';
|
||||
@@ -552,70 +553,67 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
TrackManager::setJoin(true);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setTrackPower(POWERMODE::ON, t);
|
||||
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
//TrackManager::streamTrackState(NULL,t);
|
||||
|
||||
return;
|
||||
}
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, t);
|
||||
//StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
return;
|
||||
@@ -630,7 +628,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
case 's': // STATUS <s>
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
@@ -651,13 +649,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case ' ': // < >
|
||||
StringFormatter::send(stream, F("\n"));
|
||||
return;
|
||||
case 'C': // CONFIG <C [params]>
|
||||
if (parseC(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
#ifndef DISABLE_DIAG
|
||||
case 'D': // < >
|
||||
case 'D': // DIAG <D [params]>
|
||||
if (parseD(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
#endif
|
||||
case '=': // <= Track manager control >
|
||||
case '=': // TACK MANAGER CONTROL <= [params]>
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
@@ -714,27 +716,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
||||
id,
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getRouteType(id), // A/R
|
||||
RMFT2::getRouteDescription(id)
|
||||
#else
|
||||
'X',F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||
if (params!=1) break; // <JA>
|
||||
StringFormatter::send(stream, F("<jA>\n"));
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
@@ -742,11 +728,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else {
|
||||
const FSH * functionNames= RMFT2::getRosterFunctions(id);
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id),
|
||||
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
|
||||
}
|
||||
auto rosterName= RMFT2::getRosterName(id);
|
||||
if (!rosterName) rosterName=F("");
|
||||
|
||||
auto functionNames= RMFT2::getRosterFunctions(id);
|
||||
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
|
||||
if (!functionNames) functionNames=F("");
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, rosterName, functionNames);
|
||||
}
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -839,6 +829,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'L': // LCC interface implemented in EXRAIL parser
|
||||
break; // Will <X> if not intercepted by EXRAIL
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||
for (int i = 0; i < params; i++)
|
||||
@@ -1044,19 +1037,27 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||
if (params == 0)
|
||||
return false;
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_RESET:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
DIAG(F("28 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
DIAG(F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
@@ -1076,12 +1077,35 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
|
||||
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
|
||||
Diag::ACK = onOff;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
if (params == 0)
|
||||
return false;
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||
Diag::CMD = onOff;
|
||||
return true;
|
||||
@@ -1103,34 +1127,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_RESET:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
if (params >= 2)
|
||||
EEStore::dump(p[1]);
|
||||
return true;
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
StringFormatter::send(stream, F("28 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
StringFormatter::send(stream, F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
||||
|
||||
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
@@ -1153,7 +1157,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
return parseC(stream, params, p);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1181,7 +1185,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (tto) {
|
||||
bool type = tto->isEXTT();
|
||||
uint8_t position = tto->getPosition();
|
||||
StringFormatter::send(stream, F("<i %d %d>\n"), type, position);
|
||||
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1207,7 +1211,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (!DCCTurntable::create(p[0])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
tto->addPosition(0, 0, p[2]);
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
} else {
|
||||
if (!tto) return false;
|
||||
if (!tto->isEXTT()) return false;
|
||||
@@ -1224,7 +1228,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
tto->addPosition(0, 0, p[3]);
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1238,7 +1242,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
// tto must exist, no more than 48 positions, angle 0 - 3600
|
||||
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
|
||||
tto->addPosition(p[2], p[3], p[4]);
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ struct DCCEXParser
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
|
@@ -83,16 +83,34 @@ int DCCTimer::freeMemory() {
|
||||
#include <soc/sens_struct.h>
|
||||
#undef ADC_INPUT_MAX_VALUE
|
||||
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) < 11) ? (X)-2 : (X)-11)
|
||||
#else
|
||||
#warning This ESP32 variant is not supported!
|
||||
#endif
|
||||
|
||||
int IRAM_ATTR local_adc1_get_raw(int channel) {
|
||||
uint16_t adc_value;
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas_start1.meas1_start_sar = 0;
|
||||
SENS.sar_meas_start1.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas_start1.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas_start1.meas1_data_sar;
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
// Between variants... Espressif decides to rename things... sigh
|
||||
SENS.sar_meas1_ctrl2.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas1_ctrl2.meas1_start_sar = 0;
|
||||
SENS.sar_meas1_ctrl2.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas1_ctrl2.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas1_ctrl2.meas1_data_sar;
|
||||
#else
|
||||
#warning This ESP32 variant is not supported!
|
||||
#endif
|
||||
return adc_value;
|
||||
}
|
||||
|
||||
|
@@ -306,18 +306,18 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
// Calculate percentage duty cycle from value given
|
||||
uint32_t duty_cycle = (value * 100 / 256) + 1;
|
||||
if (pin_timer[pin] != NULL) {
|
||||
if (duty_cycle == 100)
|
||||
{
|
||||
pin_timer[pin]->pauseChannel(pin_channel[pin]);
|
||||
DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if (duty_cycle == 100)
|
||||
// {
|
||||
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
|
||||
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
pin_timer[pin]->resumeChannel(pin_channel[pin]);
|
||||
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
|
||||
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||
}
|
||||
// }
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||
|
360
EXRAIL2.cpp
360
EXRAIL2.cpp
@@ -55,22 +55,6 @@
|
||||
#include "Turntables.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_START=23232;
|
||||
const int16_t HASH_KEYWORD_RESERVE=11392;
|
||||
const int16_t HASH_KEYWORD_FREE=-23052;
|
||||
const int16_t HASH_KEYWORD_LATCH=1618;
|
||||
const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||
const int16_t HASH_KEYWORD_KILL=5218;
|
||||
const int16_t HASH_KEYWORD_ALL=3457;
|
||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||
const int16_t HASH_KEYWORD_RED=26099;
|
||||
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||
|
||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
|
||||
@@ -85,8 +69,8 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
|
||||
// when pausingTask is set, that is the ONLY task that gets any service,
|
||||
// and all others will have their locos stopped, then resumed after the pausing task resumes.
|
||||
byte RMFT2::flags[MAX_FLAGS];
|
||||
|
||||
LookList * RMFT2::sequenceLookup=NULL;
|
||||
Print * RMFT2::LCCSerial=0;
|
||||
LookList * RMFT2::routeLookup=NULL;
|
||||
LookList * RMFT2::onThrowLookup=NULL;
|
||||
LookList * RMFT2::onCloseLookup=NULL;
|
||||
LookList * RMFT2::onActivateLookup=NULL;
|
||||
@@ -100,9 +84,8 @@ LookList * RMFT2::onClockLookup=NULL;
|
||||
LookList * RMFT2::onRotateLookup=NULL;
|
||||
#endif
|
||||
LookList * RMFT2::onOverloadLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
byte * RMFT2::routeStateArray=nullptr;
|
||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||
|
||||
// getOperand instance version, uses progCounter from instance.
|
||||
uint16_t RMFT2::getOperand(byte n) {
|
||||
@@ -120,6 +103,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||
LookList::LookList(int16_t size) {
|
||||
m_size=size;
|
||||
m_loaded=0;
|
||||
m_chain=nullptr;
|
||||
if (size) {
|
||||
m_lookupArray=new int16_t[size];
|
||||
m_resultArray=new int16_t[size];
|
||||
@@ -137,8 +121,35 @@ int16_t LookList::find(int16_t value) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
if (m_lookupArray[i]==value) return m_resultArray[i];
|
||||
}
|
||||
return m_chain ? m_chain->find(value) :-1;
|
||||
}
|
||||
void LookList::chain(LookList * chain) {
|
||||
m_chain=chain;
|
||||
}
|
||||
void LookList::handleEvent(const FSH* reason,int16_t id) {
|
||||
// New feature... create multiple ONhandlers
|
||||
for (int i=0;i<m_size;i++)
|
||||
if (m_lookupArray[i]==id)
|
||||
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
|
||||
}
|
||||
|
||||
|
||||
void LookList::stream(Print * _stream) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
_stream->print(" ");
|
||||
_stream->print(m_lookupArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t LookList::findPosition(int16_t value) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
if (m_lookupArray[i]==value) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int16_t LookList::size() {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
int progCounter;
|
||||
@@ -171,28 +182,36 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
|
||||
// create lookups
|
||||
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
|
||||
routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION);
|
||||
routeLookup->chain(LookListLoader(OPCODE_SEQUENCE));
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte));
|
||||
routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *));
|
||||
}
|
||||
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
|
||||
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
|
||||
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
|
||||
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
#ifndef IO_NO_HAL
|
||||
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
||||
#endif
|
||||
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
||||
// onLCCLookup is not the same so not loaded here.
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
if (compileFeatures & FEATURE_SIGNAL) {
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
}
|
||||
|
||||
int progCounter;
|
||||
for (progCounter=0;; SKIPOP){
|
||||
@@ -311,184 +330,15 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) {
|
||||
#endif
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'R';
|
||||
}
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'A';
|
||||
int16_t progCounter=routeLookup->find(id);
|
||||
if (progCounter>=0) {
|
||||
byte type=GET_OPCODE;
|
||||
if (type==OPCODE_ROUTE) return 'R';
|
||||
if (type==OPCODE_AUTOMATION) return 'A';
|
||||
}
|
||||
return 'X';
|
||||
}
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
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':
|
||||
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
reject=!parseSlash(stream,paramCount,p);
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
default: // other commands pass through
|
||||
break;
|
||||
}
|
||||
if (reject) {
|
||||
opcode=0;
|
||||
StringFormatter::send(stream,F("<X>"));
|
||||
}
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
if (paramCount==0) { // STATUS
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
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;
|
||||
}
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_PAUSE: // </ PAUSE>
|
||||
if (paramCount!=1) return false;
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESUME: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->loco) task->driveLoco(task->speedo);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
case HASH_KEYWORD_START: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||
int pc=sequenceLookup->find(route);
|
||||
if (pc<0) return false;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This emits Routes and Automations to Withrottle
|
||||
// Automations are given a state to set the button to "handoff" which implies
|
||||
// handing over the loco to the automation.
|
||||
// Routes are given "Set" buttons and do not cause the loco to be handed over.
|
||||
|
||||
|
||||
|
||||
RMFT2::RMFT2(int progCtr) {
|
||||
progCounter=progCtr;
|
||||
@@ -537,7 +387,7 @@ RMFT2::~RMFT2() {
|
||||
}
|
||||
|
||||
void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||
int pc=sequenceLookup->find(route);
|
||||
int pc=routeLookup->find(route);
|
||||
if (pc<0) return;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
@@ -780,13 +630,27 @@ void RMFT2::loop2() {
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_SET_POWER:
|
||||
// operand is TRACK_POWER , trackid
|
||||
//byte thistrack=getOperand(1);
|
||||
switch (operand) {
|
||||
case TRACK_POWER_0:
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1));
|
||||
break;
|
||||
case TRACK_POWER_1:
|
||||
TrackManager::setTrackPower(POWERMODE::ON, getOperand(1));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OPCODE_SET_TRACK:
|
||||
// operand is trackmode<<8 | track id
|
||||
// If DC/DCX use my loco for DC address
|
||||
{
|
||||
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
|
||||
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
|
||||
int16_t cab=(mode & TRACK_MODE_DC) ? loco : 0;
|
||||
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
|
||||
}
|
||||
break;
|
||||
@@ -924,7 +788,7 @@ void RMFT2::loop2() {
|
||||
}
|
||||
|
||||
case OPCODE_FOLLOW:
|
||||
progCounter=sequenceLookup->find(operand);
|
||||
progCounter=routeLookup->find(operand);
|
||||
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
|
||||
return;
|
||||
|
||||
@@ -934,7 +798,7 @@ void RMFT2::loop2() {
|
||||
return;
|
||||
}
|
||||
callStack[stackDepth++]=progCounter+3;
|
||||
progCounter=sequenceLookup->find(operand);
|
||||
progCounter=routeLookup->find(operand);
|
||||
if (progCounter<0) kill(F("CALL unknown"),operand);
|
||||
return;
|
||||
|
||||
@@ -997,7 +861,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_START:
|
||||
{
|
||||
int newPc=sequenceLookup->find(operand);
|
||||
int newPc=routeLookup->find(operand);
|
||||
if (newPc<0) break;
|
||||
new RMFT2(newPc);
|
||||
}
|
||||
@@ -1005,7 +869,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_SENDLOCO: // cab, route
|
||||
{
|
||||
int newPc=sequenceLookup->find(getOperand(1));
|
||||
int newPc=routeLookup->find(getOperand(1));
|
||||
if (newPc<0) break;
|
||||
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||
newtask->loco=operand;
|
||||
@@ -1020,7 +884,21 @@ void RMFT2::loop2() {
|
||||
invert=false;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_LCC: // short form LCC
|
||||
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
|
||||
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
|
||||
break;
|
||||
|
||||
case OPCODE_LCCX: // long form LCC
|
||||
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
|
||||
StringFormatter::send(LCCSerial,F("<L x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
break;
|
||||
|
||||
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
|
||||
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
|
||||
@@ -1045,7 +923,16 @@ void RMFT2::loop2() {
|
||||
case OPCODE_PRINT:
|
||||
printMessage(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_ROUTE_HIDDEN:
|
||||
manageRouteState(operand,2);
|
||||
break;
|
||||
case OPCODE_ROUTE_ACTIVE:
|
||||
manageRouteState(operand,0);
|
||||
break;
|
||||
case OPCODE_ROUTE_INACTIVE:
|
||||
manageRouteState(operand,1);
|
||||
break;
|
||||
|
||||
case OPCODE_ROUTE:
|
||||
case OPCODE_AUTOMATION:
|
||||
case OPCODE_SEQUENCE:
|
||||
@@ -1058,6 +945,7 @@ void RMFT2::loop2() {
|
||||
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
|
||||
case OPCODE_ONLCC: // LCC event catchers ignored here
|
||||
case OPCODE_ONTHROW:
|
||||
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
@@ -1127,13 +1015,14 @@ 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);
|
||||
|
||||
// Schedule any event handler for this signal change.
|
||||
// Thjis will work even without a signal definition.
|
||||
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
|
||||
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
|
||||
else handleEvent(F("AMBER"), onAmberLookup,id);
|
||||
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
|
||||
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
|
||||
else onAmberLookup->handleEvent(F("AMBER"),id);
|
||||
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return;
|
||||
@@ -1194,6 +1083,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return false;
|
||||
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||
@@ -1201,26 +1091,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
|
||||
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
|
||||
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
|
||||
else onThrowLookup->handleEvent(F("THROW"),turnoutId);
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
|
||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
||||
if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
|
||||
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
|
||||
}
|
||||
|
||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||
// Hunt for an ONCHANGE for this sensor
|
||||
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
|
||||
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
|
||||
}
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
|
||||
// Hunt or an ONROTATE for this turntable
|
||||
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
|
||||
if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1229,8 +1119,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||
if (change) {
|
||||
handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
|
||||
onClockLookup->handleEvent(F("CLOCK"),clocktime);
|
||||
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,15 +1129,11 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for Power event on track : %c"), track);
|
||||
if (overload) {
|
||||
handleEvent(F("POWER"),onOverloadLookup,track);
|
||||
onOverloadLookup->handleEvent(F("POWER"),track);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) 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;
|
||||
while(task) {
|
||||
@@ -1363,3 +1249,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::manageRouteState(uint16_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
|
||||
int16_t position=routeLookup->findPosition(id);
|
||||
if (position<0) return;
|
||||
// set state beside it
|
||||
if (routeStateArray[position]==state) return;
|
||||
routeStateArray[position]=state;
|
||||
CommandDistributor::broadcastRouteState(id,state);
|
||||
}
|
||||
}
|
||||
void RMFT2::manageRouteCaption(uint16_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
|
||||
int16_t position=routeLookup->findPosition(id);
|
||||
if (position<0) return;
|
||||
// set state beside it
|
||||
if (routeCaptionArray[position]==caption) return;
|
||||
routeCaptionArray[position]=caption;
|
||||
CommandDistributor::broadcastRouteCaption(id,caption);
|
||||
}
|
||||
}
|
||||
|
||||
|
48
EXRAIL2.h
48
EXRAIL2.h
@@ -59,16 +59,16 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_SET_TRACK,OPCODE_SET_POWER,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
#ifndef IO_NO_HAL
|
||||
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
|
||||
#endif
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
|
||||
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
|
||||
OPCODE_ONOVERLOAD,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -81,7 +81,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO
|
||||
OPCODE_IFLOCO,
|
||||
OPCODE_IFTTPOSITION
|
||||
};
|
||||
|
||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||
@@ -95,7 +96,12 @@ enum thrunger: byte {
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
static const byte SECTION_FLAG = 0x80;
|
||||
@@ -115,13 +121,20 @@ enum thrunger: byte {
|
||||
class LookList {
|
||||
public:
|
||||
LookList(int16_t size);
|
||||
void chain(LookList* chainTo);
|
||||
void add(int16_t lookup, int16_t result);
|
||||
int16_t find(int16_t value);
|
||||
int16_t find(int16_t value); // finds result value
|
||||
int16_t findPosition(int16_t value); // finds index
|
||||
int16_t size();
|
||||
void stream(Print * _stream);
|
||||
void handleEvent(const FSH* reason,int16_t id);
|
||||
|
||||
private:
|
||||
int16_t m_size;
|
||||
int16_t m_loaded;
|
||||
int16_t * m_lookupArray;
|
||||
int16_t * m_resultArray;
|
||||
int16_t * m_resultArray;
|
||||
LookList* m_chain;
|
||||
};
|
||||
|
||||
class RMFT2 {
|
||||
@@ -155,7 +168,8 @@ class LookList {
|
||||
static const FSH * getRosterFunctions(int16_t id);
|
||||
static const FSH * getTurntableDescription(int16_t id);
|
||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||
|
||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
@@ -172,7 +186,6 @@ private:
|
||||
#endif
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
@@ -192,7 +205,8 @@ private:
|
||||
static const HIGHFLASH byte RouteCode[];
|
||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||
static byte flags[MAX_FLAGS];
|
||||
static LookList * sequenceLookup;
|
||||
static Print * LCCSerial;
|
||||
static LookList * routeLookup;
|
||||
static LookList * onThrowLookup;
|
||||
static LookList * onCloseLookup;
|
||||
static LookList * onActivateLookup;
|
||||
@@ -206,6 +220,14 @@ private:
|
||||
static LookList * onRotateLookup;
|
||||
#endif
|
||||
static LookList * onOverloadLookup;
|
||||
|
||||
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 byte * routeStateArray;
|
||||
static const FSH ** routeCaptionArray;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
@@ -227,4 +249,8 @@ private:
|
||||
byte stackDepth;
|
||||
int callStack[MAX_STACK_DEPTH];
|
||||
};
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
#endif
|
||||
|
@@ -86,6 +86,8 @@
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef SCREEN
|
||||
#undef LCC
|
||||
#undef LCCX
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef ONACTIVATE
|
||||
@@ -94,6 +96,7 @@
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONLCC
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
@@ -123,6 +126,10 @@
|
||||
#undef ROTATE
|
||||
#undef ROTATE_DCC
|
||||
#undef ROUTE
|
||||
#undef ROUTE_ACTIVE
|
||||
#undef ROUTE_INACTIVE
|
||||
#undef ROUTE_HIDDEN
|
||||
#undef ROUTE_CAPTION
|
||||
#undef SENDLOCO
|
||||
#undef SEQUENCE
|
||||
#undef SERIAL
|
||||
@@ -138,6 +145,7 @@
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SET_POWER
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
@@ -192,7 +200,7 @@
|
||||
#define ENDTASK
|
||||
#define ESTOP
|
||||
#define EXRAIL
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description)
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
@@ -220,7 +228,9 @@
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LATCH(sensor_id)
|
||||
#define LCC(eventid)
|
||||
#define LCCX(senderid,eventid)
|
||||
#define LCD(row,msg)
|
||||
#define SCREEN(display,row,msg)
|
||||
#define LCN(msg)
|
||||
@@ -235,6 +245,7 @@
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONLCC(sender,event)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONROTATE(turntable_id)
|
||||
@@ -260,6 +271,10 @@
|
||||
#define ROTATE_DCC(turntable_id,position)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define ROUTE(id,description)
|
||||
#define ROUTE_ACTIVE(id)
|
||||
#define ROUTE_INACTIVE(id)
|
||||
#define ROUTE_HIDDEN(id)
|
||||
#define ROUTE_CAPTION(id,caption)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
#define SERIAL(msg)
|
||||
@@ -275,6 +290,7 @@
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
|
292
EXRAIL2Parser.cpp
Normal file
292
EXRAIL2Parser.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// THIS file is an extension of the RMFT2 class
|
||||
// normally found in EXRAIL2.cpp
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_START=23232;
|
||||
const int16_t HASH_KEYWORD_RESERVE=11392;
|
||||
const int16_t HASH_KEYWORD_FREE=-23052;
|
||||
const int16_t HASH_KEYWORD_LATCH=1618;
|
||||
const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||
const int16_t HASH_KEYWORD_KILL=5218;
|
||||
const int16_t HASH_KEYWORD_ALL=3457;
|
||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||
const int16_t HASH_KEYWORD_RED=26099;
|
||||
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
|
||||
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':
|
||||
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
reject=!parseSlash(stream,paramCount,p);
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
// This entire code block is compiled out if LLC macros not used
|
||||
if (!(compileFeatures & FEATURE_LCC)) return;
|
||||
|
||||
if (paramCount==0) { //<L> LCC adapter introducing self
|
||||
LCCSerial=stream; // now we know where to send events we raise
|
||||
|
||||
// loop through all possible sent events
|
||||
for (int progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
|
||||
if (opcode==OPCODE_LCCX) { // long form LCC
|
||||
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
}}
|
||||
|
||||
// we stream the hex events we wish to listen to
|
||||
// and at the same time build the event index looku.
|
||||
|
||||
|
||||
int eventIndex=0;
|
||||
for (int progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==OPCODE_ONLCC) {
|
||||
onLCCLookup[eventIndex]=progCounter; // TODO skip...
|
||||
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
|
||||
eventIndex,
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
eventIndex++;
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
|
||||
opcode=0;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'J': // throttle info commands
|
||||
// This entire code block is compiled out if FEATURE_ROUTESTATE macros not used
|
||||
if (paramCount<1) return;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
if (paramCount==1) {// <JA>
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
routeLookup->stream(stream);
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
opcode=0;
|
||||
return;
|
||||
}
|
||||
if (paramCount==2) { // <JA id>
|
||||
uint16_t id=p[1];
|
||||
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
|
||||
id, getRouteType(id), getRouteDescription(id));
|
||||
|
||||
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||
// Send any non-default button states or captions
|
||||
int16_t statePos=routeLookup->findPosition(id);
|
||||
if (statePos>=0) {
|
||||
if (routeStateArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
|
||||
if (routeCaptionArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
|
||||
}
|
||||
}
|
||||
opcode=0;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default: // other commands pass through
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
if (paramCount==0) { // STATUS
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
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;
|
||||
}
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
|
||||
if (compileFeatures & FEATURE_SIGNAL) {
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_PAUSE: // </ PAUSE>
|
||||
if (paramCount!=1) return false;
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESUME: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->loco) task->driveLoco(task->speedo);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
case HASH_KEYWORD_START: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||
int pc=routeLookup->find(route);
|
||||
if (pc<0) return false;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -63,6 +63,11 @@
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -74,12 +79,42 @@
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
|
||||
// Pass 1c detect compile time featurtes
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef SIGNAL
|
||||
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||
#undef SIGNALH
|
||||
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||
#undef SERVO_SIGNAL
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
|
||||
|
||||
#undef LCC
|
||||
#define LCC(eventid) | FEATURE_LCC
|
||||
#undef LCCX
|
||||
#define LCCX(senderid,eventid) | FEATURE_LCC
|
||||
#undef ONLCC
|
||||
#define ONLCC(senderid,eventid) | FEATURE_LCC
|
||||
#undef ROUTE_ACTIVE
|
||||
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_INACTIVE
|
||||
#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_HIDDEN
|
||||
#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
|
||||
|
||||
const byte RMFT2::compileFeatures = 0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 2 create throttle route list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
@@ -126,6 +161,12 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
manageRouteCaption(id,F(caption));\
|
||||
return;\
|
||||
}
|
||||
#undef SERIAL
|
||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||
#undef SERIAL1
|
||||
@@ -177,6 +218,8 @@ void RMFT2::printMessage(uint16_t id) {
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TURNOUT
|
||||
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||
#undef TURNOUTL
|
||||
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
|
||||
#undef PIN_TURNOUT
|
||||
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||
#undef SERVO_TURNOUT
|
||||
@@ -197,7 +240,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
#undef DCC_TURNTABLE
|
||||
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description)
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
|
||||
|
||||
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||
switch (turntableId) {
|
||||
@@ -273,6 +316,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0,0 };
|
||||
|
||||
// Pass 9 ONLCC counter and lookup array
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ONLCC
|
||||
#define ONLCC(sender,event) +1
|
||||
|
||||
const int RMFT2::countLCCLookup=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
|
||||
// Last Pass : create main routes table
|
||||
// Only undef the macros, not dummy them.
|
||||
#define RMFT2_UNDEF_ONLY
|
||||
@@ -317,7 +370,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ESTOP OPCODE_SPEED,V(1),
|
||||
#define EXRAIL
|
||||
#ifndef IO_NO_HAL
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home),
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
@@ -349,6 +402,11 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCC(eventid) OPCODE_LCC,V(eventid),
|
||||
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
@@ -357,6 +415,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||
@@ -392,6 +454,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
|
||||
#endif
|
||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
||||
#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id),
|
||||
#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id),
|
||||
#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id),
|
||||
#define ROUTE_CAPTION(id,caption) PRINT(caption)
|
||||
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||
#define SERIAL(msg) PRINT(msg)
|
||||
@@ -407,11 +473,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202309241855Z"
|
||||
#define GITHUB_SHA "devel-202311132306Z"
|
||||
|
@@ -117,35 +117,46 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Disable the I2C device, as TRISE can only be programmed whilst disabled
|
||||
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
if (i2cClockSpeed > 100000L)
|
||||
if (i2cClockSpeed > 100000UL)
|
||||
{
|
||||
if (i2cClockSpeed > 400000L)
|
||||
i2cClockSpeed = 400000L;
|
||||
// if (i2cClockSpeed > 400000L)
|
||||
// i2cClockSpeed = 400000L;
|
||||
|
||||
t_rise = 300; // nanoseconds
|
||||
}
|
||||
else
|
||||
{
|
||||
i2cClockSpeed = 100000L;
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 1000; // nanoseconds
|
||||
}
|
||||
// Configure the rise time register
|
||||
s->TRISE = (t_rise / (1000 / i2c_MHz)) + 1;
|
||||
// Configure the rise time register - max allowed tRISE is 1000ns,
|
||||
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
|
||||
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle (use 2:1)
|
||||
// Bit 11-0: FREQR
|
||||
if (i2cClockSpeed > 100000L) {
|
||||
// In fast mode, I2C period is 3 * CCR * TPCLK1.
|
||||
//APB1clk1 / 3 / i2cClockSpeed = 38, but that results in 306KHz not 400!
|
||||
ccr_freq = 30; // So 30 gives 396KHz or so!
|
||||
s->CCR = (uint16_t)(ccr_freq | 0x8000); // We need Fast Mode set
|
||||
} else {
|
||||
// In standard mode, I2C period is 2 * CCR * TPCLK1
|
||||
ccr_freq = (APB1clk1 / 2 / i2cClockSpeed); // Should be 225 for 45Mhz APB1 clock
|
||||
s->CCR |= (uint16_t)ccr_freq;
|
||||
}
|
||||
// if (i2cClockSpeed > 400000UL) {
|
||||
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
|
||||
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
|
||||
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
|
||||
// } else {
|
||||
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
|
||||
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
|
||||
// if (i2cClockSpeed > 100000UL)
|
||||
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
|
||||
// }
|
||||
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
@@ -159,6 +170,7 @@ void I2CManagerClass::I2C_init()
|
||||
// Query the clockspeed from the STM32 HAL layer
|
||||
APB1clk1 = HAL_RCC_GetPCLK1Freq();
|
||||
i2c_MHz = APB1clk1 / 1000000UL;
|
||||
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
|
||||
// Enable clocks
|
||||
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
|
||||
// Reset the I2C1 peripheral to initial state
|
||||
@@ -181,6 +193,7 @@ void I2CManagerClass::I2C_init()
|
||||
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
||||
|
||||
// Software reset the I2C peripheral
|
||||
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
@@ -191,6 +204,7 @@ void I2CManagerClass::I2C_init()
|
||||
// Set I2C peripheral clock frequency
|
||||
// s->CR2 |= I2C_PERIPH_CLK;
|
||||
s->CR2 |= i2c_MHz;
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
|
||||
|
||||
// set own address to 00 - not used in master mode
|
||||
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
|
||||
@@ -214,6 +228,7 @@ void I2CManagerClass::I2C_init()
|
||||
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
|
||||
#endif
|
||||
|
||||
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
|
||||
// Calculate baudrate and set default rate for now
|
||||
// Configure the Clock Control Register for 100KHz SCL frequency
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
@@ -221,12 +236,14 @@ void I2CManagerClass::I2C_init()
|
||||
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
|
||||
// s->CCR = I2C_PERIPH_CLK * 5;
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // i2c_MHz * 5;
|
||||
// s->CCR = i2c_MHz * 5;
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
|
||||
|
||||
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
// s->TRISE = I2C_PERIPH_CLK + 1; // 1000 ns / 50 ns = 20 + 1 = 21
|
||||
s->TRISE = i2c_MHz + 1;
|
||||
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
|
||||
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
|
@@ -605,6 +605,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||
}
|
||||
setPower(POWERMODE::ALERT);
|
||||
if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
|
||||
DIAG(F("TRACK %c INVERT"), trackno + 'A');
|
||||
invertOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// all well
|
||||
@@ -676,8 +680,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
DIAG(F("Calling EXRAIL"));
|
||||
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
||||
#endif
|
||||
// power on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
|
@@ -3,7 +3,7 @@
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2022,2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -28,8 +28,15 @@
|
||||
#include "DCCTimer.h"
|
||||
|
||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||
// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV)
|
||||
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
|
||||
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
|
||||
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
|
||||
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
|
||||
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32,
|
||||
TRACK_MODE_ALL = 62, // only to operate all tracks
|
||||
TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
@@ -148,7 +155,9 @@ class MotorDriver {
|
||||
// otherwise the call from interrupt context can undo whatever we do
|
||||
// from outside interrupt
|
||||
void setBrake( bool on, bool interruptContext=false);
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (invertPhase)
|
||||
high = !high;
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
@@ -168,6 +177,12 @@ class MotorDriver {
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
if (signalPin2 != UNUSED_PIN) {
|
||||
if (on)
|
||||
pinMode(signalPin2, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin2, INPUT);
|
||||
}
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
@@ -232,6 +247,32 @@ class MotorDriver {
|
||||
#endif
|
||||
inline void setMode(TRACK_MODE m) {
|
||||
trackMode = m;
|
||||
invertOutput(trackMode & TRACK_MODE_INV);
|
||||
};
|
||||
inline void invertOutput() { // toggles output inversion
|
||||
invertPhase = !invertPhase;
|
||||
invertOutput(invertPhase);
|
||||
};
|
||||
inline void invertOutput(bool b) { // sets output inverted or not
|
||||
if (b)
|
||||
invertPhase = 1;
|
||||
else
|
||||
invertPhase = 0;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
pinpair p = getSignalPin();
|
||||
uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin);
|
||||
if (invertPhase) // set or clear the invert bit in the gpio out register
|
||||
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
else
|
||||
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin);
|
||||
if (invertPhase) // clear or set the invert bit in the gpio out register
|
||||
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
else
|
||||
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
inline TRACK_MODE getMode() {
|
||||
return trackMode;
|
||||
@@ -263,7 +304,7 @@ class MotorDriver {
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
bool invertFault; // fault pin passed as negative means pin is inverted
|
||||
|
||||
bool invertPhase = 0; // phase of out pin is inverted
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
//
|
||||
|
@@ -117,6 +117,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
|
||||
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
||||
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
|
||||
case 'M':
|
||||
{ // this prints a unsigned long microseconds time in readable format
|
||||
unsigned long time = va_arg(args, long);
|
||||
@@ -218,4 +219,15 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
|
||||
if (!formatLeft) stream->print(value, DEC);
|
||||
}
|
||||
|
||||
|
||||
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
|
||||
const char FLASH hexchars[]="0123456789ABCDEF";
|
||||
void StringFormatter::printHex(Print * stream,uint16_t value) {
|
||||
char result[5];
|
||||
for (int i=3;i>=0;i--) {
|
||||
result[i]=GETFLASH(hexchars+(value & 0x0F));
|
||||
value>>=4;
|
||||
}
|
||||
result[4]='\0';
|
||||
stream->print(result);
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ class StringFormatter
|
||||
static void lcd2(uint8_t display, byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
static void printEscape( char c);
|
||||
static void printHex(Print * stream,uint16_t value);
|
||||
|
||||
private:
|
||||
static void send2(Print * serial, const FSH* input,va_list args);
|
||||
|
301
TrackManager.cpp
301
TrackManager.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2022,2023 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -26,7 +26,8 @@
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include"CommandDistributor.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "DCCEXParser.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
@@ -44,6 +45,11 @@ const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
const int16_t HASH_KEYWORD_AUTO = -5457;
|
||||
#ifdef BOOSTER_INPUT
|
||||
const int16_t HASH_KEYWORD_BOOST = 11269;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_INV = 11857;
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
@@ -86,7 +92,7 @@ void TrackManager::sampleCurrent() {
|
||||
if (!waiting) {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
|
||||
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) {
|
||||
track[tr]->startCurrentFromHW();
|
||||
// for scope debug track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
@@ -196,8 +202,8 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
||||
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
if (track[t]->getMode() & TRACK_MODE_DC)
|
||||
track[t]->setDCSignal(speedbyte);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +212,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
if (mode & TRACK_MODE_DC) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
DIAG(F("Uno has no PWM timers available for DC"));
|
||||
return false;
|
||||
@@ -222,21 +228,37 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
pinpair p = track[trackToSet]->getSignalPin();
|
||||
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
||||
gpio_reset_pin((gpio_num_t)p.pin);
|
||||
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
||||
gpio_reset_pin((gpio_num_t)p.invpin);
|
||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
}
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (mode & TRACK_MODE_BOOST) {
|
||||
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
|
||||
pinMode(BOOSTER_INPUT, INPUT);
|
||||
gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
|
||||
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
|
||||
}
|
||||
} else // elseif clause continues
|
||||
#endif
|
||||
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
|
||||
// gpio_reset_pin may reset to input
|
||||
pinMode(p.pin, OUTPUT);
|
||||
if (p.invpin != UNUSED_PIN)
|
||||
pinMode(p.invpin, OUTPUT);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
if (mode & TRACK_MODE_PROG) {
|
||||
#else
|
||||
if (false) {
|
||||
#endif
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
|
||||
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
@@ -254,16 +276,20 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
||||
if (!(mode & TRACK_MODE_DC)) {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
||||
// BOOST:
|
||||
// Leave it as is
|
||||
// otherwise:
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
if (!(mode & TRACK_MODE_BOOST))
|
||||
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// re-evaluate HighAccuracy mode
|
||||
@@ -273,7 +299,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
|
||||
if (track[t]->getMode() & TRACK_MODE_DC) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
@@ -281,7 +307,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
@@ -299,10 +325,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
#else
|
||||
// For ESP32 we just reinitialize the DCC Waveform
|
||||
DCCWaveform::begin();
|
||||
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
|
||||
track[trackToSet]->setMode(mode);
|
||||
#endif
|
||||
|
||||
// This block must be AFTER the PWM-Timer modifications
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
if (mode & TRACK_MODE_DC) {
|
||||
// DC tracks need to be given speed of the throttle for that cab address
|
||||
// otherwise will not match other tracks on same cab.
|
||||
// This also needs to allow for inverted DCX
|
||||
@@ -311,7 +339,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
// Normal running tracks are set to the global power state
|
||||
track[trackToSet]->setPower(
|
||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
||||
(mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ?
|
||||
mainPowerGuess : POWERMODE::OFF);
|
||||
//DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
@@ -319,8 +347,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (track[t]->getMode()==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
|
||||
@@ -331,6 +357,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
FOR_EACH_TRACK(t)
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
@@ -351,12 +378,21 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
#endif
|
||||
if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
||||
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -364,33 +400,44 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
// null stream means send to commandDistributor for broadcast
|
||||
if (track[t]==NULL) return;
|
||||
auto format=F("");
|
||||
switch(track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
auto format=F("<= %d XXX>\n");
|
||||
TRACK_MODE tm = track[t]->getMode();
|
||||
if (tm & TRACK_MODE_MAIN) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
format=F("<= %c MAIN A>\n");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c MAIN I>\n");
|
||||
else
|
||||
format=F("<= %c MAIN>\n");
|
||||
break;
|
||||
#ifndef DISABLE_PROG
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
#endif
|
||||
case TRACK_MODE_NONE:
|
||||
format=F("<= %c NONE>\n");
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
format=F("<= %c DC %d>\n");
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
format=F("<= %c DCX %d>\n");
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
||||
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
||||
#ifndef DISABLE_PROG
|
||||
else if (tm & TRACK_MODE_PROG)
|
||||
format=F("<= %c PROG>\n");
|
||||
#endif
|
||||
else if (tm & TRACK_MODE_NONE)
|
||||
format=F("<= %c NONE>\n");
|
||||
else if(tm & TRACK_MODE_EXT)
|
||||
format=F("<= %c EXT>\n");
|
||||
else if(tm & TRACK_MODE_BOOST) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
format=F("<= %c B A>\n");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c B I>\n");
|
||||
else
|
||||
format=F("<= %c B>\n");
|
||||
}
|
||||
else if (tm & TRACK_MODE_DC) {
|
||||
if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c DCX %d>\n");
|
||||
else
|
||||
format=F("<= %c DC %d>\n");
|
||||
}
|
||||
|
||||
if (stream)
|
||||
StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
|
||||
else
|
||||
CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
|
||||
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
@@ -405,13 +452,13 @@ void TrackManager::loop() {
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
|
||||
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -419,56 +466,93 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
// Set track power for all tracks with this mode
|
||||
void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver *driver=track[t];
|
||||
if (trackmode & driver->getMode()) {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
if (trackmode & TRACK_MODE_DC) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
} else {
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
}
|
||||
}
|
||||
driver->setPower(powermode);
|
||||
}
|
||||
}
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Set track power for this track, inependent of mode
|
||||
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||
MotorDriver *driver=track[t];
|
||||
TRACK_MODE trackmode = driver->getMode();
|
||||
if (trackmode & TRACK_MODE_DC) {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
}
|
||||
} else {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
}
|
||||
}
|
||||
driver->setPower(powermode);
|
||||
}
|
||||
|
||||
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
// It reports the first track only, as main, regardless of track settings.
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
}
|
||||
|
||||
// returns state of the one and only prog track
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||
return track[t]->getPower(); // optimize: there is max one prog track
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
// returns on if all are on. returns off otherwise
|
||||
POWERMODE TrackManager::getMainPower() {
|
||||
POWERMODE result = POWERMODE::OFF;
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (track[t]->getMode() & TRACK_MODE_MAIN) {
|
||||
POWERMODE p = track[t]->getPower();
|
||||
if (p == POWERMODE::OFF)
|
||||
return POWERMODE::OFF; // done and out
|
||||
if (p == POWERMODE::ON)
|
||||
result = POWERMODE::ON;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TrackManager::getPower(byte t, char s[]) {
|
||||
if (track[t]) {
|
||||
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
|
||||
s[2] = t + 'A';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
@@ -510,7 +594,7 @@ void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
@@ -538,3 +622,32 @@ bool TrackManager::isPowerOn(byte t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrackManager::isProg(byte t) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::returnMode(byte t) {
|
||||
return (track[t]->getMode());
|
||||
}
|
||||
|
||||
int16_t TrackManager::returnDCAddr(byte t) {
|
||||
return (trackDCAddr[t]);
|
||||
}
|
||||
|
||||
const char* TrackManager::getModeName(byte Mode) {
|
||||
|
||||
//DIAG(F("PowerMode %d"), Mode);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case 1: return "NONE";
|
||||
case 2: return "MAIN";
|
||||
case 4: return "PROG";
|
||||
case 8: return "DC";
|
||||
case 16: return "DCX";
|
||||
case 32: return "EXT";
|
||||
default: return "----";
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,10 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
@@ -58,28 +62,36 @@ class TrackManager {
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
#endif
|
||||
static void setPower2(bool progTrack,POWERMODE mode);
|
||||
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
||||
static void setTrackPower(POWERMODE mode, byte t);
|
||||
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
||||
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
||||
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
||||
static POWERMODE getMainPower();
|
||||
static POWERMODE getProgPower();
|
||||
static bool getPower(byte t, char s[]);
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void sampleCurrent();
|
||||
static void reportGauges(Print* stream);
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportPowerChange(Print* stream, byte thistrack);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
static bool isProg(byte t);
|
||||
static byte returnMode(byte t);
|
||||
static int16_t returnDCAddr(byte t);
|
||||
static const char* getModeName(byte Mode);
|
||||
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
@@ -99,7 +111,7 @@ class TrackManager {
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static byte tempProgTrack; // holds the prog track number during join
|
||||
#endif
|
||||
|
@@ -185,7 +185,7 @@ public:
|
||||
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
|
||||
if (!tto->isHidden()) {
|
||||
gotOne = true;
|
||||
StringFormatter::send(stream, F("<i %d %d>\n"), tto->getId(), tto->getPosition());
|
||||
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
|
||||
}
|
||||
return gotOne;
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
void feedTheDog0(){
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
// feed dog 0
|
||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
TIMERG0.wdt_feed=1; // feed dog
|
||||
@@ -46,6 +47,13 @@ void feedTheDog0(){
|
||||
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
//TIMERG1.wdt_feed=1; // feed dog
|
||||
//TIMERG1.wdt_wprotect=0; // write protect
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
TIMERG0.wdtwprotect.wdt_wkey = TIMG_WDT_WKEY_V; // write enable
|
||||
TIMERG0.wdtfeed.wdt_feed = 1; // feed dog
|
||||
TIMERG0.wdtwprotect.wdt_wkey = 0; // write protect
|
||||
#else
|
||||
#warning This ESP32 variant not supported
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -37,5 +37,5 @@ public:
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
#endif //WifiESP8266_h
|
||||
#endif //ESP8266
|
||||
#endif // include guard
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
@@ -201,17 +201,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
if (checkForOK(2000, F("AT version:"), true, false)) {
|
||||
char version[] = "0.0.0.0";
|
||||
for (int i=0; i<8;i++) {
|
||||
char version[] = "0.0.0.0-xxx";
|
||||
for (int i=0; i<11;i++) {
|
||||
while(!wifiStream->available());
|
||||
version[i]=wifiStream->read();
|
||||
StringFormatter::printEscape(version[i]);
|
||||
if ((version[0] == '0') ||
|
||||
(version[0] == '2' && version[2] == '0') ||
|
||||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
|
||||
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
|
||||
forceAP = true;
|
||||
}
|
||||
}
|
||||
if ((version[0] == '0') ||
|
||||
(version[0] == '2' && version[2] == '0') ||
|
||||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
|
||||
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
|
||||
DIAG(F("You need to up/downgrade the ESP firmware"));
|
||||
SSid = F("UPDATE_ESP_FIRMWARE");
|
||||
forceAP = true;
|
||||
}
|
||||
}
|
||||
checkForOK(2000, true, false);
|
||||
|
@@ -266,6 +266,12 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
// BOOSTER PIN INPUT ON ESP32
|
||||
// On ESP32 you have the possibility to define a pin as booster input
|
||||
// Arduio pin D2 is GPIO 26 on ESPDuino32
|
||||
//
|
||||
//#define BOOSTER_INPUT 26
|
||||
|
||||
// SABERTOOTH
|
||||
//
|
||||
// This is a very special option and only useful if you happen to have a
|
||||
|
@@ -125,9 +125,15 @@
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
#define ARDUINO_TYPE "ESP32-WROOM"
|
||||
#elif defined (ARDUINO_ESP32S3_DEV)
|
||||
#define ARDUINO_TYPE "ESP32-S3"
|
||||
// #undef USB_SERIAL
|
||||
// #define USB_SERIAL SerialUSB
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
|
@@ -24,6 +24,7 @@
|
||||
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
|
@@ -30,7 +30,6 @@ include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
; monitor_filters = time
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -193,9 +192,6 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Experimental - no reason this should not work, but not
|
||||
; tested as yet
|
||||
;
|
||||
[env:Nucleo-F401RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f401re
|
||||
@@ -205,59 +201,6 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F13ZH has variant files
|
||||
; but NOT the nucleo_f413zh.json file which needs to be
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F413ZH]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f413zh
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F446ZE needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F446ZE]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f446ze
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F412ZG needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F412ZG]
|
||||
; platform = ststm32
|
||||
; board = blah_f412zg
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
; [env:Nucleo-F429ZI]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f429zi
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; arduino-libraries/Ethernet @ ^2.0.1
|
||||
; stm32duino/STM32Ethernet @ ^1.3.0
|
||||
; stm32duino/STM32duino LwIP @ ^2.1.2
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
@@ -296,4 +239,18 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
lib_ignore =
|
||||
|
||||
[env:ESP32_S3]
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
framework = arduino
|
||||
board_upload.flash_size = 8MB
|
||||
board_build.partitions = default_8MB.csv
|
||||
build_flags =
|
||||
-std=c++17
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
board_build.arduino.memory_type = opi_opi
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
20
version.h
20
version.h
@@ -3,7 +3,25 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.1.10"
|
||||
#define VERSION "5.2.2"
|
||||
// 5.2.2 - ESP32-S3 initial code support, WDT, ADCee(), but note RMT not tested!
|
||||
// 5.2.1 - Trackmanager rework for simpler structure
|
||||
// 5.2.0 - ESP32: Autoreverse and booster mode support
|
||||
// 5.1.21 - EXRAIL invoke multiple ON handlers for same event
|
||||
// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION
|
||||
// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
||||
// 5.1.18 - TURNOUTL bugfix
|
||||
// 5.1.17 - Divide out C for config and D for diag commands
|
||||
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
|
||||
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
|
||||
// 5.1.14 - Fixed IFTTPOSITION
|
||||
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
|
||||
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
|
||||
// Added EXRAIL SET_POWER(track, ON/OFF)
|
||||
// Fixed a problem whereby <1 MAIN> also powered on PROG track
|
||||
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
|
||||
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
|
||||
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
|
||||
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
|
||||
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
|
||||
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
|
||||
|
Reference in New Issue
Block a user