mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 10:23:45 +02:00
Compare commits
127 Commits
v5.2.22-De
...
devel_chri
Author | SHA1 | Date | |
---|---|---|---|
|
3fc90c916c | ||
|
132e2d0de2 | ||
|
23845f2df2 | ||
|
76755993f1 | ||
|
91e60b3716 | ||
|
8a5a832b1d | ||
|
5ea6feb11a | ||
|
8987d622e6 | ||
|
263c3d01e3 | ||
|
182479c07b | ||
|
3317b4666e | ||
|
f41f61dd5f | ||
|
6b713bf57c | ||
|
38a9585a41 | ||
|
1a307eea3d | ||
|
e4a3aa9f1e | ||
|
f581d56bdc | ||
|
7b77d4ce1e | ||
|
d367f5dc81 | ||
|
dc5f5e05b9 | ||
|
cff4075937 | ||
|
84b90ae757 | ||
|
6d7d2325da | ||
|
fdc956576b | ||
|
8f0a5c1ec0 | ||
|
94083b9ab8 | ||
|
72ef199315 | ||
|
e69b777a2f | ||
|
c7ed47400d | ||
|
02bf50b909 | ||
|
c8f18e4d67 | ||
|
7a93cf7be8 | ||
|
87073b0d36 | ||
|
0587e6fc09 | ||
|
3cda869c6e | ||
|
59d855549e | ||
|
e3081a7e56 | ||
|
8eec85edcf | ||
|
d753eb43e3 | ||
|
9aac34b403 | ||
|
be218d3032 | ||
|
4b04a80e6f | ||
|
b752666899 | ||
|
3d6f41398d | ||
|
7503421eb6 | ||
|
274affce45 | ||
|
b29a01f436 | ||
|
1101cfd637 | ||
|
3fa2edb0da | ||
|
423d1932ae | ||
|
dec39a2ae1 | ||
|
821115caad | ||
|
fe9b1da8a3 | ||
|
fbbedc7577 | ||
|
dcd332603c | ||
|
7e4093f03f | ||
|
7ee4188d88 | ||
|
5742b71ec6 | ||
|
8705c8c33f | ||
|
28d60d4984 | ||
|
e4904e4080 | ||
|
59b0e8383d | ||
|
784088b0df | ||
|
c780b96856 | ||
|
4b97d63cf3 | ||
|
6f1df6ce8e | ||
|
eacf48380b | ||
|
8293749ac7 | ||
|
25cb878060 | ||
|
7a9e225602 | ||
|
1443ea8df9 | ||
|
cd47782052 | ||
|
4931c5ed75 | ||
|
53fec9bc3a | ||
|
4780ea63cf | ||
|
5f6e18e1e7 | ||
|
be40a7e274 | ||
|
e7f82bdf92 | ||
|
63702ae64e | ||
|
7cbf4de1b9 | ||
|
3c4e4bb14d | ||
|
6d0740eab4 | ||
|
0a52a26d50 | ||
|
daa2ffc459 | ||
|
9728d19b19 | ||
|
99a09c713f | ||
|
811bce4b2a | ||
|
cf1e1c92b3 | ||
|
3b162996ad | ||
|
fb414a7a50 | ||
|
a5b73c823a | ||
|
657c08c653 | ||
|
bc37a2d2cf | ||
|
3c0704dbd1 | ||
|
95bf5aae38 | ||
|
8216579f62 | ||
|
a54a262f68 | ||
|
a508ee7055 | ||
|
20ae915eaf | ||
|
35a0bde115 | ||
|
d24d09c37a | ||
|
9ab6b3d4ea | ||
|
d8c282434c | ||
|
43648fd9f4 | ||
|
b5ddade2b3 | ||
|
2e4995cab3 | ||
|
818e05b425 | ||
|
c5168f030f | ||
|
8036ba1c48 | ||
|
6f076720f7 | ||
|
d899da5898 | ||
|
3ce9d2ec88 | ||
|
9ebb1c5fb1 | ||
|
19efa749b8 | ||
|
36cc46e88d | ||
|
bba74a08f6 | ||
|
ab58c38e7b | ||
|
d4f0a7c8f3 | ||
|
ba0a41b6f2 | ||
|
bf17f2018b | ||
|
67387d2dc3 | ||
|
adb8b56c92 | ||
|
387ea019bd | ||
|
a981f83bb9 | ||
|
749a859db5 | ||
|
659c58b307 | ||
|
0b9ec7460b |
@@ -310,6 +310,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastMessage(char * message) {
|
||||
broadcastReply(COMMAND_TYPE, F("<m \"%s\">\n"),message);
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%s\n"),message);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter, const FSH *modename, int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format, trackLetter, modename, dcAddr);
|
||||
}
|
||||
|
@@ -60,6 +60,7 @@ public :
|
||||
static void forget(byte clientId);
|
||||
static void broadcastRouteState(uint16_t routeId,byte state);
|
||||
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
|
||||
static void broadcastMessage(char * message);
|
||||
|
||||
// Handling code for virtual LCD receiver.
|
||||
static Print * getVirtualLCDSerial(byte screen, byte row);
|
||||
|
@@ -65,6 +65,9 @@
|
||||
#ifdef EXRAIL_WARNING
|
||||
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
|
||||
#endif
|
||||
// compile time check, passwords 1 to 7 chars do not work, so do not try to compile with them at all
|
||||
// remember trailing '\0', sizeof("") == 1.
|
||||
#define PASSWDCHECK(S) static_assert(sizeof(S) == 1 || sizeof(S) > 8, "Password shorter than 8 chars")
|
||||
|
||||
void setup()
|
||||
{
|
||||
@@ -102,10 +105,12 @@ void setup()
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
PASSWDCHECK(WIFI_PASSWORD); // compile time check
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
PASSWDCHECK(WIFI_PASSWORD); // compile time check
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
|
173
DCC.cpp
173
DCC.cpp
@@ -153,6 +153,22 @@ uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||
return speedTable[reg].speedCode;
|
||||
}
|
||||
|
||||
// returns 0 to 7 for frequency
|
||||
uint8_t DCC::getThrottleFrequency(int cab) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
(void)cab;
|
||||
return 0;
|
||||
#else
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0)
|
||||
return 0; // use default frequency
|
||||
// shift out first 29 bits so we have the 3 "frequency bits" left
|
||||
uint8_t res = (uint8_t)(speedTable[reg].functions >>29);
|
||||
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns direction on loco
|
||||
// or true/forward on "loco not found"
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
@@ -183,43 +199,55 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
b[nB++] = functionNumber >>7 ; // high order bits
|
||||
}
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We use the reminder table up to 28 for normal functions.
|
||||
// We use 29 to 31 for DC frequency as well so up to 28
|
||||
// are "real" functions and 29 to 31 are frequency bits
|
||||
// controlled by function buttons
|
||||
if (functionNumber > 31)
|
||||
return true;
|
||||
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return false;
|
||||
|
||||
// Take care of functions:
|
||||
// Set state of function
|
||||
unsigned long previous=speedTable[reg].functions;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
uint32_t previous=speedTable[reg].functions;
|
||||
uint32_t funcmask = (1UL<<functionNumber);
|
||||
if (on) {
|
||||
speedTable[reg].functions |= funcmask;
|
||||
} else {
|
||||
speedTable[reg].functions &= ~funcmask;
|
||||
}
|
||||
if (speedTable[reg].functions != previous) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
if (functionNumber <= 28)
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flip function state
|
||||
// Flip function state (used from withrottle protocol)
|
||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28) return;
|
||||
if (cab<=0 || functionNumber>31) return;
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
speedTable[reg].functions ^= funcmask;
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
if (functionNumber <= 28) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
}
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
|
||||
int DCC::getFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
||||
// Report function state (used from withrottle protocol)
|
||||
// returns 0 false, 1 true or -1 for do not know
|
||||
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>31)
|
||||
return -1; // unknown
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
if (reg<0)
|
||||
return -1;
|
||||
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||
@@ -278,6 +306,57 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DCC::setExtendedAccessory(int16_t address, int16_t value, byte repeats) {
|
||||
|
||||
/* From https://www.nmra.org/sites/default/files/s-9.2.1_2012_07.pdf
|
||||
|
||||
The Extended Accessory Decoder Control Packet is included for the purpose of transmitting aspect control to signal
|
||||
decoders or data bytes to more complex accessory decoders. Each signal head can display one aspect at a time.
|
||||
{preamble} 0 10AAAAAA 0 0AAA0AA1 0 000XXXXX 0 EEEEEEEE 1
|
||||
|
||||
XXXXX is for a single head. A value of 00000 for XXXXX indicates the absolute stop aspect. All other aspects
|
||||
represented by the values for XXXXX are determined by the signaling system used and the prototype being
|
||||
modeled.
|
||||
|
||||
From https://normen.railcommunity.de/RCN-213.pdf:
|
||||
|
||||
More information is in RCN-213 about how the address bits are organized.
|
||||
preamble -0- 1 0 A7 A6 A5 A4 A3 A2 -0- 0 ^A10 ^A9 ^A8 0 A1 A0 1 -0- ....
|
||||
|
||||
Thus in byte packet form the format is 10AAAAAA, 0AAA0AA1, 000XXXXX
|
||||
|
||||
Die Adresse f<>r den ersten erweiterten Zubeh<65>rdecoder ist wie bei den einfachen
|
||||
Zubeh<EFBFBD>rdecodern die Adresse 4 = 1000-0001 0111-0001 . Diese Adresse wird in
|
||||
Anwenderdialogen als Adresse 1 dargestellt.
|
||||
|
||||
This means that the first address shown to the user as "1" is mapped
|
||||
to internal address 4.
|
||||
|
||||
Note that the Basic accessory format mentions "By convention these
|
||||
bits (bits 4-6 of the second data byte) are in ones complement" but
|
||||
this note is absent from the advanced packet description. The
|
||||
english translation does not mention that the address format for
|
||||
the advanced packet follows the one for the basic packet but
|
||||
according to the RCN-213 this is the case.
|
||||
|
||||
We allow for addresses from -3 to 2047-3 as that allows to address the
|
||||
whole range of the 11 bits sent to track.
|
||||
*/
|
||||
if ((address > 2044) || (address < -3)) return false; // 2047-3, 11 bits but offset 3
|
||||
if (value != (value & 0x1F)) return false; // 5 bits
|
||||
|
||||
address+=3; // +3 offset according to RCN-213
|
||||
byte b[3];
|
||||
b[0]= 0x80 // bits always on
|
||||
| ((address>>2) & 0x3F); // shift out 2, mask out used bits
|
||||
b[1]= 0x01 // bits always on
|
||||
| (((~(address>>8)) & 0x07)<<4) // shift out 8, invert, mask 3 bits, shift up 4
|
||||
| ((address & 0x03)<<1); // mask 2 bits, shift up 1
|
||||
b[2]=value;
|
||||
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), repeats);
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// writeCVByteMain: Write a byte with PoM on main. This writes
|
||||
// the 5 byte sized packet to implement this DCC function
|
||||
@@ -421,6 +500,36 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
// first check cv20 for extended addressing
|
||||
SETCV, (ackOp)20, // CV 19 is extended
|
||||
SETBYTE, (ackOp)0,
|
||||
VB, WACK, ITSKIP, // skip past extended section if cv20 is zero
|
||||
// read cv20 and 19 and merge
|
||||
STARTMERGE, // Setup to read cv 20
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
||||
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
||||
SETCV, (ackOp)19,
|
||||
STARTMERGE, // Setup to read cv 19
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, NAKFAIL, // cant recover if cv 19 unreadable
|
||||
COMBINE1920, // Combile byte with stash and callback
|
||||
// end of advanced 20,19 check
|
||||
SKIPTARGET,
|
||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||
SETBYTE, (ackOp)0,
|
||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||
@@ -487,6 +596,10 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
// Clear consist CV 19,20
|
||||
SETCV,(ackOp)20,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore dedcoder without cv20 support
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore dedcoder without cv19 support
|
||||
@@ -502,9 +615,25 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
// for CONSIST_ID_PROG the 20,19 values are already calculated
|
||||
const ackOp FLASH CONSIST_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV,(ackOp)20,
|
||||
SETBYTEH, // high byte to CV 20
|
||||
WB,WACK, // ignore dedcoder without cv20 support
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
// Clear consist CV 19
|
||||
// Clear consist CV 19,20
|
||||
SETCV,(ackOp)20,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore dedcoder without cv20 support
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore decoder without cv19 support
|
||||
@@ -573,6 +702,26 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) {
|
||||
if (id<0 || id>10239) { //0x27FF according to standard
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
byte cv20;
|
||||
byte cv19;
|
||||
|
||||
if (id<=HIGHEST_SHORT_ADDR) {
|
||||
cv19=id;
|
||||
cv20=0;
|
||||
}
|
||||
else {
|
||||
cv20=id/100;
|
||||
cv19=id%100;
|
||||
}
|
||||
if (reverse) cv19|=0x80;
|
||||
DCCACK::Setup((cv20<<8)|cv19, CONSIST_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||
int reg=lookupSpeedTable(cab, false);
|
||||
|
8
DCC.h
8
DCC.h
@@ -61,16 +61,18 @@ public:
|
||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static int8_t getThrottleSpeed(int cab);
|
||||
static uint8_t getThrottleSpeedByte(int cab);
|
||||
static uint8_t getThrottleFrequency(int cab);
|
||||
static bool getThrottleDirection(int cab);
|
||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||
static void setFunction(int cab, byte fByte, byte eByte);
|
||||
static bool setFn(int cab, int16_t functionNumber, bool on);
|
||||
static void changeFn(int cab, int16_t functionNumber);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static int8_t getFn(int cab, int16_t functionNumber);
|
||||
static uint32_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
||||
static bool setExtendedAccessory(int16_t address, int16_t value, byte repeats=3);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
@@ -83,7 +85,7 @@ public:
|
||||
|
||||
static void getLocoId(ACK_CALLBACK callback);
|
||||
static void setLocoId(int id,ACK_CALLBACK callback);
|
||||
|
||||
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
|
||||
// Enhanced API functions
|
||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
@@ -98,7 +100,7 @@ public:
|
||||
int loco;
|
||||
byte speedCode;
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
uint32_t functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
|
17
DCCACK.cpp
17
DCCACK.cpp
@@ -314,6 +314,14 @@ void DCCACK::loop() {
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case COMBINE1920:
|
||||
// ackManagerStash is cv20, ackManagerByte is CV 19
|
||||
// This will not be called if cv20==0
|
||||
ackManagerByte &= 0x7F; // ignore direction marker
|
||||
ackManagerByte %=100; // take last 2 decimal digits
|
||||
callback( ackManagerStash*100+ackManagerByte);
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
@@ -322,6 +330,15 @@ void DCCACK::loop() {
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKSKIP:
|
||||
if (ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
|
2
DCCACK.h
2
DCCACK.h
@@ -56,6 +56,8 @@ enum ackOp : byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
NAKSKIP, // skip to SKIPTARGET if ack false
|
||||
COMBINE1920, // combine cvs 19 and 20 and callback
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
|
1
DCCEX.h
1
DCCEX.h
@@ -49,6 +49,7 @@
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "KeywordHasher.h"
|
||||
#include "EXRAIL.h"
|
||||
|
||||
#endif
|
||||
|
240
DCCEXParser.cpp
240
DCCEXParser.cpp
@@ -45,7 +45,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
0, Track power off
|
||||
1, Track power on
|
||||
a, DCC accessory control
|
||||
A,
|
||||
A, DCC extended accessory control
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
@@ -68,7 +68,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
K, Reserved for future use - Potentially Railcom
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||
m,
|
||||
m, message to throttles broadcast
|
||||
M, Write DCC packet
|
||||
n,
|
||||
N,
|
||||
@@ -116,6 +116,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
#include "EXRAIL2.h"
|
||||
#include "Turntables.h"
|
||||
#include "version.h"
|
||||
#include "KeywordHasher.h"
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
// flashlist requires a far pointer for high flash access.
|
||||
@@ -126,57 +127,6 @@ Once a new OPCODE is decided upon, update this list.
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_CABS = -11981;
|
||||
const int16_t HASH_KEYWORD_RAM = 25982;
|
||||
const int16_t HASH_KEYWORD_CMD = 9962;
|
||||
const int16_t HASH_KEYWORD_ACK = 3113;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_DCC = 6436;
|
||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||
#ifndef DISABLE_PROG
|
||||
const int16_t HASH_KEYWORD_JOIN = -30750;
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
||||
const int16_t HASH_KEYWORD_MAX = 16244;
|
||||
const int16_t HASH_KEYWORD_MIN = 15978;
|
||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
||||
const int16_t HASH_KEYWORD_RETRY = 25704;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
||||
const int16_t HASH_KEYWORD_TT=2688;
|
||||
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_M='M';
|
||||
const int16_t HASH_KEYWORD_O='O';
|
||||
const int16_t HASH_KEYWORD_P='P';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_X='X';
|
||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||
const int16_t HASH_KEYWORD_EXTT = 8573;
|
||||
const int16_t HASH_KEYWORD_ADD = 3201;
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
Print *DCCEXParser::stashStream = NULL;
|
||||
@@ -333,25 +283,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return; // filterCallback asked us to ignore
|
||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||
{
|
||||
if (params==1) { // <t cab> display state
|
||||
|
||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||
if (slot>=0) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
}
|
||||
else // send dummy state speed 0 fwd no functions.
|
||||
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t cab;
|
||||
int16_t tspeed;
|
||||
int16_t direction;
|
||||
|
||||
|
||||
if (params==1) { // <t cab> display state
|
||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||
if (slot>=0)
|
||||
CommandDistributor::broadcastLoco(slot);
|
||||
else // send dummy state speed 0 fwd no functions.
|
||||
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (params == 4)
|
||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||
// ignore register p[0]
|
||||
cab = p[1];
|
||||
tspeed = p[2];
|
||||
direction = p[3];
|
||||
@@ -434,6 +381,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
|
||||
case 'A': // EXTENDED ACCESSORY <A address value>
|
||||
// Note: if this happens to match a defined EXRAIL
|
||||
// DCCX_SIGNAL, then EXRAIL will have intercepted
|
||||
// this command alrerady.
|
||||
if (params==2 && DCC::setExtendedAccessory(p[0],p[1])) return;
|
||||
break;
|
||||
|
||||
case 'T': // TURNOUT <T ...>
|
||||
if (parseT(stream, params, p))
|
||||
@@ -504,6 +458,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
DCC::setLocoId(p[0],callback_Wloco);
|
||||
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||
else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) {
|
||||
DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist);
|
||||
}
|
||||
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||
else
|
||||
@@ -567,20 +524,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
if (p[0]=="MAIN"_hk) { // <1 MAIN>
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
else if (p[0] == "JOIN"_hk) { // <1 JOIN>
|
||||
TrackManager::setJoin(true);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
else if (p[0]=="PROG"_hk) { // <1 PROG>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
#endif
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setTrackPower(POWERMODE::ON, t);
|
||||
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
|
||||
@@ -600,17 +557,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
if (p[0]=="MAIN"_hk) { // <0 MAIN>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
else if (p[0]=="PROG"_hk) { // <0 PROG>
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||
}
|
||||
#endif
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, t);
|
||||
@@ -625,12 +582,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
return;
|
||||
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// No longer useful because of multiple tracks See <JG> and <JI>
|
||||
if (params>0) break;
|
||||
TrackManager::reportObsoleteCurrent(stream);
|
||||
return;
|
||||
|
||||
#endif
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
@@ -704,7 +662,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
//if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
|
||||
case "C"_hk: // <JC mmmm nn> sets time and speed
|
||||
if (params==1) { // <JC> returns latest time
|
||||
int16_t x = CommandDistributor::retClockTime();
|
||||
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
||||
@@ -713,28 +671,28 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
CommandDistributor::setClockTime(p[1], p[2], 1);
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_G: // <JG> current gauge limits
|
||||
case "G"_hk: // <JG> current gauge limits
|
||||
if (params>1) break;
|
||||
TrackManager::reportGauges(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_I: // <JI> current values
|
||||
case "I"_hk: // <JI> current values
|
||||
if (params>1) break;
|
||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||
if (params!=1) break; // <JA>
|
||||
StringFormatter::send(stream, F("<jA>\n"));
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_M: // <JM> intercepted by EXRAIL
|
||||
case "M"_hk: // <JM> intercepted by EXRAIL
|
||||
if (params>1) break; // invalid cant do
|
||||
// <JM> requests stash size so say none.
|
||||
StringFormatter::send(stream,F("<jM 0>\n"));
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
case "R"_hk: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
if (params==1) {
|
||||
@@ -753,7 +711,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_T: // <JT> returns turnout list
|
||||
case "T"_hk: // <JT> returns turnout list
|
||||
StringFormatter::send(stream, F("<jT"));
|
||||
if (params==1) { // <JT>
|
||||
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
||||
@@ -780,7 +738,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
// No turntables without HAL support
|
||||
#ifndef IO_NO_HAL
|
||||
case HASH_KEYWORD_O: // <JO returns turntable list
|
||||
case "O"_hk: // <JO returns turntable list
|
||||
StringFormatter::send(stream, F("<jO"));
|
||||
if (params==1) { // <JO>
|
||||
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
|
||||
@@ -805,7 +763,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
}
|
||||
return;
|
||||
case HASH_KEYWORD_P: // <JP id> returns turntable position list for the turntable id
|
||||
case "P"_hk: // <JP id> returns turntable position list for the turntable id
|
||||
if (params==2) { // <JP id>
|
||||
Turntable *tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {
|
||||
@@ -842,6 +800,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
break;
|
||||
#endif
|
||||
|
||||
case '/': // implemented in EXRAIL parser
|
||||
case 'L': // LCC interface implemented in EXRAIL parser
|
||||
break; // Will <X> if not intercepted by EXRAIL
|
||||
|
||||
@@ -972,14 +931,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
switch (p[1]) {
|
||||
// Turnout messages use 1=throw, 0=close.
|
||||
case 0:
|
||||
case HASH_KEYWORD_C:
|
||||
case "C"_hk:
|
||||
state = true;
|
||||
break;
|
||||
case 1:
|
||||
case HASH_KEYWORD_T:
|
||||
case "T"_hk:
|
||||
state= false;
|
||||
break;
|
||||
case HASH_KEYWORD_X:
|
||||
case "X"_hk:
|
||||
{
|
||||
Turnout *tt = Turnout::get(p[0]);
|
||||
if (tt) {
|
||||
@@ -996,14 +955,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
}
|
||||
|
||||
default: // Anything else is some kind of turnout create function.
|
||||
if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
|
||||
if (params == 6 && p[1] == "SERVO"_hk) { // <T id SERVO n n n n>
|
||||
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
|
||||
return false;
|
||||
} else
|
||||
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
|
||||
if (params == 3 && p[1] == "VPIN"_hk) { // <T id VPIN n>
|
||||
if (!VpinTurnout::create(p[0], p[2])) return false;
|
||||
} else
|
||||
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
|
||||
if (params >= 3 && p[1] == "DCC"_hk) {
|
||||
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
|
||||
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
||||
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
|
||||
@@ -1069,41 +1028,66 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||
switch (p[0])
|
||||
{
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
case "PROGBOOST"_hk:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_RESET:
|
||||
case "RESET"_hk:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
case "SPEED28"_hk:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
DIAG(F("28 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
case "SPEED128"_hk:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
DIAG(F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
#if defined(HAS_ENOUGH_MEMORY) && !defined(ARDUINO_ARCH_UNO)
|
||||
case "RAILCOM"_hk:
|
||||
{ // <C RAILCOM ON|OFF|DEBUG >
|
||||
if (params<2) return false;
|
||||
bool on=false;
|
||||
bool debug=false;
|
||||
switch (p[1]) {
|
||||
case "ON"_hk:
|
||||
case 1:
|
||||
on=true;
|
||||
break;
|
||||
case "DEBUG"_hk:
|
||||
on=true;
|
||||
debug=true;
|
||||
break;
|
||||
case "OFF"_hk:
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
DIAG(F("Railcom %S")
|
||||
,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF"));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
if (p[1] == "LIMIT"_hk) {
|
||||
DCCACK::setAckLimit(p[2]);
|
||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||
} else if (p[1] == "MIN"_hk) {
|
||||
DCCACK::setMinAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||
} else if (p[1] == "MAX"_hk) {
|
||||
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||
} else if (p[1] == "RETRY"_hk) {
|
||||
if (p[2] >255) p[2]=3;
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
|
||||
|
||||
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
|
||||
Diag::ACK = onOff;
|
||||
@@ -1121,66 +1105,66 @@ 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
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
case "CABS"_hk: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
case "RAM"_hk: // <D RAM>
|
||||
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||
case "CMD"_hk: // <D CMD ON/OFF>
|
||||
Diag::CMD = onOff;
|
||||
return true;
|
||||
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
case HASH_KEYWORD_WIFI: // <D WIFI ON/OFF>
|
||||
case "WIFI"_hk: // <D WIFI ON/OFF>
|
||||
Diag::WIFI = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF>
|
||||
case "ETHERNET"_hk: // <D ETHERNET ON/OFF>
|
||||
Diag::ETHERNET = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
|
||||
case "WIT"_hk: // <D WIT ON/OFF>
|
||||
Diag::WITHROTTLE = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_LCN: // <D LCN ON/OFF>
|
||||
case "LCN"_hk: // <D LCN ON/OFF>
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
case "EEPROM"_hk: // <D EEPROM NumEntries>
|
||||
if (params >= 2)
|
||||
EEStore::dump(p[1]);
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
||||
case "SERVO"_hk: // <D SERVO vpin position [profile]>
|
||||
|
||||
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
|
||||
case "ANOUT"_hk: // <D ANOUT vpin position [profile]>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
||||
case "ANIN"_hk: // <D ANIN vpin> Display analogue input value
|
||||
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||
break;
|
||||
return true;
|
||||
|
||||
#if !defined(IO_NO_HAL)
|
||||
case HASH_KEYWORD_HAL:
|
||||
if (p[1] == HASH_KEYWORD_SHOW)
|
||||
case "HAL"_hk:
|
||||
if (p[1] == "SHOW"_hk)
|
||||
IODevice::DumpAll();
|
||||
else if (p[1] == HASH_KEYWORD_RESET)
|
||||
else if (p[1] == "RESET"_hk)
|
||||
IODevice::reset();
|
||||
break;
|
||||
return true;
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
||||
case "TT"_hk: // <D TT vpin steps activity>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
return true;
|
||||
|
||||
default: // invalid/unknown
|
||||
return parseC(stream, params, p);
|
||||
@@ -1232,7 +1216,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_DCC) {
|
||||
if (p[1] == "DCC"_hk) {
|
||||
if (tto || p[2] < 0 || p[2] > 3600) return false;
|
||||
if (!DCCTurntable::create(p[0])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
@@ -1249,7 +1233,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
case 4: // <I id EXTT vpin home> create an EXTT turntable
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_EXTT) {
|
||||
if (p[1] == "EXTT"_hk) {
|
||||
if (tto || p[3] < 0 || p[3] > 3600) return false;
|
||||
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
@@ -1264,7 +1248,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
case 5: // <I id ADD position value angle> add a position
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_ADD) {
|
||||
if (p[1] == "ADD"_hk) {
|
||||
// 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]);
|
||||
@@ -1367,3 +1351,11 @@ void DCCEXParser::callback_Wloco(int16_t result)
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_Wconsist(int16_t result)
|
||||
{
|
||||
if (result==1) result=stashP[1]; // pick up original requested id from command
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
||||
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
@@ -71,6 +71,7 @@ struct DCCEXParser
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_Rloco(int16_t result);
|
||||
static void callback_Wloco(int16_t result);
|
||||
static void callback_Wconsist(int16_t result);
|
||||
static void callback_Vbit(int16_t result);
|
||||
static void callback_Vbyte(int16_t result);
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
|
@@ -62,8 +62,14 @@ class DCCTimer {
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
static void clearPWM();
|
||||
static void startRailcomTimer(byte brakePin);
|
||||
static void ackRailcomTimer();
|
||||
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
static void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
static void DCCEXanalogWrite(uint8_t pin, int value, bool invert);
|
||||
static void DCCEXledcDetachPin(uint8_t pin);
|
||||
static void DCCEXanalogCopyChannel(int8_t frompin, int8_t topin);
|
||||
static void DCCEXInrushControlOn(uint8_t pin, int duty, bool invert);
|
||||
static void DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted);
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
@@ -85,6 +91,7 @@ class DCCTimer {
|
||||
static void reset();
|
||||
|
||||
private:
|
||||
static void DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency);
|
||||
static int freeMemory();
|
||||
static volatile int minimum_free_memory;
|
||||
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
|
140
DCCTimerAVR.cpp
140
DCCTimerAVR.cpp
@@ -29,6 +29,7 @@
|
||||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
@@ -39,6 +40,9 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#define TIMER2_A_PIN 10
|
||||
#define TIMER2_B_PIN 9
|
||||
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
@@ -55,6 +59,67 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
/* The Railcom timer is started in such a way that it
|
||||
- First triggers 28uS after the last TIMER1 tick.
|
||||
This provides an accurate offset (in High Accuracy mode)
|
||||
for the start of the Railcom cutout.
|
||||
- Sets the Railcom pin high at first tick,
|
||||
because its been setup with 100% PWM duty cycle.
|
||||
|
||||
- Cycles at 436uS so the second tick is the
|
||||
correct distance from the cutout.
|
||||
|
||||
- Waveform code is responsible for altering the PWM
|
||||
duty cycle to 0% any time between the first and last tick.
|
||||
(there will be 7 DCC timer1 ticks in which to do this.)
|
||||
|
||||
*/
|
||||
(void) brakePin; // Ignored... works on pin 9 only
|
||||
const int cutoutDuration = 430; // Desired interval in microseconds
|
||||
|
||||
// Set up Timer2 for CTC mode (Clear Timer on Compare Match)
|
||||
TCCR2A = 0; // Clear Timer2 control register A
|
||||
TCCR2B = 0; // Clear Timer2 control register B
|
||||
TCNT2 = 0; // Initialize Timer2 counter value to 0
|
||||
// Configure Phase and Frequency Correct PWM mode
|
||||
TCCR2A = (1 << COM2B1); // enable pwm on pin 9
|
||||
TCCR2A |= (1 << WGM20);
|
||||
|
||||
|
||||
// Set Timer 2 prescaler to 32
|
||||
TCCR2B = (1 << CS21) | (1 << CS20); // 32 prescaler
|
||||
|
||||
// Set the compare match value for desired interval
|
||||
OCR2A = (F_CPU / 1000000) * cutoutDuration / 64 - 1;
|
||||
|
||||
// Calculate the compare match value for desired duty cycle
|
||||
OCR2B = OCR2A+1; // set duty cycle to 100%= OCR2A)
|
||||
|
||||
// Enable Timer2 output on pin 9 (OC2B)
|
||||
DDRB |= (1 << DDB1);
|
||||
// TODO Fudge TCNT2 to sync with last tcnt1 tick + 28uS
|
||||
|
||||
// Previous TIMER1 Tick was at rising end-of-packet bit
|
||||
// Cutout starts half way through first preamble
|
||||
// that is 2.5 * 58uS later.
|
||||
// TCNT1 ticks 8 times / microsecond
|
||||
// auto microsendsToFirstRailcomTick=(58+58+29)-(TCNT1/8);
|
||||
// set the railcom timer counter allowing for phase-correct
|
||||
|
||||
// CHris's NOTE:
|
||||
// I dont kniow quite how this calculation works out but
|
||||
// it does seems to get a good answer.
|
||||
|
||||
TCNT2=193 + (ICR1 - TCNT1)/8;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
OCR2B= 0x00; // brake pin pwm duty cycle 0 at next tick
|
||||
}
|
||||
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
@@ -125,6 +190,81 @@ void DCCTimer::reset() {
|
||||
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// Speed mapping is done like this:
|
||||
// No functions buttons: 000 0 -> low 131Hz
|
||||
// Only F29 pressed 001 1 -> mid 490Hz
|
||||
// F30 with or w/o F29 01x 2-3 -> high 3400Hz
|
||||
// F31 with or w/o F29/30 1xx 4-7 -> supersonic 62500Hz
|
||||
uint8_t abits;
|
||||
uint8_t bbits;
|
||||
if (pin == 9 || pin == 10) { // timer 2 is different
|
||||
|
||||
if (fbits >= 4)
|
||||
abits = B00000011;
|
||||
else
|
||||
abits = B00000001;
|
||||
|
||||
if (fbits >= 4)
|
||||
bbits = B0001;
|
||||
else if (fbits >= 2)
|
||||
bbits = B0010;
|
||||
else if (fbits == 1)
|
||||
bbits = B0100;
|
||||
else // fbits == 0
|
||||
bbits = B0110;
|
||||
|
||||
TCCR2A = (TCCR2A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR2B = (TCCR2B & B11110000) | bbits; // set WGM2 and 3 bits of prescaler
|
||||
DIAG(F("Timer 2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
|
||||
} else { // not timer 9 or 10
|
||||
abits = B01;
|
||||
|
||||
if (fbits >= 4)
|
||||
bbits = B1001;
|
||||
else if (fbits >= 2)
|
||||
bbits = B0010;
|
||||
else if (fbits == 1)
|
||||
bbits = B0011;
|
||||
else
|
||||
bbits = B0100;
|
||||
|
||||
switch (pin) {
|
||||
// case 9 and 10 taken care of above by if()
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR4B = (TCCR4B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||
//DIAG(F("Timer 4 A=%x B=%x"), TCCR4A, TCCR4B);
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | abits; // set WGM0 and WGM1
|
||||
TCCR5B = (TCCR5B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||
//DIAG(F("Timer 5 A=%x B=%x"), TCCR5A, TCCR5B);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 16
|
||||
#else
|
||||
|
114
DCCTimerESP.cpp
114
DCCTimerESP.cpp
@@ -78,6 +78,7 @@ int DCCTimer::freeMemory() {
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DIAG.h"
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
#include <soc/sens_struct.h>
|
||||
@@ -151,10 +152,28 @@ void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
if (f >= 16)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
/*
|
||||
else if (f == 7) // not used on ESP32
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||
*/
|
||||
else if (f >= 4)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||
else if (f >= 3)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||
else if (f >= 2)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||
else if (f == 1)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||
else
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||
}
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
@@ -164,7 +183,7 @@ void DCCTimer::reset() {
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] != 0) {
|
||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||
@@ -172,23 +191,104 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
}
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
void DCCTimer::DCCEXledcDetachPin(uint8_t pin) {
|
||||
DIAG(F("Clear pin %d channel"), pin);
|
||||
pin_to_channel[pin] = 0;
|
||||
pinMatrixOutDetach(pin, false, false);
|
||||
}
|
||||
|
||||
static byte LEDCToMux[] = {
|
||||
LEDC_HS_SIG_OUT0_IDX,
|
||||
LEDC_HS_SIG_OUT1_IDX,
|
||||
LEDC_HS_SIG_OUT2_IDX,
|
||||
LEDC_HS_SIG_OUT3_IDX,
|
||||
LEDC_HS_SIG_OUT4_IDX,
|
||||
LEDC_HS_SIG_OUT5_IDX,
|
||||
LEDC_HS_SIG_OUT6_IDX,
|
||||
LEDC_HS_SIG_OUT7_IDX,
|
||||
LEDC_LS_SIG_OUT0_IDX,
|
||||
LEDC_LS_SIG_OUT1_IDX,
|
||||
LEDC_LS_SIG_OUT2_IDX,
|
||||
LEDC_LS_SIG_OUT3_IDX,
|
||||
LEDC_LS_SIG_OUT4_IDX,
|
||||
LEDC_LS_SIG_OUT5_IDX,
|
||||
LEDC_LS_SIG_OUT6_IDX,
|
||||
LEDC_LS_SIG_OUT7_IDX,
|
||||
};
|
||||
|
||||
void DCCTimer::DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted) {
|
||||
DIAG(F("Attaching pin %d to channel %d %c"), pin, channel, inverted ? 'I' : ' ');
|
||||
ledcAttachPin(pin, channel);
|
||||
if (inverted) // we attach again but with inversion
|
||||
gpio_matrix_out(pin, LEDCToMux[channel], inverted, 0);
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogCopyChannel(int8_t frompin, int8_t topin) {
|
||||
// arguments are signed depending on inversion of pins
|
||||
DIAG(F("Pin %d copied to %d"), frompin, topin);
|
||||
bool inverted = false;
|
||||
if (frompin<0)
|
||||
frompin = -frompin;
|
||||
if (topin<0) {
|
||||
inverted = true;
|
||||
topin = -topin;
|
||||
}
|
||||
int channel = pin_to_channel[frompin]; // after abs(frompin)
|
||||
pin_to_channel[topin] = channel;
|
||||
DCCTimer::DCCEXledcAttachPin(topin, channel, inverted);
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
||||
// This allocates channels 15, 13, 11, ....
|
||||
// so each channel gets its own timer.
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] == 0) {
|
||||
int search_channel;
|
||||
int n;
|
||||
if (!cnt_channel) {
|
||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
// search for free channels top down
|
||||
for (search_channel=LEDC_CHANNELS-1; search_channel >=cnt_channel; search_channel -= 2) {
|
||||
bool chanused = false;
|
||||
for (n=0; n < SOC_GPIO_PIN_COUNT; n++) {
|
||||
if (pin_to_channel[n] == search_channel) { // current search_channel used
|
||||
chanused = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chanused)
|
||||
continue;
|
||||
if (n == SOC_GPIO_PIN_COUNT) // current search_channel unused
|
||||
break;
|
||||
}
|
||||
if (search_channel >= cnt_channel) {
|
||||
pin_to_channel[pin] = search_channel;
|
||||
DIAG(F("Pin %d assigned to search channel %d"), pin, search_channel);
|
||||
} else {
|
||||
pin_to_channel[pin] = --cnt_channel; // This sets 15, 13, ...
|
||||
DIAG(F("Pin %d assigned to new channel %d"), pin, cnt_channel);
|
||||
--cnt_channel; // Now we are at 14, 12, ...
|
||||
}
|
||||
ledcSetup(pin_to_channel[pin], 1000, 8);
|
||||
DCCEXledcAttachPin(pin, pin_to_channel[pin], invert);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
// This else is only here so we can enable diag
|
||||
// Pin should be already attached to channel
|
||||
// DIAG(F("Pin %d assigned to old channel %d"), pin, pin_to_channel[pin]);
|
||||
}
|
||||
ledcWrite(pin_to_channel[pin], value);
|
||||
}
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXInrushControlOn(uint8_t pin, int duty, bool inverted) {
|
||||
// this uses hardcoded channel 0
|
||||
ledcSetup(0, 62500, 8);
|
||||
DCCEXledcAttachPin(pin, 0, inverted);
|
||||
ledcWrite(0, duty);
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
pinMode(pin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
|
@@ -80,6 +80,15 @@ extern char *__malloc_heap_start;
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
@@ -125,6 +134,11 @@ void DCCTimer::reset() {
|
||||
while(true){}
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
@@ -76,6 +76,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||
// copied from rf24 branch
|
||||
void TCC0_Handler() {
|
||||
@@ -156,6 +165,11 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
|
@@ -201,6 +201,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
@@ -257,6 +266,23 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
if (f >= 16)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||
else if (f == 7)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||
else if (f >= 4)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||
else if (f >= 3)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||
else if (f >= 2)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||
else if (f == 1)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||
else
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||
}
|
||||
|
||||
// TODO: rationalise the size of these... could really use sparse arrays etc.
|
||||
static HardwareTimer * pin_timer[100] = {0};
|
||||
static uint32_t channel_frequency[100] = {0};
|
||||
@@ -267,7 +293,7 @@ static uint32_t pin_channel[100] = {0};
|
||||
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
|
||||
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
|
||||
// work well... then work backwards to integrate with HA mode if we can.
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency)
|
||||
{
|
||||
if (pin_timer[pin] == NULL) {
|
||||
// Automatically retrieve TIM instance and channel associated to pin
|
||||
@@ -307,7 +333,9 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
||||
if (invert)
|
||||
value = 255-value;
|
||||
// Calculate percentage duty cycle from value given
|
||||
uint32_t duty_cycle = (value * 100 / 256) + 1;
|
||||
if (pin_timer[pin] != NULL) {
|
||||
|
@@ -39,6 +39,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
@@ -141,6 +150,11 @@ void DCCTimer::reset() {
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
@@ -115,8 +115,22 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
}
|
||||
|
||||
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
||||
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
||||
|
||||
|
||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||
if (on) {
|
||||
// TODO check possible
|
||||
railcomActive=true;
|
||||
railcomDebug=debug;
|
||||
}
|
||||
else {
|
||||
railcomActive=false;
|
||||
railcomDebug=false;
|
||||
}
|
||||
return railcomActive;
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
@@ -124,16 +138,16 @@ void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
// set state WAVE_MID_1 for a 1=bit
|
||||
// or WAVE_HIGH_0 for a 0 bit.
|
||||
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
|
||||
|
||||
// As we get to the end of the preambles, open the reminder window.
|
||||
// This delays any reminder insertion until the last moment so
|
||||
// that the reminder doesn't block a more urgent packet.
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
|
||||
if (remainingPreambles==1) promotePendingPacket();
|
||||
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
@@ -157,6 +171,12 @@ void DCCWaveform::interrupt2() {
|
||||
bytes_sent = 0;
|
||||
// preamble for next packet will start...
|
||||
remainingPreambles = requiredPreambles;
|
||||
|
||||
// set the railcom coundown to trigger half way
|
||||
// through the first preamble bit.
|
||||
// Note.. we are still sending the last packet bit
|
||||
// and we then have to allow for the packet end bit
|
||||
if (isMainTrack && railcomActive) DCCTimer::startRailcomTimer(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +228,11 @@ void DCCWaveform::promotePendingPacket() {
|
||||
|
||||
// nothing to do, just send idles or resets
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
// Note: If railcomDebug is on, then we send resets to the main
|
||||
// track instead of idles. This means that all data will be zeros
|
||||
// and only the porersets will be ones, making it much
|
||||
// easier to read on a logic analyser.
|
||||
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
@@ -270,7 +294,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
// The resets will be zero not only now but as well repeats packets into the future
|
||||
clearResets(repeats+1);
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
do {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel != NULL)
|
||||
@@ -297,4 +321,10 @@ bool DCCWaveform::isReminderWindowOpen() {
|
||||
void IRAM_ATTR DCCWaveform::loop() {
|
||||
DCCACK::checkAck(progTrack.getResets());
|
||||
}
|
||||
|
||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||
// TODO... ESP32 railcom waveform
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -40,7 +40,14 @@ const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload s
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
// to the transform array.
|
||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||
enum WAVE_STATE : byte {
|
||||
WAVE_START=0, // wave going high at start of bit
|
||||
WAVE_MID_1=1, // middle of 1 bit
|
||||
WAVE_HIGH_0=2, // first part of 0 bit high
|
||||
WAVE_MID_0=3, // middle of 0 bit
|
||||
WAVE_LOW_0=4, // first part of 0 bit low
|
||||
WAVE_PENDING=5 // next bit not yet known
|
||||
};
|
||||
|
||||
// NOTE: static functions are used for the overall controller, then
|
||||
// one instance is created for each track.
|
||||
@@ -78,6 +85,8 @@ class DCCWaveform {
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
bool isReminderWindowOpen();
|
||||
void promotePendingPacket();
|
||||
static bool setRailcom(bool on, bool debug);
|
||||
static bool isRailcom() {return railcomActive;}
|
||||
|
||||
private:
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
@@ -103,6 +112,9 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
static volatile bool railcomActive; // switched on by user
|
||||
static volatile bool railcomDebug; // switched on by user
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static RMTChannel *rmtMainChannel;
|
||||
static RMTChannel *rmtProgChannel;
|
||||
|
200
EXRAIL2.cpp
200
EXRAIL2.cpp
@@ -54,6 +54,7 @@
|
||||
#include "TrackManager.h"
|
||||
#include "Turntables.h"
|
||||
#include "IODevice.h"
|
||||
#include "EXRAILSensor.h"
|
||||
|
||||
|
||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||
@@ -176,7 +177,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
|
||||
/* static */ void RMFT2::begin() {
|
||||
|
||||
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
|
||||
//DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
|
||||
|
||||
bool saved_diag=diag;
|
||||
diag=true;
|
||||
@@ -251,6 +252,12 @@ if (compileFeatures & FEATURE_SIGNAL) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_ONSENSOR:
|
||||
new EXRAILSensor(operand,progCounter+3,true );
|
||||
break;
|
||||
case OPCODE_ONBUTTON:
|
||||
new EXRAILSensor(operand,progCounter+3,false );
|
||||
break;
|
||||
case OPCODE_TURNOUT: {
|
||||
VPIN id=operand;
|
||||
int addr=getOperand(progCounter,1);
|
||||
@@ -373,7 +380,7 @@ RMFT2::RMFT2(int progCtr) {
|
||||
speedo=0;
|
||||
forward=true;
|
||||
invert=false;
|
||||
timeoutFlag=false;
|
||||
blinkState=not_blink_task;
|
||||
stackDepth=0;
|
||||
onEventStartPosition=-1; // Not handling an ONxxx
|
||||
|
||||
@@ -411,7 +418,7 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||
|
||||
void RMFT2::driveLoco(byte speed) {
|
||||
if (loco<=0) return; // Prevent broadcast!
|
||||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
//if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
/* TODO.....
|
||||
power on appropriate track if DC or main if dcc
|
||||
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||
@@ -480,6 +487,7 @@ bool RMFT2::skipIfBlock() {
|
||||
}
|
||||
|
||||
void RMFT2::loop() {
|
||||
EXRAILSensor::checkAll();
|
||||
|
||||
// Round Robin call to a RMFT task each time
|
||||
if (loopTask==NULL) return;
|
||||
@@ -491,6 +499,23 @@ void RMFT2::loop() {
|
||||
void RMFT2::loop2() {
|
||||
if (delayTime!=0 && millis()-delayStart < delayTime) return;
|
||||
|
||||
// special stand alone blink task
|
||||
if (compileFeatures & FEATURE_BLINK) {
|
||||
if (blinkState==blink_low) {
|
||||
IODevice::write(blinkPin,HIGH);
|
||||
blinkState=blink_high;
|
||||
delayMe(getOperand(1));
|
||||
return;
|
||||
}
|
||||
if (blinkState==blink_high) {
|
||||
IODevice::write(blinkPin,LOW);
|
||||
blinkState=blink_low;
|
||||
delayMe(getOperand(2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal progstep following tasks continue here.
|
||||
byte opcode = GET_OPCODE;
|
||||
int16_t operand = getOperand(0);
|
||||
|
||||
@@ -511,6 +536,10 @@ void RMFT2::loop2() {
|
||||
Turnout::setClosed(operand, true);
|
||||
break;
|
||||
|
||||
case OPCODE_TOGGLE_TURNOUT:
|
||||
Turnout::setClosed(operand, Turnout::isThrown(operand));
|
||||
break;
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_ROTATE:
|
||||
uint8_t activity;
|
||||
@@ -560,39 +589,39 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_AT:
|
||||
timeoutFlag=false;
|
||||
blinkState=not_blink_task;
|
||||
if (readSensor(operand)) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATGTE: // wait for analog sensor>= value
|
||||
timeoutFlag=false;
|
||||
blinkState=not_blink_task;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATLT: // wait for analog sensor < value
|
||||
timeoutFlag=false;
|
||||
blinkState=not_blink_task;
|
||||
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
|
||||
timeoutStart=millis();
|
||||
timeoutFlag=false;
|
||||
blinkState=not_blink_task;
|
||||
break;
|
||||
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
if (readSensor(operand)) break; // success without timeout
|
||||
if (millis()-timeoutStart > 100*getOperand(1)) {
|
||||
timeoutFlag=true;
|
||||
blinkState=at_timeout;
|
||||
break; // and drop through
|
||||
}
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
|
||||
skipIf=!timeoutFlag;
|
||||
skipIf=blinkState!=at_timeout;
|
||||
break;
|
||||
|
||||
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
|
||||
@@ -624,13 +653,25 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_SET:
|
||||
killBlinkOnVpin(operand);
|
||||
IODevice::write(operand,true);
|
||||
break;
|
||||
|
||||
case OPCODE_RESET:
|
||||
killBlinkOnVpin(operand);
|
||||
IODevice::write(operand,false);
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_BLINK:
|
||||
// Start a new task to blink this vpin
|
||||
killBlinkOnVpin(operand);
|
||||
{
|
||||
auto newtask=new RMFT2(progCounter);
|
||||
newtask->blinkPin=operand;
|
||||
newtask->blinkState=blink_low; // will go high on first call
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_PAUSE:
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=this;
|
||||
@@ -669,6 +710,45 @@ void RMFT2::loop2() {
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_SETFREQ:
|
||||
// Frequency is default 0, or 1, 2,3
|
||||
//if (loco) DCC::setFn(loco,operand,true);
|
||||
switch (operand) {
|
||||
case 0: // default - all F-s off
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,true);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,true);
|
||||
DCC::setFn(loco,31,false);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (loco) {
|
||||
DCC::setFn(loco,29,false);
|
||||
DCC::setFn(loco,30,false);
|
||||
DCC::setFn(loco,31,true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
; // do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
pausingTask=NULL;
|
||||
driveLoco(speedo);
|
||||
@@ -776,6 +856,10 @@ void RMFT2::loop2() {
|
||||
case OPCODE_FOFF:
|
||||
if (loco) DCC::setFn(loco,operand,false);
|
||||
break;
|
||||
|
||||
case OPCODE_FTOGGLE:
|
||||
if (loco) DCC::changeFn(loco,operand);
|
||||
break;
|
||||
|
||||
case OPCODE_DRIVE:
|
||||
{
|
||||
@@ -791,6 +875,10 @@ void RMFT2::loop2() {
|
||||
case OPCODE_XFOFF:
|
||||
DCC::setFn(operand,getOperand(1),false);
|
||||
break;
|
||||
|
||||
case OPCODE_XFTOGGLE:
|
||||
DCC::changeFn(operand,getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_DCCACTIVATE: {
|
||||
// operand is address<<3 | subaddr<<1 | active
|
||||
@@ -800,6 +888,14 @@ void RMFT2::loop2() {
|
||||
DCC::setAccessory(addr,subaddr,active);
|
||||
break;
|
||||
}
|
||||
case OPCODE_ASPECT: {
|
||||
// operand is address<<5 | value
|
||||
int16_t address=operand>>5;
|
||||
byte aspect=operand & 0x1f;
|
||||
if (!signalAspectEvent(address,aspect))
|
||||
DCC::setExtendedAccessory(address,aspect);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_FOLLOW:
|
||||
progCounter=routeLookup->find(operand);
|
||||
@@ -978,7 +1074,7 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ROUTE:
|
||||
case OPCODE_AUTOMATION:
|
||||
case OPCODE_SEQUENCE:
|
||||
if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
|
||||
//if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
|
||||
break;
|
||||
|
||||
case OPCODE_AUTOSTART: // Handled only during begin process
|
||||
@@ -996,6 +1092,8 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
case OPCODE_ONBUTTON:
|
||||
case OPCODE_ONSENSOR:
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
|
||||
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
|
||||
@@ -1058,10 +1156,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
//if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
// Schedule any event handler for this signal change.
|
||||
// Thjis will work even without a signal definition.
|
||||
// This will work even without a signal definition.
|
||||
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);
|
||||
@@ -1078,7 +1176,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
|
||||
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
|
||||
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
|
||||
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
//if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
|
||||
@@ -1086,7 +1184,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
// A servo signal, the pin numbers are actually servo positions
|
||||
// Note, setting a signal to a zero position has no effect.
|
||||
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
|
||||
if (diag) DIAG(F("sigA %d %d"),id,servopos);
|
||||
//if (diag) DIAG(F("sigA %d %d"),id,servopos);
|
||||
if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
|
||||
return;
|
||||
}
|
||||
@@ -1098,6 +1196,16 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sigtype== DCCX_SIGNAL_FLAG) {
|
||||
// redpin,amberpin,greenpin are the 3 aspects
|
||||
byte value=redpin;
|
||||
if (rag==SIGNAL_AMBER) value=amberpin;
|
||||
if (rag==SIGNAL_GREEN) value=greenpin;
|
||||
DCC::setExtendedAccessory(sigid & SIGNAL_ID_MASK,value);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// LED or similar 3 pin signal, (all pins zero would be a virtual signal)
|
||||
// If amberpin is zero, synthesise amber from red+green
|
||||
const byte SIMAMBER=0x00;
|
||||
@@ -1110,16 +1218,19 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
if (redpin) {
|
||||
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
|
||||
if (!aHigh) redval=!redval;
|
||||
killBlinkOnVpin(redpin);
|
||||
IODevice::write(redpin,redval);
|
||||
}
|
||||
if (amberpin) {
|
||||
bool amberval=(rag==SIGNAL_AMBER);
|
||||
if (!aHigh) amberval=!amberval;
|
||||
killBlinkOnVpin(amberpin);
|
||||
IODevice::write(amberpin,amberval);
|
||||
}
|
||||
if (greenpin) {
|
||||
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
|
||||
if (!aHigh) greenval=!greenval;
|
||||
killBlinkOnVpin(greenpin);
|
||||
IODevice::write(greenpin,greenval);
|
||||
}
|
||||
}
|
||||
@@ -1131,6 +1242,38 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||
}
|
||||
|
||||
|
||||
// signalAspectEvent returns true if the aspect is destined
|
||||
// for a defined DCCX_SIGNAL which will handle all the RAG flags
|
||||
// and ON* handlers.
|
||||
// Otherwise false so the parser should send the command directly
|
||||
bool RMFT2::signalAspectEvent(int16_t address, byte aspect ) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
|
||||
int16_t sigslot=getSignalSlot(address);
|
||||
if (sigslot<0) return false; // this is not a defined signal
|
||||
int16_t sigpos=sigslot*8;
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
if (sigtype!=DCCX_SIGNAL_FLAG) return false; // not a DCCX signal
|
||||
// Turn an aspect change into a RED/AMBER/GREEN setting
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2)) {
|
||||
doSignal(sigid,SIGNAL_RED);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4)) {
|
||||
doSignal(sigid,SIGNAL_AMBER);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6)) {
|
||||
doSignal(sigid,SIGNAL_GREEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // aspect is not a defined one
|
||||
}
|
||||
|
||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
|
||||
@@ -1159,7 +1302,7 @@ void RMFT2::rotateEvent(int16_t turntableId, bool change) {
|
||||
void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
// Hunt for an ONTIME for this time
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||
DIAG(F("clockEvent at : %d"), clocktime);
|
||||
if (change) {
|
||||
onClockLookup->handleEvent(F("CLOCK"),clocktime);
|
||||
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
|
||||
@@ -1169,12 +1312,31 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||
// Hunt for an ONOVERLOAD for this item
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for Power event on track : %c"), track);
|
||||
DIAG(F("powerEvent : %c"), track);
|
||||
if (overload) {
|
||||
onOverloadLookup->handleEvent(F("POWER"),track);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used when setting pins so that a SET or RESET
|
||||
// will cause any blink task on that pin to terminate.
|
||||
// It will be compiled out of existence if no BLINK feature is used.
|
||||
void RMFT2::killBlinkOnVpin(VPIN pin) {
|
||||
if (!(compileFeatures & FEATURE_BLINK)) return;
|
||||
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (
|
||||
(task->blinkState==blink_high || task->blinkState==blink_low)
|
||||
&& task->blinkPin==pin) {
|
||||
task->kill();
|
||||
return;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) return;
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
|
||||
// Check we dont already have a task running this handler
|
||||
RMFT2 * task=loopTask;
|
||||
@@ -1245,6 +1407,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
break;
|
||||
case thrunge_parse:
|
||||
case thrunge_broadcast:
|
||||
case thrunge_message:
|
||||
case thrunge_lcd:
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (!buffer) buffer=new StringBuffer();
|
||||
@@ -1282,6 +1445,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
case thrunge_withrottle:
|
||||
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
|
||||
break;
|
||||
case thrunge_message:
|
||||
CommandDistributor::broadcastMessage(buffer->getString());
|
||||
break;
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
break;
|
||||
|
49
EXRAIL2.h
49
EXRAIL2.h
@@ -33,7 +33,7 @@
|
||||
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
|
||||
// searching easier as a parameter can never be confused with an opcode.
|
||||
//
|
||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||
OPCODE_RESERVE,OPCODE_FREE,
|
||||
OPCODE_AT,OPCODE_AFTER,
|
||||
@@ -41,9 +41,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ATGTE,OPCODE_ATLT,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||
OPCODE_BLINK,
|
||||
OPCODE_ENDIF,OPCODE_ELSE,
|
||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
@@ -51,10 +53,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
|
||||
#endif
|
||||
OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SETFREQ,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,OPCODE_ASPECT,
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
@@ -71,7 +73,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
OPCODE_ROUTE_DISABLED,
|
||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||
|
||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
// see skipIfBlock()
|
||||
@@ -94,16 +96,25 @@ enum thrunger: byte {
|
||||
thrunge_serial,thrunge_parse,
|
||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcn,
|
||||
thrunge_lcn,thrunge_message,
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
|
||||
enum BlinkState: byte {
|
||||
not_blink_task,
|
||||
blink_low, // blink task running with pin LOW
|
||||
blink_high, // blink task running with pin high
|
||||
at_timeout // ATTIMEOUT timed out flag
|
||||
};
|
||||
|
||||
// Flag bits for compile time features.
|
||||
static const byte FEATURE_SIGNAL= 0x80;
|
||||
static const byte FEATURE_LCC = 0x40;
|
||||
static const byte FEATURE_ROSTER= 0x20;
|
||||
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||
static const byte FEATURE_STASH = 0x08;
|
||||
static const byte FEATURE_BLINK = 0x04;
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
@@ -155,9 +166,11 @@ class LookList {
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static void rotateEvent(int16_t id, bool change);
|
||||
static void powerEvent(int16_t track, bool overload);
|
||||
static bool signalAspectEvent(int16_t address, byte aspect );
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
static const int16_t DCCX_SIGNAL_FLAG=0x3000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
@@ -172,7 +185,7 @@ class LookList {
|
||||
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[]) ;
|
||||
@@ -190,6 +203,7 @@ private:
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static void killBlinkOnVpin(VPIN pin);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
@@ -242,10 +256,10 @@ private:
|
||||
union {
|
||||
unsigned long waitAfter; // Used by OPCODE_AFTER
|
||||
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
|
||||
VPIN blinkPin; // Used by blink tasks
|
||||
};
|
||||
bool timeoutFlag;
|
||||
byte taskId;
|
||||
|
||||
BlinkState blinkState; // includes AT_TIMEOUT flag.
|
||||
uint16_t loco;
|
||||
bool forward;
|
||||
bool invert;
|
||||
@@ -258,4 +272,23 @@ private:
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
// IO_I2CDFPlayer commands and values
|
||||
enum : uint8_t{
|
||||
DF_PLAY = 0x0F,
|
||||
DF_VOL = 0x06,
|
||||
DF_FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||
DF_REPEATPLAY = 0x08,
|
||||
DF_STOPPLAY = 0x16,
|
||||
DF_EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||
DF_RESET = 0x0C,
|
||||
DF_DACON = 0x1A,
|
||||
DF_SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||
DF_NORMAL = 0x00, // Equalizer parameters
|
||||
DF_POP = 0x01,
|
||||
DF_ROCK = 0x02,
|
||||
DF_JAZZ = 0x03,
|
||||
DF_CLASSIC = 0x04,
|
||||
DF_BASS = 0x05,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -31,18 +31,22 @@
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef ANOUT
|
||||
#undef ASPECT
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
#undef ATTIMEOUT
|
||||
#undef AUTOMATION
|
||||
#undef AUTOSTART
|
||||
#undef BLINK
|
||||
#undef BROADCAST
|
||||
#undef CALL
|
||||
#undef CLEAR_STASH
|
||||
#undef CLEAR_ALL_STASH
|
||||
#undef CLOSE
|
||||
#undef CONFIGURE_SERVO
|
||||
#undef DCC_SIGNAL
|
||||
#undef DCCX_SIGNAL
|
||||
#undef DCC_TURNTABLE
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
@@ -63,10 +67,12 @@
|
||||
#undef FOLLOW
|
||||
#undef FON
|
||||
#undef FORGET
|
||||
#undef FTOGGLE
|
||||
#undef FREE
|
||||
#undef FWD
|
||||
#undef GREEN
|
||||
#undef HAL
|
||||
#undef HAL_IGNORE_DEFAULTS
|
||||
#undef IF
|
||||
#undef IFAMBER
|
||||
#undef IFCLOSED
|
||||
@@ -83,6 +89,7 @@
|
||||
#undef IFTTPOSITION
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JMRI_SENSOR
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
#undef LATCH
|
||||
@@ -92,6 +99,7 @@
|
||||
#undef LCCX
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef MESSAGE
|
||||
#undef ONACTIVATE
|
||||
#undef ONACTIVATEL
|
||||
#undef ONAMBER
|
||||
@@ -106,6 +114,8 @@
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONROTATE
|
||||
#undef ONBUTTON
|
||||
#undef ONSENSOR
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
@@ -151,14 +161,17 @@
|
||||
#undef SET_TRACK
|
||||
#undef SET_POWER
|
||||
#undef SETLOCO
|
||||
#undef SETFREQ
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STASH
|
||||
#undef STEALTH
|
||||
#undef STEALTH_GLOBAL
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef TOGGLE_TURNOUT
|
||||
#undef TT_ADDPOSITION
|
||||
#undef TURNOUT
|
||||
#undef TURNOUTL
|
||||
@@ -173,6 +186,7 @@
|
||||
#undef WITHROTTLE
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
#undef XFTOGGLE
|
||||
|
||||
#ifndef RMFT2_UNDEF_ONLY
|
||||
#define ACTIVATE(addr,subaddr)
|
||||
@@ -183,17 +197,21 @@
|
||||
#define AMBER(signal_id)
|
||||
#define ANOUT(vpin,value,param1,param2)
|
||||
#define AT(sensor_id)
|
||||
#define ASPECT(address,value)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||
#define AUTOMATION(id,description)
|
||||
#define AUTOSTART
|
||||
#define BLINK(vpin,onDuty,offDuty)
|
||||
#define BROADCAST(msg)
|
||||
#define CALL(route)
|
||||
#define CLEAR_STASH(id)
|
||||
#define CLEAR_ALL_STASH(id)
|
||||
#define CLOSE(id)
|
||||
#define CLOSE(id)
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||
#define DCC_TURNTABLE(id,home,description)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
@@ -215,9 +233,11 @@
|
||||
#define FON(func)
|
||||
#define FORGET
|
||||
#define FREE(blockid)
|
||||
#define FTOGGLE(func)
|
||||
#define FWD(speed)
|
||||
#define GREEN(signal_id)
|
||||
#define HAL(haltype,params...)
|
||||
#define HAL_IGNORE_DEFAULTS
|
||||
#define IF(sensor_id)
|
||||
#define IFAMBER(signal_id)
|
||||
#define IFCLOSED(turnout_id)
|
||||
@@ -234,6 +254,7 @@
|
||||
#define IFTTPOSITION(turntable_id,position)
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JMRI_SENSOR(vpin,count...)
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
@@ -242,6 +263,7 @@
|
||||
#define LCD(row,msg)
|
||||
#define SCREEN(display,row,msg)
|
||||
#define LCN(msg)
|
||||
#define MESSAGE(msg)
|
||||
#define MOVETT(id,steps,activity)
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
@@ -259,6 +281,8 @@
|
||||
#define ONROTATE(turntable_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define ONCHANGE(sensor_id)
|
||||
#define ONSENSOR(sensor_id)
|
||||
#define ONBUTTON(sensor_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
@@ -302,14 +326,17 @@
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
#define SETFREQ(loco,freq)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STASH(id)
|
||||
#define STEALTH(code...)
|
||||
#define STEALTH_GLOBAL(code...)
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#define TOGGLE_TURNOUT(id)
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define TURNOUTL(id,addr,description...)
|
||||
@@ -324,4 +351,6 @@
|
||||
#define WITHROTTLE(msg)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
#define XFTOGGLE(cab,func)
|
||||
|
||||
#endif
|
||||
|
@@ -28,25 +28,7 @@
|
||||
#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';
|
||||
const int16_t HASH_KEYWORD_M='M';
|
||||
|
||||
#include "KeywordHasher.h"
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
@@ -54,21 +36,28 @@ const int16_t HASH_KEYWORD_M='M';
|
||||
|
||||
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);
|
||||
if (p[0]=="EXRAIL"_hk) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]=="ON"_hk || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
reject=!parseSlash(stream,paramCount,p);
|
||||
opcode=0;
|
||||
if (parseSlash(stream,paramCount,p)) opcode=0;
|
||||
break;
|
||||
|
||||
case 'A': // <A address aspect>
|
||||
if (paramCount!=2) break;
|
||||
// Ask exrail if this is just changing the aspect on a
|
||||
// predefined DCCX_SIGNAL. Because this will handle all
|
||||
// the IFRED and ONRED type issues at the same time.
|
||||
if (signalAspectEvent(p[0],p[1])) opcode=0; // all done
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
// This entire code block is compiled out if LLC macros not used
|
||||
if (!(compileFeatures & FEATURE_LCC)) return;
|
||||
@@ -116,16 +105,18 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
||||
}
|
||||
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
|
||||
int16_t eventid=p[0];
|
||||
reject=eventid<0 || eventid>=countLCCLookup;
|
||||
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
opcode=0;
|
||||
bool reject = eventid<0 || eventid>=countLCCLookup;
|
||||
if (!reject) {
|
||||
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
opcode=0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'J': // throttle info commands
|
||||
if (paramCount<1) return;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
case "A"_hk: // <JA> returns automations/routes
|
||||
if (paramCount==1) {// <JA>
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
routeLookup->stream(stream);
|
||||
@@ -152,7 +143,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HASH_KEYWORD_M:
|
||||
case "M"_hk:
|
||||
// NOTE: we only need to handle valid calls here because
|
||||
// DCCEXParser has to have code to handle the <J<> cases where
|
||||
// exrail isnt involved anyway.
|
||||
@@ -192,12 +183,20 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if ((compileFeatures & FEATURE_BLINK)
|
||||
&& (task->blinkState==blink_high || task->blinkState==blink_low)) {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,BLINK=%d"),
|
||||
(int)(task->taskId),task->progCounter,task->blinkPin
|
||||
);
|
||||
}
|
||||
else {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
|
||||
(int)(task->taskId),task->progCounter,task->loco,
|
||||
task->invert?'I':' ',
|
||||
task->speedo,
|
||||
task->forward?'F':'R'
|
||||
);
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
@@ -236,13 +235,13 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_PAUSE: // </ PAUSE>
|
||||
case "PAUSE"_hk: // </ 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>
|
||||
case "RESUME"_hk: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{
|
||||
@@ -256,7 +255,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
return true;
|
||||
|
||||
|
||||
case HASH_KEYWORD_START: // </ START [cab] route >
|
||||
case "START"_hk: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
@@ -273,7 +272,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
if (p[0]=="KILL"_hk && p[1]=="ALL"_hk) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
@@ -282,7 +281,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
case "KILL"_hk: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
@@ -297,27 +296,27 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
case "RESERVE"_hk: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
case "FREE"_hk: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
case "LATCH"_hk:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
case "UNLATCH"_hk:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
case "RED"_hk:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
case "AMBER"_hk:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
case "GREEN"_hk:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
|
112
EXRAILMacros.h
112
EXRAILMacros.h
@@ -59,6 +59,10 @@
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// PLAYSOUND is alias of ANOUT to make the user experience of a Conductor beter for
|
||||
// playing sounds with IO_I2CDFPlayer
|
||||
#define PLAYSOUND ANOUT
|
||||
|
||||
// helper macro to strip leading zeros off time inputs
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
@@ -74,13 +78,94 @@
|
||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1d Detect sequence duplicates.
|
||||
// This pass generates no runtime data or code
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
#undef SEQUENCE
|
||||
#define SEQUENCE(id) id,
|
||||
constexpr int16_t compileTimeSequenceList[]={
|
||||
#include "myAutomation.h"
|
||||
0
|
||||
};
|
||||
constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
|
||||
|
||||
|
||||
// Compile time function to check for sequence nos.
|
||||
constexpr bool hasseq(const int16_t value, const int16_t pos=0 ) {
|
||||
return pos>=stuffSize? false :
|
||||
compileTimeSequenceList[pos]==value
|
||||
|| hasseq(value,pos+1);
|
||||
}
|
||||
|
||||
// Compile time function to check for duplicate sequence nos.
|
||||
constexpr bool hasdup(const int16_t value, const int16_t pos ) {
|
||||
return pos>=stuffSize? false :
|
||||
compileTimeSequenceList[pos]==value
|
||||
|| hasseq(value,pos+1)
|
||||
|| hasdup(compileTimeSequenceList[pos],pos+1);
|
||||
}
|
||||
|
||||
|
||||
static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AUTOMATION detected");
|
||||
|
||||
//pass 1s static asserts to
|
||||
// - check call and follows etc for existing sequence numbers
|
||||
// - check range on LATCH/UNLATCH
|
||||
// This pass generates no runtime data or code
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ASPECT
|
||||
#define ASPECT(address,value) static_assert(address <=2044, "invalid Address"); \
|
||||
static_assert(address>=-3, "Invalid value");
|
||||
#undef CALL
|
||||
#define CALL(id) static_assert(hasseq(id),"Sequence not found");
|
||||
#undef FOLLOW
|
||||
#define FOLLOW(id) static_assert(hasseq(id),"Sequence not found");
|
||||
#undef START
|
||||
#define START(id) static_assert(hasseq(id),"Sequence not found");
|
||||
#undef SENDLOCO
|
||||
#define SENDLOCO(cab,id) static_assert(hasseq(id),"Sequence not found");
|
||||
#undef LATCH
|
||||
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef UNLATCH
|
||||
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef RESERVE
|
||||
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef FREE
|
||||
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef SPEED
|
||||
#define SPEED(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||
#undef FWD
|
||||
#define FWD(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||
#undef REV
|
||||
#define REV(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1g Implants STEALTH_GLOBAL in correct place
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef STEALTH_GLOBAL
|
||||
#define STEALTH_GLOBAL(code...) code
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
// Also allows creating EXTurntable object
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
void exrailHalSetup() {
|
||||
#undef HAL_IGNORE_DEFAULTS
|
||||
#define HAL_IGNORE_DEFAULTS ignore_defaults=true;
|
||||
#undef JMRI_SENSOR
|
||||
#define JMRI_SENSOR(vpin,count...) Sensor::createMultiple(vpin,##count);
|
||||
#undef CONFIGURE_SERVO
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile) IODevice::configureServo(vpin,pos1,pos2,PCA9685::profile);
|
||||
bool exrailHalSetup() {
|
||||
bool ignore_defaults=false;
|
||||
#include "myAutomation.h"
|
||||
return ignore_defaults;
|
||||
}
|
||||
|
||||
// Pass 1c detect compile time featurtes
|
||||
@@ -93,6 +178,8 @@ void exrailHalSetup() {
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
|
||||
#undef DCCX_SIGNAL
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) | FEATURE_SIGNAL
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
|
||||
|
||||
@@ -121,6 +208,8 @@ void exrailHalSetup() {
|
||||
#define PICKUP_STASH(id) | FEATURE_STASH
|
||||
#undef STASH
|
||||
#define STASH(id) | FEATURE_STASH
|
||||
#undef BLINK
|
||||
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
|
||||
|
||||
const byte RMFT2::compileFeatures = 0
|
||||
#include "myAutomation.h"
|
||||
@@ -172,6 +261,9 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef MESSAGE
|
||||
#define MESSAGE(msg) THRUNGE(msg,thrunge_message)
|
||||
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
@@ -269,6 +361,8 @@ const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,home,description...) T_DESC(turntable_id,position,description)
|
||||
|
||||
const FSH * RMFT2::getTurntablePositionDescription(int16_t turntableId, uint8_t positionId) {
|
||||
(void)turntableId;
|
||||
(void)positionId;
|
||||
#include "myAutomation.h"
|
||||
return NULL;
|
||||
}
|
||||
@@ -322,6 +416,8 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
|
||||
#undef DCCX_SIGNAL
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) id | RMFT2::DCCX_SIGNAL_FLAG,redAspect,amberAspect,greenAspect,
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
@@ -356,17 +452,20 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
|
||||
#define ASPECT(address,value) OPCODE_ASPECT,V((address<<5) | (value & 0x1F)),
|
||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||
#define BLINK(vpin,onDuty,offDuty) OPCODE_BLINK,V(vpin),OPCODE_PAD,V(onDuty),OPCODE_PAD,V(offDuty),
|
||||
#define BROADCAST(msg) PRINT(msg)
|
||||
#define CALL(route) OPCODE_CALL,V(route),
|
||||
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
|
||||
#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0),
|
||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#ifndef IO_NO_HAL
|
||||
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
@@ -376,6 +475,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||
#define DONE OPCODE_ENDTASK,0,0,
|
||||
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||
#define ELSE OPCODE_ELSE,0,0,
|
||||
@@ -393,9 +493,11 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define FON(func) OPCODE_FON,V(func),
|
||||
#define FORGET OPCODE_FORGET,0,0,
|
||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||
#define FTOGGLE(func) OPCODE_FTOGGLE,V(func),
|
||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||
#define HAL(haltype,params...)
|
||||
#define HAL_IGNORE_DEFAULTS
|
||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
@@ -414,6 +516,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#endif
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JMRI_SENSOR(vpin,count...)
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
@@ -425,7 +528,9 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define STEALTH(code...) PRINT(dummy)
|
||||
#define STEALTH_GLOBAL(code...)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
#define MESSAGE(msg) PRINT(msg)
|
||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
@@ -448,6 +553,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#endif
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||
#define ONSENSOR(sensor_id) OPCODE_ONSENSOR,V(sensor_id),
|
||||
#define ONBUTTON(sensor_id) OPCODE_ONBUTTON,V(sensor_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
@@ -493,6 +600,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#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 SETFREQ(loco,freq) OPCODE_SETLOCO,V(loco), OPCODE_SETFREQ,V(freq),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
@@ -500,6 +608,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define STASH(id) OPCODE_STASH,V(id),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#define TOGGLE_TURNOUT(id) OPCODE_TOGGLE_TURNOUT,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
|
||||
#endif
|
||||
@@ -516,6 +625,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#endif
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
|
||||
|
||||
// Build RouteCode
|
||||
const int StringMacroTracker2=__COUNTER__;
|
||||
|
104
EXRAILSensor.cpp
Normal file
104
EXRAILSensor.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* © 2024 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**********************************************************************
|
||||
EXRAILSensor represents a sensor that should be monitored in order
|
||||
to call an exrail ONBUTTON or ONCHANGE handler.
|
||||
These are created at EXRAIL startup and thus need no delete or listing
|
||||
capability.
|
||||
The basic logic is similar to that found in the Sensor class
|
||||
except that on the relevant change an EXRAIL thread is started.
|
||||
**********************************************************************/
|
||||
|
||||
#include "EXRAILSensor.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
void EXRAILSensor::checkAll() {
|
||||
if (firstSensor == NULL) return; // No sensors to be scanned
|
||||
if (readingSensor == NULL) {
|
||||
// Not currently scanning sensor list
|
||||
unsigned long thisTime = micros();
|
||||
if (thisTime - lastReadCycle < cycleInterval) return;
|
||||
// Required time has elapsed since last read cycle started,
|
||||
// so initiate new scan through the sensor list
|
||||
readingSensor = firstSensor;
|
||||
lastReadCycle = thisTime;
|
||||
}
|
||||
|
||||
// Loop until either end of list is encountered or we pause for some reason
|
||||
byte sensorCount = 0;
|
||||
|
||||
while (readingSensor != NULL) {
|
||||
bool pause=readingSensor->check();
|
||||
// Move to next sensor in list.
|
||||
readingSensor = readingSensor->nextSensor;
|
||||
// Currently process max of 16 sensors per entry.
|
||||
// Performance measurements taken during development indicate that, with 128 sensors configured
|
||||
// on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices
|
||||
// within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond.
|
||||
if (pause || (++sensorCount)>=16) return;
|
||||
}
|
||||
}
|
||||
|
||||
bool EXRAILSensor::check() {
|
||||
// check for debounced change in this sensor
|
||||
inputState = IODevice::read(pin);
|
||||
|
||||
// Check if changed since last time, and process changes.
|
||||
if (inputState == active) {// no change
|
||||
latchDelay = minReadCount; // Reset counter
|
||||
return false; // no change
|
||||
}
|
||||
|
||||
// Change detected ... has it stayed changed for long enough
|
||||
if (latchDelay > 0) {
|
||||
latchDelay--;
|
||||
return false;
|
||||
}
|
||||
|
||||
// change validated, act on it.
|
||||
active = inputState;
|
||||
latchDelay = minReadCount; // Reset debounce counter
|
||||
if (onChange || active) {
|
||||
new RMFT2(progCounter);
|
||||
return true; // Don't check any more sensors on this entry
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) {
|
||||
// Add to the start of the list
|
||||
//DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter);
|
||||
nextSensor = firstSensor;
|
||||
firstSensor = this;
|
||||
|
||||
pin=_pin;
|
||||
progCounter=_progCounter;
|
||||
onChange=_onChange;
|
||||
|
||||
IODevice::configureInput(pin, true);
|
||||
active = IODevice::read(pin);
|
||||
inputState = active;
|
||||
latchDelay = minReadCount;
|
||||
}
|
||||
|
||||
EXRAILSensor *EXRAILSensor::firstSensor=NULL;
|
||||
EXRAILSensor *EXRAILSensor::readingSensor=NULL;
|
||||
unsigned long EXRAILSensor::lastReadCycle=0;
|
50
EXRAILSensor.h
Normal file
50
EXRAILSensor.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* © 2024 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EXRAILSensor_h
|
||||
#define EXRAILSensor_h
|
||||
#include "IODevice.h"
|
||||
class EXRAILSensor {
|
||||
static EXRAILSensor * firstSensor;
|
||||
static EXRAILSensor * readingSensor;
|
||||
static unsigned long lastReadCycle;
|
||||
|
||||
public:
|
||||
static void checkAll();
|
||||
|
||||
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange);
|
||||
bool check();
|
||||
|
||||
private:
|
||||
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
|
||||
// should not be less than device scan cycle time.
|
||||
static const byte minReadCount = 4; // number of additional scans before acting on change
|
||||
// E.g. 1 means that a change is ignored for one scan and actioned on the next.
|
||||
// Max value is 63
|
||||
|
||||
EXRAILSensor* nextSensor;
|
||||
VPIN pin;
|
||||
int progCounter;
|
||||
bool active;
|
||||
bool inputState;
|
||||
bool onChange;
|
||||
byte latchDelay;
|
||||
};
|
||||
#endif
|
@@ -47,6 +47,10 @@ void EthernetInterface::setup()
|
||||
};
|
||||
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
static IPAddress myIP(IP_ADDRESS);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Aquire IP Address from DHCP and start server
|
||||
*
|
||||
@@ -60,14 +64,14 @@ EthernetInterface::EthernetInterface()
|
||||
connected=false;
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
if (Ethernet.begin(mac, IP_ADDRESS) == 0)
|
||||
Ethernet.begin(mac, myIP);
|
||||
#else
|
||||
if (Ethernet.begin(mac) == 0)
|
||||
#endif
|
||||
{
|
||||
DIAG(F("Ethernet.begin FAILED"));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found or W5100"));
|
||||
}
|
||||
@@ -136,7 +140,7 @@ bool EthernetInterface::checkLink() {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||
Ethernet.setLocalIP(myIP); // for static IP, set it again
|
||||
#endif
|
||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202401100719Z"
|
||||
#define GITHUB_SHA "devel-202404061747Z"
|
||||
|
@@ -54,6 +54,8 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
return F("Time-of-flight sensor");
|
||||
else if (address >= 0x3c && address <= 0x3d)
|
||||
return F("OLED Display");
|
||||
else if (address >= 0x48 && address <= 0x57) // SC16IS752x UART detection
|
||||
return F("SC16IS75x UART");
|
||||
else if (address >= 0x48 && address <= 0x4f)
|
||||
return F("Analogue Inputs or PWM");
|
||||
else if (address >= 0x40 && address <= 0x4f)
|
||||
@@ -363,4 +365,4 @@ void I2CAddress::toHex(const uint8_t value, char *buffer) {
|
||||
|
||||
/* static */ bool I2CAddress::_addressWarningDone = false;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -110,7 +110,6 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
// Use 10x the rise time spec to enable integer divide of 50ns clock period
|
||||
uint16_t t_rise;
|
||||
uint32_t ccr_freq;
|
||||
|
||||
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
|
||||
// writes to CR1 while STOP is being executed!
|
||||
|
44
IODevice.cpp
44
IODevice.cpp
@@ -33,7 +33,7 @@
|
||||
|
||||
// Link to halSetup function. If not defined, the function reference will be NULL.
|
||||
extern __attribute__((weak)) void halSetup();
|
||||
extern __attribute__((weak)) void exrailHalSetup();
|
||||
extern __attribute__((weak)) bool exrailHalSetup();
|
||||
|
||||
//==================================================================================================================
|
||||
// Static methods
|
||||
@@ -60,34 +60,31 @@ void IODevice::begin() {
|
||||
halSetup();
|
||||
|
||||
// include any HAL devices defined in exrail.
|
||||
bool ignoreDefaults=false;
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
ignoreDefaults=exrailHalSetup();
|
||||
if (ignoreDefaults) return;
|
||||
|
||||
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
||||
// Allocates 32 pins 100-131
|
||||
if (checkNoOverlap(100, 16, 0x40)) {
|
||||
const bool silent=true; // no message if these conflict
|
||||
if (checkNoOverlap(100, 16, 0x40, silent)) {
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
} else {
|
||||
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
|
||||
}
|
||||
if (checkNoOverlap(116, 16, 0x41)) {
|
||||
}
|
||||
|
||||
if (checkNoOverlap(116, 16, 0x41, silent)) {
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
} else {
|
||||
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
|
||||
}
|
||||
}
|
||||
|
||||
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
|
||||
// Allocates 32 pins 164-195
|
||||
if (checkNoOverlap(164, 16, 0x20)) {
|
||||
if (checkNoOverlap(164, 16, 0x20, silent)) {
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
} else {
|
||||
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
|
||||
}
|
||||
if (checkNoOverlap(180, 16, 0x21)) {
|
||||
}
|
||||
|
||||
if (checkNoOverlap(180, 16, 0x21, silent)) {
|
||||
MCP23017::create(180, 16, 0x21);
|
||||
} else {
|
||||
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset() function to reinitialise all devices
|
||||
@@ -339,7 +336,10 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||
// returns true if pins DONT overlap with existing device
|
||||
// TODO: Move the I2C address reservation and checks into the I2CManager code.
|
||||
// That will enable non-HAL devices to reserve I2C addresses too.
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
||||
// Silent is used by the default setup so that there is no message if the default
|
||||
// device has already been handled by the user setup.
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins,
|
||||
I2CAddress i2cAddress, bool silent) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
|
||||
#endif
|
||||
@@ -352,14 +352,14 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
|
||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||
if (!noOverlap) {
|
||||
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
||||
if (!silent) DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check for overlapping I2C address
|
||||
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
||||
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
|
||||
if (!silent) DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -166,7 +166,8 @@ public:
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1,
|
||||
I2CAddress i2cAddress=0, bool silent=false);
|
||||
|
||||
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
|
||||
// pin range.
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
* © 2024, Harald Barth. All rights reserved.
|
||||
*
|
||||
* This file is part of EX-CommandStation
|
||||
*
|
||||
@@ -100,8 +101,14 @@ private:
|
||||
if (_digitalPinBytes < digitalBytesNeeded) {
|
||||
// Not enough space, free any existing buffer and allocate a new one
|
||||
if (_digitalPinBytes > 0) free(_digitalInputStates);
|
||||
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
||||
_digitalPinBytes = digitalBytesNeeded;
|
||||
if ((_digitalInputStates = (byte*) calloc(digitalBytesNeeded, 1)) != NULL) {
|
||||
_digitalPinBytes = digitalBytesNeeded;
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander I2C:%s ERROR alloc %d bytes"), _I2CAddress.toString(), digitalBytesNeeded);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_digitalPinBytes = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +124,16 @@ private:
|
||||
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
_analoguePinBytes = analogueBytesNeeded;
|
||||
if (_analogueInputStates != NULL &&
|
||||
_analogueInputBuffer != NULL &&
|
||||
_analoguePinMap != NULL) {
|
||||
_analoguePinBytes = analogueBytesNeeded;
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander I2C:%s ERROR alloc analog pin bytes"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_analoguePinBytes = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -241,7 +257,7 @@ private:
|
||||
|
||||
// If we're not doing anything now, check to see if a new input transfer is due.
|
||||
if (_readState == RDS_IDLE) {
|
||||
if (currentMicros - _lastDigitalRead > _digitalRefresh && _numDigitalPins>0) { // Delay for digital read refresh
|
||||
if (_numDigitalPins>0 && currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
|
||||
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
|
||||
// be allocated from heap (object state).
|
||||
_readCommandBuffer[0] = EXIORDD;
|
||||
@@ -249,7 +265,7 @@ private:
|
||||
// non-blocking read
|
||||
_lastDigitalRead = currentMicros;
|
||||
_readState = RDS_DIGITAL;
|
||||
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh && _numAnaloguePins>0) { // Delay for analogue read refresh
|
||||
} else if (_numAnaloguePins>0 && currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
|
||||
// Issue new read for analogue input states
|
||||
_readCommandBuffer[0] = EXIORDAN;
|
||||
I2CManager.read(_I2CAddress, _analogueInputBuffer,
|
||||
@@ -364,14 +380,14 @@ private:
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
|
||||
uint8_t* _digitalInputStates;
|
||||
uint8_t* _analogueInputStates;
|
||||
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers
|
||||
uint8_t* _digitalInputStates = NULL;
|
||||
uint8_t* _analogueInputStates = NULL;
|
||||
uint8_t* _analogueInputBuffer = NULL; // buffer for I2C input transfers
|
||||
uint8_t _readCommandBuffer[1];
|
||||
|
||||
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
|
||||
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed)
|
||||
uint8_t* _analoguePinMap;
|
||||
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
|
||||
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
|
||||
uint8_t* _analoguePinMap = NULL;
|
||||
I2CRB _i2crb;
|
||||
|
||||
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states
|
||||
|
@@ -83,6 +83,7 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
// Read returns status as obtained in our loop.
|
||||
// Return false if our status value is invalid.
|
||||
int EXTurntable::_read(VPIN vpin) {
|
||||
(void)vpin; // surpress warning
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
if (_stepperStatus > 1) {
|
||||
return false;
|
||||
@@ -127,6 +128,8 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#else
|
||||
(void)duration;
|
||||
#endif
|
||||
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
_previousStatus = _stepperStatus;
|
||||
|
@@ -1,7 +1,9 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
* © 2024, Paul Antoine
|
||||
* © 2023, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -112,13 +114,14 @@ protected:
|
||||
// Fill buffer with spaces
|
||||
memset(_buffer, ' ', _numCols*_numRows);
|
||||
|
||||
_displayDriver->clearNative();
|
||||
|
||||
// Add device to list of HAL devices (not necessary but allows
|
||||
// status to be displayed using <D HAL SHOW> and device to be
|
||||
// reinitialised using <D HAL RESET>).
|
||||
IODevice::addDevice(this);
|
||||
|
||||
// Moved after addDevice() to ensure I2CManager.begin() has been called fisrt
|
||||
_displayDriver->clearNative();
|
||||
|
||||
// Also add this display to list of display handlers
|
||||
DisplayInterface::addDisplay(displayNo);
|
||||
|
||||
|
805
IO_I2CDFPlayer.h
Normal file
805
IO_I2CDFPlayer.h
Normal file
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||
* amplifier, so it only needs a power supply and a speaker.
|
||||
* This driver is a modified version of the IO_DFPlayer.h file
|
||||
* *********************************************************************************************
|
||||
*
|
||||
* Dec 2023, Added NXP SC16IS752 I2C Dual UART to enable the DFPlayer connection over the I2C bus
|
||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||
* needed as the RX Fifo holds the reply
|
||||
*
|
||||
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
||||
* corrupt data. This need more analysis and experimenatation.
|
||||
* Will push this driver to the dev branch with the uart fixed to 0
|
||||
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
||||
*
|
||||
* myHall.cpp configuration syntax:
|
||||
*
|
||||
* I2CDFPlayer::create(1st vPin, vPins, I2C address, xtal);
|
||||
*
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
||||
* 1st vPin for UART 0, 2nd for UART 1
|
||||
* I2C Address : I2C address of the serial controller, in 0x format
|
||||
* xtal : 0 for 1,8432Mhz, 1 for 14,7456Mhz
|
||||
*
|
||||
* The vPin is also a pin that can be read, it indicate if the DFPlayer has finished playing a track
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_I2CDFPlayer_h
|
||||
#define IO_I2CDFPlayer_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
//#define DIAG_I2CDFplayer
|
||||
//#define DIAG_I2CDFplayer_data
|
||||
//#define DIAG_I2CDFplayer_reg
|
||||
//#define DIAG_I2CDFplayer_playing
|
||||
|
||||
class I2CDFPlayer : public IODevice {
|
||||
private:
|
||||
const uint8_t MAXVOLUME=30;
|
||||
uint8_t RETRYCOUNT = 0x03;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
bool _repeat = false; // audio file is repeat playing
|
||||
uint8_t _previousCmd = true;
|
||||
// SC16IS752 defines
|
||||
I2CAddress _I2CAddress;
|
||||
I2CRB _rb;
|
||||
uint8_t _UART_CH=0x00; // Fix uart ch to 0 for now
|
||||
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit
|
||||
uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
uint32_t BAUD_RATE = 9600;
|
||||
uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
uint8_t TEMP_REG_VAL = 0x00;
|
||||
uint8_t FIFO_RX_LEVEL = 0x00;
|
||||
uint8_t RX_BUFFER = 0x00; // nr of bytes copied into _inbuffer
|
||||
uint8_t FIFO_TX_LEVEL = 0x00;
|
||||
bool _playCmd = false;
|
||||
bool _volCmd = false;
|
||||
bool _folderCmd = false;
|
||||
uint8_t _requestedFolder = 0x01; // default to folder 01
|
||||
uint8_t _currentFolder = 0x01; // default to folder 01
|
||||
bool _repeatCmd = false;
|
||||
bool _stopplayCmd = false;
|
||||
bool _resetCmd = false;
|
||||
bool _eqCmd = false;
|
||||
uint8_t _requestedEQValue = DF_NORMAL;
|
||||
uint8_t _currentEQvalue = DF_NORMAL; // start equalizer value
|
||||
bool _daconCmd = false;
|
||||
uint8_t _audioMixer = 0x01; // Default to output amplifier 1
|
||||
bool _setamCmd = false; // Set the Audio mixer channel
|
||||
uint8_t _outbuffer [11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel
|
||||
uint8_t _inbuffer[10]; // expected DFPlayer return 10 bytes
|
||||
|
||||
unsigned long _sc16is752_xtal_freq;
|
||||
unsigned long SC16IS752_XTAL_FREQ_LOW = 1843200; // To support cheap eBay/AliExpress SC16IS752 boards
|
||||
unsigned long SC16IS752_XTAL_FREQ_HIGH = 14745600; // Support for higher baud rates, standard for modular EX-IO system
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
if (xtal == 0){
|
||||
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_LOW;
|
||||
} else { // should be 1
|
||||
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_HIGH;
|
||||
}
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new I2CDFPlayer(firstVpin, nPins, i2cAddress, xtal);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
// check if SC16IS752 exist first, initialize and then resume DFPlayer init via SC16IS752
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)){
|
||||
DIAG(F("SC16IS752 I2C:%s UART detected"), _I2CAddress.toString());
|
||||
Init_SC16IS752(); // Initialize UART
|
||||
if (_deviceState == DEVSTATE_FAILED){
|
||||
DIAG(F("SC16IS752 I2C:%s UART initialization failed"), _I2CAddress.toString());
|
||||
}
|
||||
} else {
|
||||
DIAG(F("SC16IS752 I2C:%s UART not detected"), _I2CAddress.toString());
|
||||
}
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
// Now init DFPlayer
|
||||
// Send a query to the device to see if it responds
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
sendPacket(0x42,0,0);
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Read responses from device
|
||||
uint8_t status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
|
||||
if (status == I2C_STATUS_OK) {
|
||||
processIncoming(currentMicros);
|
||||
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||
// added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
|
||||
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
|
||||
if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
|
||||
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_awaitingResponse = false;
|
||||
_playing = false;
|
||||
_retryCounter = RETRYCOUNT;
|
||||
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: 0x%x"), _I2CAddress.toString(), _retryCounter, _UART_CH);
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds// reset timeout
|
||||
_awaitingResponse = false; // trigger sending a keep alive 0x42 in processOutgoing()
|
||||
_retryCounter --; // decrement retry counter
|
||||
resetRX_fifo(); // reset the RX fifo as it has corrupt data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, try next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Send any commands that need to go.
|
||||
processOutgoing(currentMicros);
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
|
||||
// Check for incoming data, and update busy flag and other state accordingly
|
||||
|
||||
void processIncoming(unsigned long currentMicros) {
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
RX_fifo_lvl();
|
||||
if (FIFO_RX_LEVEL >= 10) {
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s Retrieving data from RX Fifo on UART_CH: 0x%x FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), _UART_CH, FIFO_RX_LEVEL);
|
||||
#endif
|
||||
_outbuffer[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 10, _outbuffer, 1); // inbuffer[] has the data now
|
||||
//delayUntil(currentMicros + 10000); // Allow time to get the data
|
||||
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _inbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _inbuffer[0x%x]: 0x%x"), i, _inbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
FIFO_RX_LEVEL = 0; //set to 0, we'll read a fresh FIFO_RX_LEVEL next time
|
||||
return; // No data or not enough data in rx fifo, check again next time around
|
||||
}
|
||||
|
||||
|
||||
bool ok = false;
|
||||
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
|
||||
while (RX_BUFFER != 0) {
|
||||
int c = _inbuffer[_inputIndex]; // Start at 0, increment to FIFO_RX_LEVEL
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
case 2:
|
||||
if (c== 0x06) ok = true;
|
||||
break;
|
||||
case 3:
|
||||
_recvCMD = c; // CMD byte
|
||||
ok = true;
|
||||
break;
|
||||
case 6:
|
||||
switch (_recvCMD) {
|
||||
//DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
|
||||
case 0x42:
|
||||
// Response to status query
|
||||
_playing = (c != 0);
|
||||
// Mark the device online and cancel timeout
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s, UART_CH: 0x0%x, _deviceState: 0x0%x"),_I2CAddress.toString(), _UART_CH, _deviceState);
|
||||
#endif
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// Error codes; 1: Module Busy
|
||||
DIAG(F("I2CDFPlayer: Error %d returned from device"), c);
|
||||
_playing = false;
|
||||
break;
|
||||
}
|
||||
ok = true;
|
||||
break;
|
||||
case 4: case 5: case 7: case 8:
|
||||
ok = true; // Skip over these bytes in message.
|
||||
break;
|
||||
case 9:
|
||||
if (c==0xef) {
|
||||
// Message finished
|
||||
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ok){
|
||||
_inputIndex++; // character as expected, so increment index
|
||||
RX_BUFFER --; // Decrease FIFO_RX_LEVEL with each character read from _inbuffer[_inputIndex]
|
||||
} else {
|
||||
_inputIndex = 0; // otherwise reset.
|
||||
RX_BUFFER = 0;
|
||||
}
|
||||
}
|
||||
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
|
||||
}
|
||||
|
||||
|
||||
// Send any commands that need to be sent
|
||||
void processOutgoing(unsigned long currentMicros) {
|
||||
// When two commands are sent in quick succession, the device will often fail to
|
||||
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||
// between successive commands to get reliable operation.
|
||||
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if ( _resetCmd == true){
|
||||
sendPacket(0x0C,0,0);
|
||||
_resetCmd = false;
|
||||
} else if(_volCmd == true) { // do the volme before palying a track
|
||||
if(_requestedVolumeLevel >= 0 && _requestedVolumeLevel <= 30){
|
||||
_currentVolume = _requestedVolumeLevel; // If _requestedVolumeLevel is out of range, sent _currentV1olume
|
||||
}
|
||||
sendPacket(0x06, 0x00, _currentVolume);
|
||||
_volCmd = false;
|
||||
} else if (_playCmd == true) {
|
||||
// Change song
|
||||
if (_requestedSong != -1) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
|
||||
#endif
|
||||
sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
|
||||
_requestedSong = -1;
|
||||
_playCmd = false;
|
||||
}
|
||||
} //else if (_requestedSong == 0) {
|
||||
else if (_stopplayCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Stop playing: _stopplayCmd: 0x%x"), _stopplayCmd);
|
||||
#endif
|
||||
sendPacket(0x16, 0x00, 0x00); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_repeat = false; // reset repeat
|
||||
_stopplayCmd = false;
|
||||
} else if (_folderCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
|
||||
#endif
|
||||
if (_currentFolder != _requestedFolder){
|
||||
_currentFolder = _requestedFolder;
|
||||
}
|
||||
_folderCmd = false;
|
||||
} else if (_repeatCmd == true) {
|
||||
if(_repeat == false) { // No repeat play currently
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Repeat: _repeatCmd: 0x%x, _requestedSong: %d, _repeat: 0x0%x"), _repeatCmd, _requestedSong, _repeat);
|
||||
#endif
|
||||
sendPacket(0x08, 0x00, _requestedSong); // repeat playing audio file in root folder
|
||||
_requestedSong = -1;
|
||||
_repeat = true;
|
||||
}
|
||||
_repeatCmd= false;
|
||||
} else if (_daconCmd == true) { // Always turn DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: DACON: _daconCmd: 0x%x"), _daconCmd);
|
||||
#endif
|
||||
sendPacket(0x1A,0,0x00);
|
||||
_daconCmd = false;
|
||||
} else if (_eqCmd == true){ // Set Equalizer, values 0x00 - 0x05
|
||||
if (_currentEQvalue != _requestedEQValue){
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: EQ: _eqCmd: 0x%x, _currentEQvalue: 0x0%x, _requestedEQValue: 0x0%x"), _eqCmd, _currentEQvalue, _requestedEQValue);
|
||||
#endif
|
||||
_currentEQvalue = _requestedEQValue;
|
||||
sendPacket(0x07,0x00,_currentEQvalue);
|
||||
}
|
||||
_eqCmd = false;
|
||||
} else if (_setamCmd == true){ // Set Audio mixer channel
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
}*/
|
||||
_setamCmd = false;
|
||||
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||
// Poll device every second that other commands aren't being sent,
|
||||
// to check if it's still connected and responding.
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive") );
|
||||
#endif
|
||||
sendPacket(0x42,0,0);
|
||||
if (!_awaitingResponse) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive, _awaitingResponse: 0x0%x"), _awaitingResponse );
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write to a vPin will do nothing
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Writing to any vPin not supported"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
// WriteAnalogue to be done on first vpin
|
||||
//
|
||||
//void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t cmd=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: VPIN:%u FileNo:%d Volume:%d Command:0x%x"), vpin, value, volume, cmd);
|
||||
#endif
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Enhanced DFPlayer commands, do nothing if not vPin 0
|
||||
// Read command and value
|
||||
switch (cmd){
|
||||
//case NONE:
|
||||
// DFPlayerCmd = cmd;
|
||||
// break;
|
||||
case DF_PLAY:
|
||||
_playCmd = true;
|
||||
_volCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
break;
|
||||
case DF_VOL:
|
||||
_volCmd = true;
|
||||
_requestedVolumeLevel = volume;
|
||||
break;
|
||||
case DF_FOLDER:
|
||||
_folderCmd = true;
|
||||
if (volume <= 0 || volume > 99){ // Range checking, valid values 1-99, else default to 1
|
||||
_requestedFolder = 0x01; // if outside range, default to folder 01
|
||||
} else {
|
||||
_requestedFolder = volume;
|
||||
}
|
||||
break;
|
||||
case DF_REPEATPLAY: // Need to check if _repeat == true, if so do nothing
|
||||
if (_repeat == false) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
|
||||
#endif
|
||||
_repeatCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
}
|
||||
break;
|
||||
case DF_STOPPLAY:
|
||||
_stopplayCmd = true;
|
||||
break;
|
||||
case DF_EQ:
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
|
||||
#endif
|
||||
_eqCmd = true;
|
||||
if (volume <= 0 || volume > 5) { // If out of range, default to NORMAL
|
||||
_requestedEQValue = DF_NORMAL;
|
||||
} else { // Valid EQ parameter range
|
||||
_requestedEQValue = volume;
|
||||
}
|
||||
break;
|
||||
case DF_RESET:
|
||||
_resetCmd = true;
|
||||
break;
|
||||
case DF_DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
_daconCmd = true;
|
||||
break;
|
||||
case DF_SETAM: // Set the audio mixer channel to 1 or 2
|
||||
_setamCmd = true;
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog SETAM: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
if (volume <= 0 || volume > 2) { // If out of range, default to 1
|
||||
_audioMixer = 1;
|
||||
} else { // Valid SETAM parameter in range
|
||||
_audioMixer = volume; // _audioMixer valid values 1 or 2
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A read on any pin indicates if the player is still playing.
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Do nothing if not vPin 0
|
||||
return _playing;
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("I2CDFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// DFPlayer command frame
|
||||
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||
// 0 -> 7E is start code
|
||||
// 1 -> FF is version
|
||||
// 2 -> 06 is length
|
||||
// 3 -> 0F is command
|
||||
// 4 -> 00 is no receive
|
||||
// 5~6 -> 01 01 is argument
|
||||
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||
// 9 -> EF is end code
|
||||
|
||||
void sendPacket(uint8_t command, uint8_t arg1 = 0, uint8_t arg2 = 0) {
|
||||
FIFO_TX_LEVEL = 0; // Reset FIFO_TX_LEVEL
|
||||
uint8_t out[] = {
|
||||
0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
command,
|
||||
00,
|
||||
//static_cast<uint8_t>(arg >> 8),
|
||||
//static_cast<uint8_t>(arg & 0x00ff),
|
||||
arg1,
|
||||
arg2,
|
||||
00,
|
||||
00,
|
||||
0xEF };
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Prepend the DFPlayer command with REG address and UART Channel in _outbuffer
|
||||
_outbuffer[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||
_outbuffer[i] = out[i-1];
|
||||
}
|
||||
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s Sent packet function"), _I2CAddress.toString());
|
||||
for (int i = 0; i < sizeof _outbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _outbuffer[0x%x]: 0x%x"), i, _outbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
TX_fifo_lvl();
|
||||
if(FIFO_TX_LEVEL > 0){ //FIFO is empty
|
||||
I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer), &_rb);
|
||||
//I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer));
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("I2CDFPlayer at: %s, TX FIFO not empty on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED; // This should not happen
|
||||
}
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
sum += packet[i];
|
||||
}
|
||||
return -sum;
|
||||
}
|
||||
|
||||
void setChecksum(uint8_t* out)
|
||||
{
|
||||
uint16_t sum = calcChecksum(out);
|
||||
out[7] = (sum >> 8);
|
||||
out[8] = (sum & 0xff);
|
||||
}
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
void Init_SC16IS752(){ // Return value is in _deviceState
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: Initialize I2C: %s , UART Ch: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
//uint16_t _divisor = (SC16IS752_XTAL_FREQ / PRESCALER) / (BAUD_RATE * 16);
|
||||
uint16_t _divisor = (_sc16is752_xtal_freq/PRESCALER)/(BAUD_RATE * 16); // Calculate _divisor for baudrate
|
||||
TEMP_REG_VAL = 0x08; // UART Software reset
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set pins to GPIO mode
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0xFF; //Set all pins as output
|
||||
UART_WriteRegister(REG_IODIR, TEMP_REG_VAL);
|
||||
UART_ReadRegister(REG_IOSTATE); // Read current state as not to overwrite the other GPIO pins
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_UART_CH == 0){ // Set Audio mixer channel
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
*/
|
||||
TEMP_REG_VAL = 0x07; // Reset FIFO, clear RX & TX FIFO
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set MCR to all 0, includes Clock divisor
|
||||
UART_WriteRegister(REG_MCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE;
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch enabled
|
||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||
UART_ReadRegister(REG_LCR);
|
||||
TEMP_REG_VAL = _inbuffer[0] & 0x7F; // Disable Divisor latch enabled bit
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch disabled
|
||||
|
||||
uint8_t status = _rb.status;
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("SC16IS752: I2C: %s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("SC16IS752: I2C: %s, _deviceState: %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
#endif
|
||||
_deviceState = DEVSTATE_NORMAL; // If I2C state is OK, then proceed to initialize DFPlayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read the Receive FIFO Level register (RXLVL), return a single unsigned integer
|
||||
// of nr of characters in the RX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40) Only display if RX FIFO has data
|
||||
// The RX fifo level is used to check if there are enough bytes to process a frame
|
||||
void RX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_RXLV);
|
||||
FIFO_RX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
if (FIFO_RX_LEVEL > 0){
|
||||
//if (FIFO_RX_LEVEL > 0 && FIFO_RX_LEVEL < 10){
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[0]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// When a frame is transmitted from the DFPlayer to the serial port, and at the same time the CS is sending a 42 query
|
||||
// the following two frames from the DFPlayer are corrupt. This result in the receive buffer being out of sync and the
|
||||
// CS will complain and generate a timeout.
|
||||
// The RX fifo has corrupt data and need to be flushed, this function does that
|
||||
//
|
||||
void resetRX_fifo(){
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX fifo reset"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
TEMP_REG_VAL = 0x03; // Reset RX fifo
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
}
|
||||
|
||||
// Set or reset GPIO pin 0 and 1 depending on the UART ch
|
||||
// This function may be modified in a future release to enable all 8 pins to be set or reset with EX-Rail
|
||||
// for various auxilary functions
|
||||
void setGPIO(){
|
||||
UART_ReadRegister(REG_IOSTATE); // Get the current GPIO pins state from the IOSTATE register
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
_setamCmd = false;
|
||||
}
|
||||
|
||||
|
||||
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer
|
||||
// of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40)
|
||||
//
|
||||
void TX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_TXLV);
|
||||
FIFO_TX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_TX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, FIFO_TX_LEVEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//void UART_WriteRegister(I2CAddress _I2CAddress, uint8_t _UART_CH, uint8_t UART_REG, uint8_t Val, I2CRB &_rb){
|
||||
void UART_WriteRegister(uint8_t UART_REG, uint8_t Val){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1;
|
||||
_outbuffer[1] = Val;
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Write register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _outbuffer[1]);
|
||||
#endif
|
||||
I2CManager.write(_I2CAddress, _outbuffer, 2);
|
||||
}
|
||||
|
||||
|
||||
void UART_ReadRegister(uint8_t UART_REG){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1; // _outbuffer[0] has now UART_REG and UART_CH
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 1, _outbuffer, 1);
|
||||
// _inbuffer has the REG data
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Read register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _inbuffer[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t{
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
|
||||
// DFPlayer commands and values
|
||||
// Declared in this scope
|
||||
enum : uint8_t{
|
||||
DF_PLAY = 0x0F,
|
||||
DF_VOL = 0x06,
|
||||
DF_FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||
DF_REPEATPLAY = 0x08,
|
||||
DF_STOPPLAY = 0x16,
|
||||
DF_EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||
DF_RESET = 0x0C,
|
||||
DF_DACON = 0x1A,
|
||||
DF_SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||
DF_NORMAL = 0x00, // Equalizer parameters
|
||||
DF_POP = 0x01,
|
||||
DF_ROCK = 0x02,
|
||||
DF_JAZZ = 0x03,
|
||||
DF_CLASSIC = 0x04,
|
||||
DF_BASS = 0x05,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CDFPlayer_h
|
@@ -42,9 +42,9 @@
|
||||
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
|
||||
*
|
||||
* #include "IO_RotaryEncoder.h"
|
||||
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 1, 0x67) // Define single Vpin, no feedback or position sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 2, 0x67) // Define two Vpins, feedback only sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 3, 0x67) // Define three Vpins, can send feedback and position update to rotary encoder software
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
57
KeywordHasher.h
Normal file
57
KeywordHasher.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* © 2024 Vincent Hamp and Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/* Reader be aware:
|
||||
This function implements the _hk data type so that a string keyword
|
||||
is hashed to the same value as the DCCEXParser uses to hash incoming
|
||||
keywords.
|
||||
Thus "MAIN"_hk generates exactly the same run time vakue
|
||||
as const int16_t HASH_KEYWORD_MAIN=11339
|
||||
*/
|
||||
#ifndef KeywordHAsher_h
|
||||
#define KeywordHasher_h
|
||||
|
||||
#include <Arduino.h>
|
||||
constexpr uint16_t CompiletimeKeywordHasher(const char * sv, uint16_t running=0) {
|
||||
return (*sv==0) ? running : CompiletimeKeywordHasher(sv+1,
|
||||
(*sv >= '0' && *sv <= '9')
|
||||
? (10*running+*sv-'0') // Numeric hash
|
||||
: ((running << 5) + running) ^ *sv
|
||||
); //
|
||||
}
|
||||
|
||||
constexpr int16_t operator""_hk(const char * keyword, size_t len)
|
||||
{
|
||||
return (int16_t) CompiletimeKeywordHasher(keyword,len*0);
|
||||
}
|
||||
|
||||
/* Some historical values for testing:
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
*/
|
||||
|
||||
static_assert("MAIN"_hk == 11339,"Keyword hasher error");
|
||||
static_assert("SLOW"_hk == -17209,"Keyword hasher error");
|
||||
static_assert("SPEED28"_hk == -17064,"Keyword hasher error");
|
||||
static_assert("SPEED128"_hk == 25816,"Keyword hasher error");
|
||||
#endif
|
120
MotorDriver.cpp
120
MotorDriver.cpp
@@ -204,7 +204,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
|
||||
@@ -325,49 +325,21 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
220, 196, 175, 165 };
|
||||
#endif
|
||||
#endif
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is differnet)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
|
||||
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
|
||||
{ // new block because of variable f
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
{
|
||||
int f = 131;
|
||||
int f = frequency;
|
||||
#ifdef VARIABLE_TONES
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
@@ -375,19 +347,15 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
||||
#else // all AVR here
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
||||
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
|
||||
//DIAG(F("DCSignal %d"), speedcode);
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
noInterrupts();
|
||||
@@ -434,60 +402,28 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
void MotorDriver::throttleInrush(bool on) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
|
||||
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT | TRACK_MODE_BOOST)))
|
||||
return;
|
||||
byte duty = on ? 208 : 0;
|
||||
if (invertBrake)
|
||||
duty = 255-duty;
|
||||
byte duty = on ? 207 : 0; // duty of 81% at 62500Hz this gives pauses of 3usec
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXInrushControlOn(brakePin, duty, invertBrake);
|
||||
} else {
|
||||
ledcDetachPin(brakePin);
|
||||
ledcDetachPin(brakePin); // not DCCTimer::DCCEXledcDetachPin() as we have not
|
||||
// registered the pin in the pin to channel array
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty,invertBrake);
|
||||
} else {
|
||||
pinMode(brakePin, OUTPUT);
|
||||
}
|
||||
#else
|
||||
#else // all AVR here
|
||||
if (invertBrake)
|
||||
duty = 255-duty;
|
||||
if(on){
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is different)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
|
||||
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||
}
|
||||
analogWrite(brakePin,duty);
|
||||
#endif
|
||||
|
@@ -34,9 +34,15 @@ 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_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};
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16,
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
TRACK_MODE_BOOST = 32,
|
||||
#else
|
||||
TRACK_MODE_BOOST = 0,
|
||||
#endif
|
||||
TRACK_MODE_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST,
|
||||
TRACK_MODE_INV = 64,
|
||||
TRACK_MODE_DCX = TRACK_MODE_DC|TRACK_MODE_INV, TRACK_MODE_AUTOINV = 128};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
@@ -187,13 +193,14 @@ class MotorDriver {
|
||||
}
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
inline int8_t getBrakePinSigned() { return invertBrake ? -brakePin : brakePin; };
|
||||
void setDCSignal(byte speedByte, uint8_t frequency=0);
|
||||
void throttleInrush(bool on);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
pinMode(brakePin, OUTPUT);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
ledcDetachPin(brakePin);
|
||||
DCCTimer::DCCEXledcDetachPin(brakePin);
|
||||
#else
|
||||
setDCSignal(128);
|
||||
#endif
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2024 Harald Barth
|
||||
* (c) 2020 Chris Harlow. All rights reserved.
|
||||
* (c) 2021 Fred Decker. All rights reserved.
|
||||
* (c) 2020 Harald Barth. All rights reserved.
|
||||
@@ -57,6 +57,10 @@
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake)
|
||||
|
||||
// You can have a CS wihout any possibility to do any track signal.
|
||||
// That's strange but possible.
|
||||
#define NO_SHIELD F("No shield at all")
|
||||
|
||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
|
38
Release_Notes/EXRAIL additions.md
Normal file
38
Release_Notes/EXRAIL additions.md
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
BLINK(vpin, onMs,offMs)
|
||||
|
||||
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
|
||||
|
||||
BLINK returns immediately, the blinking is autonomous.
|
||||
|
||||
This means a signal that always blinks amber could be done like this:
|
||||
```
|
||||
SIGNAL(30,31,32)
|
||||
ONAMBER(30) BLINK(31,500,500) DONE
|
||||
```
|
||||
The RED or GREEN calls will turn off the amber blink automatically.
|
||||
|
||||
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
|
||||
|
||||
#define FLASHAMBER(signal) \
|
||||
AMBER(signal) \
|
||||
BLINK(signal+1,500,500)
|
||||
|
||||
(Caution: this issumes that the amber pin is redpin+1)
|
||||
|
||||
==
|
||||
|
||||
FTOGGLE(function)
|
||||
Toggles the current loco function (see FON and FOFF)
|
||||
|
||||
XFTOGGLE(loco,function)
|
||||
Toggles the function on given loco. (See XFON, XFOFF)
|
||||
|
||||
TOGGLE_TURNOUT(id)
|
||||
Toggles the turnout (see CLOSE, THROW)
|
||||
|
||||
STEALTH_GLOBAL(code)
|
||||
ADVANCED C++ users only.
|
||||
Inserts code such as static variables and functions that
|
||||
may be utilised by multiple STEALTH operations.
|
||||
|
98
Release_Notes/Exrail mods.txt
Normal file
98
Release_Notes/Exrail mods.txt
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
BLINK(vpin, onMs,offMs)
|
||||
|
||||
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
|
||||
|
||||
BLINK returns immediately, the blinking is autonomous.
|
||||
|
||||
This means a signal that always blinks amber could be done like this:
|
||||
|
||||
SIGNAL(30,31,32)
|
||||
ONAMBER(30) BLINK(31,500,500) DONE
|
||||
|
||||
The RED or GREEN calls will turn off the amber blink automatically.
|
||||
|
||||
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
|
||||
|
||||
#define FLASHAMBER(signal) \
|
||||
AMBER(signal) \
|
||||
BLINK(signal+1,500,500)
|
||||
|
||||
(Caution: this assumes that the amber pin is redpin+1)
|
||||
|
||||
==
|
||||
|
||||
FTOGGLE(function)
|
||||
Toggles the current loco function (see FON and FOFF)
|
||||
|
||||
XFTOGGLE(loco,function)
|
||||
Toggles the function on given loco. (See XFON, XFOFF)
|
||||
|
||||
TOGGLE_TURNOUT(id)
|
||||
Toggles the turnout (see CLOSE, THROW)
|
||||
|
||||
STEALTH_GLOBAL(code)
|
||||
ADVANCED C++ users only.
|
||||
Inserts code such as static variables and functions that
|
||||
may be utilised by multiple STEALTH operations.
|
||||
|
||||
|
||||
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories.
|
||||
This command sends an extended accessory packet to the track, Normally used to set
|
||||
a signal aspect. Aspect numbers are undefined as sdtandards except for 0 which is
|
||||
always considered a stop.
|
||||
|
||||
// - Exrail ASPECT(address,aspect) for above.
|
||||
The ASPECT command sents an aspect to a DCC accessory using the same logic as
|
||||
<A aspect address>.
|
||||
|
||||
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
|
||||
This defines a signal (with id same as dcc address) that can be operated
|
||||
by the RED/AMBER/GREEN commands. In each case the command uses the signal
|
||||
address to refer to the signal and the aspect chosen depends on the use of the RED
|
||||
AMBER or GREEN command sent. Other aspects may be sent but will require the
|
||||
direct use of the ASPECT command.
|
||||
The IFRED/IFAMBER/IFGREEN and ONRED/ONAMBER/ONGREEN commands contunue to operate
|
||||
as for any other signal type. It is important to be aware that use of the ASPECT
|
||||
or <A> commands will correctly set the IF flags and call the ON handlers if ASPECT
|
||||
is used to set one of the three aspects defined in the DCCX_SIGNAL command.
|
||||
Direct use of other aspects does not affect the signal flags.
|
||||
ASPECT and <A> can be used withput defining any signal if tyhe flag management or
|
||||
ON event handlers are not required.
|
||||
|
||||
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
This macro offsers a more convenient way of performing the HAL call in halSetup.h
|
||||
In halSetup.h --- IODevice::configureServo(101,300,400,PCA9685::slow);
|
||||
In myAutomation.h --- CONFIGURE_SERVO(101,300,400,slow)
|
||||
|
||||
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
|
||||
This cutout will only work on a Mega2560 with a single EX8874 motor shield
|
||||
configured in the normal way with the main track brake pin on pin 9.
|
||||
<C RAILCOM ON> Turns on the cutout mechanism.
|
||||
<C RAILCOM OFF> Tirns off the cutout. (This is the default)
|
||||
<C RAILCOM DEBUG> ONLY to be used by developers used for waveform diagnostics.
|
||||
(In DEBUG mode the main track idle packets are replaced with reset packets, This
|
||||
makes it far easier to see the preambles and cutouts on a logic analyser or scope.)
|
||||
|
||||
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
||||
This Macro causes the creation of JMRI <S> type sensors in a way that is
|
||||
simpler than repeating lines of <S> commands.
|
||||
JMRI_SENSOR(100) is equenvelant to <S 100 100 1>
|
||||
JMRI_SENSOR(100,16) will create <S> type sensors for vpins 100-115.
|
||||
|
||||
// 5.2.26 - Silently ignore overridden HAL defaults
|
||||
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
|
||||
The HAL_IGNORE_DEFAULTS command, anywhere in myAutomation.h will
|
||||
prevent the startup code from trying the default I2C sensors/servos.
|
||||
// 5.2.24 - Exrail macro asserts to catch
|
||||
// : duplicate/missing automation/route/sequence/call ids
|
||||
// : latches and reserves out of range
|
||||
// : speeds out of range
|
||||
Causes compiler time messages for EXRAIL issues that would normally
|
||||
only be discovered by things going wrong at run time.
|
||||
// 5.2.13 - EXRAIL STEALTH
|
||||
Permits a certain level of C++ code to be embedded as a single step in
|
||||
an exrail sequence. Serious engineers only.
|
||||
|
||||
// 5.2.9 - EXRAIL STASH feature
|
||||
// - Added ROUTE_DISABLED macro in EXRAIL
|
@@ -230,6 +230,13 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){
|
||||
return tt;
|
||||
}
|
||||
|
||||
// Creet multiple eponymous sensors based on vpin alone.
|
||||
void Sensor::createMultiple(VPIN firstPin, byte count) {
|
||||
for (byte i=0;i<count;i++) {
|
||||
create(firstPin+i,firstPin+i,1);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Object method to directly change the input state, for sensors such as LCN which are updated
|
||||
// by means other than by polling an input.
|
||||
|
@@ -76,6 +76,7 @@ public:
|
||||
static void store();
|
||||
#endif
|
||||
static Sensor *create(int id, VPIN vpin, int pullUp);
|
||||
static void createMultiple(VPIN firstPin, byte count=1);
|
||||
static Sensor* get(int id);
|
||||
static bool remove(int id);
|
||||
static void checkAll();
|
||||
|
114
TrackManager.cpp
114
TrackManager.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022,2023 Harald Barth
|
||||
* © 2022-2024 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -19,6 +19,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "defines.h"
|
||||
#include "TrackManager.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCWaveform.h"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include "DIAG.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "KeywordHasher.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
@@ -35,26 +37,11 @@
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (track[t]->getMode()==findmode) \
|
||||
track[t]->function;
|
||||
#ifndef DISABLE_PROG
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_NONE = -26550;
|
||||
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];
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
||||
|
||||
byte TrackManager::lastTrack=0;
|
||||
int8_t TrackManager::lastTrack=-1;
|
||||
bool TrackManager::progTrackSyncMain=false;
|
||||
bool TrackManager::progTrackBoosted=false;
|
||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
@@ -171,12 +158,6 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
@@ -202,7 +183,7 @@ 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);
|
||||
track[t]->setDCSignal(speedbyte, DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +219,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
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_in(BOOSTER_INPUT, 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);
|
||||
@@ -270,18 +251,47 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
} else {
|
||||
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
||||
}
|
||||
track[trackToSet]->setMode(mode);
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
|
||||
// When a track is switched, we must clear any side effects of its previous
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||
if (!(mode & TRACK_MODE_DC)) {
|
||||
if (mode & TRACK_MODE_DC) {
|
||||
if (trackDCAddr[trackToSet] != dcAddr) {
|
||||
// new or changed DC Addr, run the new setup
|
||||
if (trackDCAddr[trackToSet] != 0) {
|
||||
// if we change dcAddr and not only
|
||||
// change from another mode,
|
||||
// first detach old DC signal
|
||||
track[trackToSet]->detachDCSignal();
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
int trackfound = -1;
|
||||
FOR_EACH_TRACK(t) {
|
||||
//DIAG(F("Checking track %c mode %x dcAddr %d"), 'A'+t, track[t]->getMode(), trackDCAddr[t]);
|
||||
if (t != trackToSet // not our track
|
||||
&& (track[t]->getMode() & TRACK_MODE_DC) // right mode
|
||||
&& trackDCAddr[t] == dcAddr) { // right addr
|
||||
//DIAG(F("Found track %c"), 'A'+t);
|
||||
trackfound = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trackfound > -1) {
|
||||
DCCTimer::DCCEXanalogCopyChannel(track[trackfound]->getBrakePinSigned(),
|
||||
track[trackToSet]->getBrakePinSigned());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// set future DC Addr;
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
} else {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
trackDCAddr[trackToSet]=0; // clear that an addr is set for DC as this is not a DC track
|
||||
}
|
||||
track[trackToSet]->setMode(mode);
|
||||
|
||||
// BOOST:
|
||||
// Leave it as is
|
||||
@@ -348,8 +358,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
}
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
track[t]->setDCSignal(speedByte);
|
||||
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
|
||||
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
@@ -362,38 +372,39 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
p[0]-="A"_hk; // convert A... to 0....
|
||||
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
||||
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
||||
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
|
||||
if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE>
|
||||
return setTrackMode(p[0],TRACK_MODE_NONE);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST>
|
||||
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
#endif
|
||||
if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO>
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO>
|
||||
if (params==2 && p[1]=="INV"_hk) // <= 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>
|
||||
if (params==3 && p[1]=="DC"_hk && 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>
|
||||
if (params==3 && p[1]=="DCX"_hk && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
|
||||
|
||||
return false;
|
||||
@@ -420,11 +431,11 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
modename=F("EXT");
|
||||
else if(tm & TRACK_MODE_BOOST) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
modename=F("B A");
|
||||
modename=F("BOOST A");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
modename=F("B I");
|
||||
modename=F("BOOST I");
|
||||
else
|
||||
modename=F("B");
|
||||
modename=F("BOOST");
|
||||
}
|
||||
else if (tm & TRACK_MODE_DC) {
|
||||
if (tm & TRACK_MODE_INV)
|
||||
@@ -516,7 +527,11 @@ void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermod
|
||||
|
||||
// Set track power for this track, inependent of mode
|
||||
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||
MotorDriver *driver=track[t];
|
||||
MotorDriver *driver=track[t];
|
||||
if (driver == NULL) { // track is not defined at all
|
||||
DIAG(F("Error: Track %c does not exist"), t+'A');
|
||||
return;
|
||||
}
|
||||
TRACK_MODE trackmode = driver->getMode();
|
||||
POWERMODE oldpower = driver->getPower();
|
||||
if (trackmode & TRACK_MODE_NONE) {
|
||||
@@ -574,14 +589,17 @@ bool TrackManager::getPower(byte t, char s[]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||
// 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>
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
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);
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
#else
|
||||
(void)stream;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrackManager::reportCurrent(Print* stream) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2022-2024 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
@@ -46,7 +46,7 @@ const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
MotorDriver * track0,
|
||||
MotorDriver * track0=NULL,
|
||||
MotorDriver * track1=NULL,
|
||||
MotorDriver * track2=NULL,
|
||||
MotorDriver * track3=NULL,
|
||||
@@ -57,7 +57,6 @@ class TrackManager {
|
||||
);
|
||||
|
||||
static void setDCCSignal( bool on);
|
||||
static void setCutout( bool on);
|
||||
static void setPROGSignal( bool on);
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
@@ -109,7 +108,7 @@ class TrackManager {
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
static int8_t lastTrack;
|
||||
static byte nextCycleTrack;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
|
@@ -247,22 +247,23 @@ DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
|
||||
StringFormatter::send(stream, F("<i %d DCCTURNTABLE>\n"), _turntableData.id);
|
||||
}
|
||||
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
(void) activity;
|
||||
#ifndef IO_NO_HAL
|
||||
int16_t value = getPositionValue(position);
|
||||
if (position == 0 || !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
int16_t addr=value>>3;
|
||||
int16_t subaddr=(value>>1) & 0x03;
|
||||
bool active=value & 0x01;
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
DCC::setAccessory(addr, subaddr, active);
|
||||
int16_t value = getPositionValue(position);
|
||||
if (position == 0 || !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
int16_t addr=value>>3;
|
||||
int16_t subaddr=(value>>1) & 0x03;
|
||||
bool active=value & 0x01;
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
DCC::setAccessory(addr, subaddr, active);
|
||||
#else
|
||||
(void)position;
|
||||
(void)position;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -187,6 +187,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
}
|
||||
break;
|
||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||
sendIntro(stream);
|
||||
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
||||
break;
|
||||
case 'M': // multithrottle
|
||||
@@ -194,7 +195,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
break;
|
||||
case 'H': // send initial connection info after receiving "HU" message
|
||||
if (cmd[1] == 'U') {
|
||||
sendIntro(stream);
|
||||
sendIntro(stream);
|
||||
}
|
||||
break;
|
||||
case 'Q': //
|
||||
@@ -498,12 +499,14 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
}
|
||||
|
||||
void WiThrottle::sendIntro(Print* stream) {
|
||||
if (introSent) // sendIntro only once
|
||||
return;
|
||||
introSent=true;
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
|
||||
}
|
||||
|
||||
@@ -568,7 +571,7 @@ void WiThrottle::sendRoutes(Print* stream) {
|
||||
|
||||
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
int16_t locoid=myLocos[loco].cab;
|
||||
int fkeys=29;
|
||||
int fkeys=32; // upper limit (send functions 0 to 31)
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
@@ -618,7 +621,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
int8_t fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||
}
|
||||
}
|
||||
|
@@ -164,6 +164,8 @@ bool WifiESP::setup(const char *SSid,
|
||||
if (haveSSID && havePassword && !forceAP) {
|
||||
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // Scan all channels so we find strongest
|
||||
// (default in Wifi library is first match)
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
#else
|
||||
@@ -204,7 +206,7 @@ bool WifiESP::setup(const char *SSid,
|
||||
if (!haveSSID || forceAP) {
|
||||
// prepare all strings
|
||||
String strSSID(forceAP ? SSid : "DCCEX_");
|
||||
String strPass(forceAP ? password : "PASS_");
|
||||
String strPass( (forceAP && havePassword) ? password : "PASS_");
|
||||
if (!forceAP) {
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
@@ -228,7 +230,8 @@ bool WifiESP::setup(const char *SSid,
|
||||
// DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||
DIAG(F("Wifi in AP mode"));
|
||||
LCD(5, F("Wifi: %s"), strSSID.c_str());
|
||||
LCD(6, F("PASS: %s"),havePassword ? password : strPass.c_str());
|
||||
if (!havePassword)
|
||||
LCD(6, F("PASS: %s"),strPass.c_str());
|
||||
// DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||
LCD(7, F("IP: %s"),WiFi.softAPIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
|
@@ -211,6 +211,19 @@ The configuration file for DCC-EX Command Station
|
||||
// #define DISABLE_VDPY
|
||||
// #define ENABLE_VDPY
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// DISABLE / ENABLE DIAG
|
||||
//
|
||||
// To diagose different errors, you can turn on differnet messages. This costs
|
||||
// program memory which we do not have enough on the Uno and Nano, so it is
|
||||
// by default DISABLED on those. If you think you can fit it (for example
|
||||
// having disabled some of the features above) you can enable it with
|
||||
// ENABLE_DIAG. You can even disable it on all other CPUs with
|
||||
// DISABLE_DIAG
|
||||
//
|
||||
// #define DISABLE_DIAG
|
||||
// #define ENABLE_DIAG
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
|
||||
// is 127 and the first long address is 128. There are manufacturers which have
|
||||
|
@@ -220,9 +220,15 @@
|
||||
//
|
||||
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
||||
#define IO_NO_HAL // HAL too big whatever you disable otherwise
|
||||
|
||||
#ifndef ENABLE_VDPY
|
||||
#define DISABLE_VDPY
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_DIAG
|
||||
#define DISABLE_DIAG
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
|
@@ -25,6 +25,7 @@
|
||||
//#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).
|
||||
//#include "IO_I2CDFPlayer.h" // DFPlayer over I2C
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
@@ -234,6 +235,31 @@ void halSetup() {
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module on a SC16IS750/SC16IS752 I2C UART
|
||||
//=======================================================================
|
||||
// DFPlayer via NXP SC16IS752 I2C Dual UART.
|
||||
// I2C address range 0x48 - 0x57
|
||||
//
|
||||
// Generic format:
|
||||
// I2CDFPlayer::create(1st vPin, vPins, I2C address, xtal);
|
||||
// Parameters:
|
||||
// 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
// vPins : Total number of virtual pins allocated (1 vPin is supported currently)
|
||||
// 1st vPin for UART 0
|
||||
// I2C Address : I2C address of the serial controller, in 0x format
|
||||
// xtal : 0 for 1.8432Mhz, 1 for 14.7456Mhz
|
||||
//
|
||||
// The vPin is also a pin that can be read with the WAITFOR(vPin) command indicating if the DFPlayer has finished playing a track
|
||||
//
|
||||
|
||||
// I2CDFPlayer::create(10000, 1, 0x48, 1);
|
||||
//
|
||||
// Configuration example on a multiplexer
|
||||
// I2CDFPlayer::create(10000, 1, {I2CMux_0, SubBus_0, 0x48}, 1);
|
||||
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 16-pad capacitative touch key pad based on TP229 IC.
|
||||
//=======================================================================
|
||||
@@ -285,12 +311,13 @@ void halSetup() {
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// firstVpin = First available Vpin to allocate
|
||||
// numPins= Number of Vpins to allocate, can be either 1 or 2
|
||||
// i2cAddress = Available I2C address (default 0x70)
|
||||
// numPins= Number of Vpins to allocate, can be either 1 to 3
|
||||
// i2cAddress = Available I2C address (default 0x67)
|
||||
|
||||
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
||||
//RotaryEncoder::create(700, 1, 0x70);
|
||||
//RotaryEncoder::create(701, 2, 0x71);
|
||||
//RotaryEncoder::create(700, 1, 0x67);
|
||||
//RotaryEncoder::create(700, 2, 0x67);
|
||||
//RotaryEncoder::create(700, 3, 0x67);
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-FastClock instance.
|
||||
|
@@ -12,7 +12,6 @@
|
||||
default_envs =
|
||||
mega2560
|
||||
uno
|
||||
mega328
|
||||
unowifiR2
|
||||
nano
|
||||
samd21-dev-usb
|
||||
@@ -149,10 +148,7 @@ build_flags =
|
||||
platform = atmelavr
|
||||
board = uno
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
@@ -165,6 +161,7 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:ESP32]
|
||||
platform = espressif32
|
||||
|
46
version.h
46
version.h
@@ -3,7 +3,51 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.2.22"
|
||||
#define VERSION "5.2.48"
|
||||
// 5.2.48 - Bugfix: HALDisplay was generating I2C traffic prior to I2C being initialised
|
||||
// 5.2.47 - EXRAIL additions:
|
||||
// STEALTH_GLOBAL
|
||||
// BLINK
|
||||
// TOGGLE_TURNOUT
|
||||
// FTOGGLE, XFTOGGLE
|
||||
// Reduced code-developmenmt DIAG noise
|
||||
// 5.2.46 - Support for extended consist CV20 in <R> and <W id>
|
||||
// - New cmd <W CONSIST id [REVERSE]> to handle long/short consist ids
|
||||
// 5.2.45 - ESP32 Trackmanager reset cab number to 0 when track is not DC
|
||||
// ESP32 fix PWM LEDC inverted pin mode
|
||||
// ESP32 rewrite PWM LEDC to use pin mux
|
||||
// 5.2.42 - ESP32 Bugfix: Uninitialized stack variable
|
||||
// 5.2.41 - Update rotary encoder default address to 0x67
|
||||
// 5.2.40 - Allow no shield
|
||||
// 5.2.39 - Functions for DC frequency: Use func up to F31
|
||||
// 5.2.38 - Exrail MESSAGE("text") to send a user message to all
|
||||
// connected throttles (uses <m "text"> and withrottle Hmtext.
|
||||
// 5.2.37 - Bugfix ESP32: Use BOOSTER_INPUT define
|
||||
// 5.2.36 - Variable frequency for DC mode
|
||||
// 5.2.35 - Bugfix: Make DCC Extended Accessories follow RCN-213
|
||||
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories
|
||||
// - Exrail ASPECT(address,aspect) for above.
|
||||
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
|
||||
// - Exrail intercept <A ...> for DCC Signals.
|
||||
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
|
||||
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
||||
// 5.2.30 - Bugfix: WiThrottle sendIntro after initial N message as well
|
||||
// 5.2.29 - Added IO_I2CDFPlayer.h to support DFPLayer over I2C connected to NXP SC16IS750/SC16IS752 (currently only single UART for SC16IS752)
|
||||
// - Added enhanced IO_I2CDFPLayer enum commands to EXRAIL2.h
|
||||
// - Added PLAYSOUND alias of ANOUT to EXRAILMacros.h
|
||||
// - Added UART detection to I2CManager.cpp
|
||||
// 5.2.28 - ESP32: Can all Wifi channels.
|
||||
// - ESP32: Only write Wifi password to display if it is a well known one
|
||||
// 5.2.27 - Bugfix: IOExpander memory allocation
|
||||
// 5.2.26 - Silently ignore overridden HAL defaults
|
||||
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
|
||||
// 5.2.25 - Fix bug causing <X> after working <D commands
|
||||
// 5.2.24 - Exrail macro asserts to catch
|
||||
// : duplicate/missing automation/route/sequence/call ids
|
||||
// : latches and reserves out of range
|
||||
// : speeds out of range
|
||||
// 5.2.23 - KeywordHasher _hk (no functional change)
|
||||
// 5.2.22 - Bugfixes: Empty turnout descriptions ok; negative route numbers valid.
|
||||
// 5.2.21 - Add STARTUP_DELAY config option to delay CS bootup
|
||||
// 5.2.20 - Check return of Ethernet.begin()
|
||||
|
Reference in New Issue
Block a user