mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 10:53:44 +02:00
Compare commits
221 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
292e51afd3 | ||
|
42cda59109 | ||
|
ce892974ab | ||
|
3f57c1210d | ||
|
c9195f8035 | ||
|
7fc5c48efa | ||
|
c245c27f5d | ||
|
67adf1e6c6 | ||
|
8e71dd8926 | ||
|
ac37228942 | ||
|
90487d2d83 | ||
|
1807fe5c5f | ||
|
6abb65c1f4 | ||
|
971732fce8 | ||
|
8a03b889a3 | ||
|
e12c3fc295 | ||
|
de8f9396f7 | ||
|
f75a6b47f9 | ||
|
bc1398d3c4 | ||
|
0988340ff8 | ||
|
c7af43c70b | ||
|
79a76e95b6 | ||
|
6766d95344 | ||
|
fb3265a413 | ||
|
93fc674e74 | ||
|
ebfde7cc81 | ||
|
4a2513d576 | ||
|
a01d36c8e5 | ||
|
4583761d03 | ||
|
45a7efc935 | ||
|
d924916381 | ||
|
9ec4f2d62a | ||
|
58d4618868 | ||
|
9fdd251a7b | ||
|
5984abe133 | ||
|
87cc8afdf9 | ||
|
7cef3dad2e | ||
|
955362a033 | ||
|
4391b049d8 | ||
|
7a68b0106d | ||
|
ea85a33e03 | ||
|
05da109144 | ||
|
38b5c0cae2 | ||
|
7e58165db9 | ||
|
945af43500 | ||
|
1b1d8fceb4 | ||
|
bb63a559ad | ||
|
fafaa7a1e1 | ||
|
bce5acc8d0 | ||
|
b3d02350f2 | ||
|
7b2647ad81 | ||
|
67c8366512 | ||
|
ebbe698e51 | ||
|
107e9d1d62 | ||
|
9b4c374cd4 | ||
|
d721ed5184 | ||
|
9073aadab7 | ||
|
d9a7eeeef3 | ||
|
1d6e6ec10e | ||
|
bded5d3588 | ||
|
87481209ec | ||
|
dbe682e5ba | ||
|
83e4e4f6ee | ||
|
45eb7c80b6 | ||
|
b541614a19 | ||
|
4756e767cf | ||
|
9ef0189ae8 | ||
|
e866fd1bd7 | ||
|
51491ac1e0 | ||
|
48524b1175 | ||
|
dc200aab75 | ||
|
3954e058c7 | ||
|
5d0da81377 | ||
|
6a5a8acd17 | ||
|
c27aa3a2d2 | ||
|
d12714d51e | ||
|
ca7d728b81 | ||
|
c4f45ddc36 | ||
|
b8b9b6d354 | ||
|
8197e2bffa | ||
|
813ad7e6a4 | ||
|
a7d0042403 | ||
|
3c30ef3c9d | ||
|
2651934a75 | ||
|
30c13190a4 | ||
|
0020ec2b71 | ||
|
7a1b363954 | ||
|
a86f0094a6 | ||
|
e7e8e84829 | ||
|
192e8d9917 | ||
|
5e5f994e48 | ||
|
ee9b195867 | ||
|
07862ff933 | ||
|
119662ddee | ||
|
5f375c57c0 | ||
|
43319fd3dd | ||
|
b1d3f3200a | ||
|
0f3e4576e4 | ||
|
0f5d1e7a51 | ||
|
44ca3bc7b9 | ||
|
dd97c4ba49 | ||
|
2361704f0d | ||
|
a0538ca61b | ||
|
c70ef3ffaa | ||
|
f5cdd88854 | ||
|
8839eb293c | ||
|
136e993418 | ||
|
54773297bf | ||
|
8e63c452b2 | ||
|
8141311e66 | ||
|
76c2b5ae91 | ||
|
85a2b9231b | ||
|
dd2260709d | ||
|
c61d8772e3 | ||
|
cfee1057c4 | ||
|
a8c9c2f98d | ||
|
f8f80b18ca | ||
|
d7b2cf3d76 | ||
|
f556cc5e1c | ||
|
ec4455ae93 | ||
|
180d5f5abb | ||
|
fc3b21e5c5 | ||
|
2f9d4429bc | ||
|
aaa1eb5385 | ||
|
8b3ca6c2ff | ||
|
92ef42b596 | ||
|
2f860e594c | ||
|
174f8f209c | ||
|
42fdf4fed3 | ||
|
1cc147cc98 | ||
|
46d0304ce0 | ||
|
05b225c352 | ||
|
c9ade73376 | ||
|
55cdbbbb66 | ||
|
086336158f | ||
|
f2891ee348 | ||
|
25c2f06574 | ||
|
98071602c3 | ||
|
d35529e94a | ||
|
9e49167be9 | ||
|
cec26c47e2 | ||
|
fcd54b3a80 | ||
|
ad4095fb04 | ||
|
a8bd3df992 | ||
|
933eab5f2d | ||
|
c51b445e41 | ||
|
f2c2e7ecaa | ||
|
62b17d4a71 | ||
|
0b3c0bfe9e | ||
|
eb54c78d74 | ||
|
def6c24bac | ||
|
163dd270e8 | ||
|
4f7d3a5cfc | ||
|
0880507d89 | ||
|
62f1c04ee3 | ||
|
7954c85b7d | ||
|
fab05bac79 | ||
|
6866216dfc | ||
|
e67ab2b05f | ||
|
5d27da58b8 | ||
|
79a318b455 | ||
|
5f34fc396a | ||
|
c34c93c2cc | ||
|
7a2beda2a9 | ||
|
f3d7851467 | ||
|
809b54d9f0 | ||
|
609d3d13de | ||
|
ddc55690f3 | ||
|
9562d1a3b9 | ||
|
36e38bf861 | ||
|
df4bae365d | ||
|
7706e6560b | ||
|
090ece6e59 | ||
|
5a5702a5b5 | ||
|
a072f3222b | ||
|
4861e592c7 | ||
|
4e2bb445d1 | ||
|
ae6958b636 | ||
|
781d0325af | ||
|
62d1f46a03 | ||
|
5860ad3f1d | ||
|
8aacb6dc5c | ||
|
92fb06c691 | ||
|
bf52f99a3a | ||
|
336a6479e4 | ||
|
271d453b99 | ||
|
de4bf42923 | ||
|
bf97adfe2d | ||
|
f1116ffba4 | ||
|
da31e9cbc5 | ||
|
7f27cfc9cb | ||
|
e7ada19c97 | ||
|
ad72e2f697 | ||
|
98d6ff7709 | ||
|
7e7435eafa | ||
|
f134d87c85 | ||
|
5ee59e5f4b | ||
|
bc14cb176f | ||
|
0fee057b1b | ||
|
f26f5ab40b | ||
|
9d0dbf7878 | ||
|
47641a4b01 | ||
|
ef95e98a44 | ||
|
8803dc0ea3 | ||
|
3ae8ce30ff | ||
|
abfd63eb0d | ||
|
aa550ec3e6 | ||
|
57d90d679a | ||
|
7d460e5ef1 | ||
|
3ccae75e37 | ||
|
6b4199be27 | ||
|
0d51294ea5 | ||
|
9e0dcb6fc8 | ||
|
052178970b | ||
|
c53dea018f | ||
|
06ace2484f | ||
|
e112be7087 | ||
|
6e45b3a434 | ||
|
f6b5a47975 | ||
|
fee0a75b36 | ||
|
c8a4323a4f |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- name: Install Python Wheel
|
- name: Install Python Wheel
|
||||||
run: pip install wheel
|
run: pip install wheel
|
||||||
- name: Install PlatformIO Core
|
- name: Install PlatformIO Core
|
||||||
run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip
|
run: pip install -U platformio
|
||||||
- name: Copy generic config over
|
- name: Copy generic config over
|
||||||
run: cp config.example.h config.h
|
run: cp config.example.h config.h
|
||||||
- name: Compile Command Station (AVR)
|
- name: Compile Command Station (AVR)
|
||||||
|
1
.github/workflows/sha.yml
vendored
1
.github/workflows/sha.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
|||||||
# Set the type of machine to run on
|
# Set the type of machine to run on
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
steps:
|
steps:
|
||||||
# Checks out a copy of your repository on the ubuntu-latest machine
|
# Checks out a copy of your repository on the ubuntu-latest machine
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ Release/*
|
|||||||
.vscode/
|
.vscode/
|
||||||
config.h
|
config.h
|
||||||
.vscode/extensions.json
|
.vscode/extensions.json
|
||||||
|
mySetup.h
|
||||||
|
myFilter.cpp
|
||||||
|
@@ -25,7 +25,7 @@ DCCEXParser * CommandDistributor::parser=0;
|
|||||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||||
if (buffer[0] == '<') {
|
if (buffer[0] == '<') {
|
||||||
if (!parser) parser = new DCCEXParser();
|
if (!parser) parser = new DCCEXParser();
|
||||||
parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
|
parser->parse(streamer, buffer, streamer);
|
||||||
}
|
}
|
||||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||||
}
|
}
|
||||||
|
@@ -58,10 +58,9 @@ void setup()
|
|||||||
// Responsibility 1: Start the usb connection for diagnostics
|
// Responsibility 1: Start the usb connection for diagnostics
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
DIAG(F("DCC++ EX v%S"),F(VERSION));
|
|
||||||
|
|
||||||
CONDITIONAL_LCD_START {
|
CONDITIONAL_LCD_START {
|
||||||
// This block is ignored if LCD not in use
|
// This block is still executed for DIAGS if LCD not in use
|
||||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||||
LCD(1,F("Starting"));
|
LCD(1,F("Starting"));
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,7 @@ void setup()
|
|||||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||||
|
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), 2560);
|
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||||
#endif // WIFI_ON
|
#endif // WIFI_ON
|
||||||
|
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
@@ -96,6 +95,11 @@ void setup()
|
|||||||
#undef SETUP
|
#undef SETUP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(LCN_SERIAL)
|
||||||
|
LCN_SERIAL.begin(115200);
|
||||||
|
LCN::init(LCN_SERIAL);
|
||||||
|
#endif
|
||||||
|
|
||||||
LCD(1,F("Ready"));
|
LCD(1,F("Ready"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,17 +126,19 @@ void loop()
|
|||||||
RMFT::loop();
|
RMFT::loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(LCN_SERIAL)
|
||||||
|
LCN::loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
LCDDisplay::loop(); // ignored if LCD not in use
|
LCDDisplay::loop(); // ignored if LCD not in use
|
||||||
|
|
||||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
// Report any decrease in memory (will automatically trigger on first call)
|
||||||
#if ENABLE_FREE_MEM_WARNING
|
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||||
static int ramLowWatermark = 32767; // replaced on first loop
|
|
||||||
|
|
||||||
int freeNow = freeMemory();
|
int freeNow = minimumFreeMemory();
|
||||||
if (freeNow < ramLowWatermark)
|
if (freeNow < ramLowWatermark)
|
||||||
{
|
{
|
||||||
ramLowWatermark = freeNow;
|
ramLowWatermark = freeNow;
|
||||||
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
|
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
336
DCC.cpp
336
DCC.cpp
@@ -45,10 +45,12 @@ const byte FN_GROUP_4=0x08;
|
|||||||
const byte FN_GROUP_5=0x10;
|
const byte FN_GROUP_5=0x10;
|
||||||
|
|
||||||
FSH* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
|
byte DCC::joinRelay=UNUSED_PIN;
|
||||||
|
byte DCC::globalSpeedsteps=128;
|
||||||
|
|
||||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||||
shieldName=(FSH *)motorShieldName;
|
shieldName=(FSH *)motorShieldName;
|
||||||
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||||
|
|
||||||
// Load stuff from EEprom
|
// Load stuff from EEprom
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
@@ -57,6 +59,14 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv
|
|||||||
DCCWaveform::begin(mainDriver,progDriver);
|
DCCWaveform::begin(mainDriver,progDriver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||||
|
joinRelay=joinRelayPin;
|
||||||
|
if (joinRelay!=UNUSED_PIN) {
|
||||||
|
pinMode(joinRelay,OUTPUT);
|
||||||
|
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||||
setThrottle2(cab, speedCode);
|
setThrottle2(cab, speedCode);
|
||||||
@@ -68,19 +78,45 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
|||||||
|
|
||||||
uint8_t b[4];
|
uint8_t b[4];
|
||||||
uint8_t nB = 0;
|
uint8_t nB = 0;
|
||||||
// DIAG(F("\nsetSpeedInternal %d %x"),cab,speedCode);
|
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
|
||||||
|
|
||||||
if (cab > 127)
|
if (cab > 127)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
b[nB++] = lowByte(cab);
|
b[nB++] = lowByte(cab);
|
||||||
b[nB++] = SET_SPEED; // 128-step speed control byte
|
|
||||||
b[nB++] = speedCode; // for encoding see setThrottle
|
if (globalSpeedsteps <= 28) {
|
||||||
|
|
||||||
|
uint8_t speed128 = speedCode & 0x7F;
|
||||||
|
uint8_t speed28;
|
||||||
|
uint8_t code28;
|
||||||
|
|
||||||
|
if (speed128 == 0 || speed128 == 1) { // stop or emergency stop
|
||||||
|
code28 = speed128;
|
||||||
|
} else {
|
||||||
|
speed28= (speed128*10+36)/46; // convert 2-127 to 1-28
|
||||||
|
/*
|
||||||
|
if (globalSpeedsteps <= 14) // Don't want to do 14 steps, to get F0 there is ugly
|
||||||
|
code28 = (speed28+3)/2 | (Value of F0); // convert 1-28 to DCC 14 step speed code
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
code28 = (speed28+3)/2 | ( (speed28 & 1) ? 0 : 0b00010000 ); // convert 1-28 to DCC 28 step speed code
|
||||||
|
}
|
||||||
|
// Construct command byte from:
|
||||||
|
// command speed direction
|
||||||
|
b[nB++] = 0b01000000 | code28 | ((speedCode & 0x80) ? 0b00100000 : 0);
|
||||||
|
|
||||||
|
} else { // 128 speedsteps
|
||||||
|
|
||||||
|
b[nB++] = SET_SPEED; // 128-step speed control byte
|
||||||
|
b[nB++] = speedCode; // for encoding see setThrottle
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||||
// DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2);
|
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||||
byte b[4];
|
byte b[4];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
|
|
||||||
@@ -90,7 +126,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
|||||||
if (byte1!=0) b[nB++] = byte1;
|
if (byte1!=0) b[nB++] = byte1;
|
||||||
b[nB++] = byte2;
|
b[nB++] = byte2;
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 3); // send packet 3 times
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||||
@@ -106,8 +142,29 @@ bool DCC::getThrottleDirection(int cab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set function to value on or off
|
// Set function to value on or off
|
||||||
void DCC::setFn( int cab, byte functionNumber, bool on) {
|
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||||
if (cab<=0 || functionNumber>28) return;
|
if (cab<=0 ) return;
|
||||||
|
|
||||||
|
if (functionNumber>28) {
|
||||||
|
//non reminding advanced binary bit set
|
||||||
|
byte b[5];
|
||||||
|
byte nB = 0;
|
||||||
|
if (cab > 127)
|
||||||
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
|
b[nB++] = lowByte(cab);
|
||||||
|
if (functionNumber <= 127) {
|
||||||
|
b[nB++] = 0b11011101; // Binary State Control Instruction short form
|
||||||
|
b[nB++] = functionNumber | (on ? 0x80 : 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b[nB++] = 0b11000000; // Binary State Control Instruction long form
|
||||||
|
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||||
|
b[nB++] = functionNumber >>7 ; // high order bits
|
||||||
|
}
|
||||||
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return;
|
if (reg<0) return;
|
||||||
|
|
||||||
@@ -126,7 +183,7 @@ void DCC::setFn( int cab, byte functionNumber, bool on) {
|
|||||||
// Change function according to how button was pressed,
|
// Change function according to how button was pressed,
|
||||||
// typically in WiThrottle.
|
// typically in WiThrottle.
|
||||||
// Returns new state or -1 if nothing was changed.
|
// Returns new state or -1 if nothing was changed.
|
||||||
int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
|
||||||
int funcstate = -1;
|
int funcstate = -1;
|
||||||
if (cab<=0 || functionNumber>28) return funcstate;
|
if (cab<=0 || functionNumber>28) return funcstate;
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
@@ -148,15 +205,15 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
|||||||
} else {
|
} else {
|
||||||
// toggle function on press, ignore release
|
// toggle function on press, ignore release
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
speedTable[reg].functions ^= funcmask;
|
speedTable[reg].functions ^= funcmask;
|
||||||
}
|
}
|
||||||
funcstate = speedTable[reg].functions & funcmask;
|
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
}
|
}
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
return funcstate;
|
return funcstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCC::getFn( int cab, byte functionNumber) {
|
int DCC::getFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
if (cab<=0 || functionNumber>28) return -1; // unknown
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return -1;
|
if (reg<0) return -1;
|
||||||
@@ -167,7 +224,7 @@ int DCC::getFn( int cab, byte functionNumber) {
|
|||||||
|
|
||||||
// Set the group flag to say we have touched the particular group.
|
// Set the group flag to say we have touched the particular group.
|
||||||
// A group will be reminded only if it has been touched.
|
// A group will be reminded only if it has been touched.
|
||||||
void DCC::updateGroupflags(byte & flags, int functionNumber) {
|
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
||||||
byte groupMask;
|
byte groupMask;
|
||||||
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
||||||
else if (functionNumber<=8) groupMask=FN_GROUP_2;
|
else if (functionNumber<=8) groupMask=FN_GROUP_2;
|
||||||
@@ -191,6 +248,10 @@ void DCC::setAccessory(int address, byte number, bool activate) {
|
|||||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// writeCVByteMain: Write a byte with PoM on main. This writes
|
||||||
|
// the 5 byte sized packet to implement this DCC function
|
||||||
|
//
|
||||||
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||||
byte b[5];
|
byte b[5];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
@@ -205,6 +266,10 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
|||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// writeCVBitMain: Write a bit of a byte with PoM on main. This writes
|
||||||
|
// the 5 byte sized packet to implement this DCC function
|
||||||
|
//
|
||||||
void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||||
byte b[5];
|
byte b[5];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
@@ -223,6 +288,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setProgTrackSyncMain(bool on) {
|
void DCC::setProgTrackSyncMain(bool on) {
|
||||||
|
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
|
||||||
DCCWaveform::progTrackSyncMain=on;
|
DCCWaveform::progTrackSyncMain=on;
|
||||||
}
|
}
|
||||||
void DCC::setProgTrackBoost(bool on) {
|
void DCC::setProgTrackBoost(bool on) {
|
||||||
@@ -276,9 +342,9 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
|||||||
|
|
||||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
WB,WACK, // Write
|
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||||
VB,WACK, // validate byte
|
// handle decoders that dont ack a write
|
||||||
ITC1, // if ok callback (1)
|
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,10 +398,6 @@ const ackOp FLASH READ_CV_PROG[] = {
|
|||||||
|
|
||||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV, (ackOp)1,
|
|
||||||
SETBIT, (ackOp)7,
|
|
||||||
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
|
|
||||||
|
|
||||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||||
@@ -404,14 +466,15 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
|||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
WB,WACK, // ignore router without cv19 support
|
WB,WACK, // ignore dedcoder without cv19 support
|
||||||
// Turn off long address flag
|
// Turn off long address flag
|
||||||
SETCV,(ackOp)29,
|
SETCV,(ackOp)29,
|
||||||
SETBIT,(ackOp)5,
|
SETBIT,(ackOp)5,
|
||||||
W0,WACK,NAKFAIL,
|
W0,WACK,
|
||||||
|
V0,WACK,NAKFAIL,
|
||||||
SETCV, (ackOp)1,
|
SETCV, (ackOp)1,
|
||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK,NAKFAIL,
|
WB,WACK, // some decoders don't ACK writes
|
||||||
VB,WACK,ITCB,
|
VB,WACK,ITCB,
|
||||||
FAIL
|
FAIL
|
||||||
};
|
};
|
||||||
@@ -421,89 +484,84 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
|||||||
// Clear consist CV 19
|
// Clear consist CV 19
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
WB,WACK, // ignore router without cv19 support
|
WB,WACK, // ignore decoder without cv19 support
|
||||||
// Turn on long address flag cv29 bit 5
|
// Turn on long address flag cv29 bit 5
|
||||||
SETCV,(ackOp)29,
|
SETCV,(ackOp)29,
|
||||||
SETBIT,(ackOp)5,
|
SETBIT,(ackOp)5,
|
||||||
W1,WACK,NAKFAIL,
|
W1,WACK,
|
||||||
|
V1,WACK,NAKFAIL,
|
||||||
// Store high byte of address in cv 17
|
// Store high byte of address in cv 17
|
||||||
SETCV, (ackOp)17,
|
SETCV, (ackOp)17,
|
||||||
SETBYTEH, // high byte of word
|
SETBYTEH, // high byte of word
|
||||||
WB,WACK,NAKFAIL,
|
WB,WACK,
|
||||||
VB,WACK,NAKFAIL,
|
VB,WACK,NAKFAIL,
|
||||||
// store
|
// store
|
||||||
SETCV, (ackOp)18,
|
SETCV, (ackOp)18,
|
||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK,NAKFAIL,
|
WB,WACK,
|
||||||
VB,WACK,ITC1, // callback(1) means Ok
|
VB,WACK,ITC1, // callback(1) means Ok
|
||||||
FAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
// On the following prog-track functions blocking defaults to false.
|
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||||
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
|
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||||
// During that wait, other parts of the system will be unresponsive.
|
|
||||||
// blocking =false means the callback will be called some time after the API returns (typically a few tenths of a second)
|
|
||||||
// but that would be very inconvenient in a Wifi situaltion where the stream becomes
|
|
||||||
// unuavailable immediately after the API rerturns.
|
|
||||||
|
|
||||||
void DCC::writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
|
|
||||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback, blocking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||||
void DCC::writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
|
|
||||||
if (bitNum >= 8) callback(-1);
|
if (bitNum >= 8) callback(-1);
|
||||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback, blocking);
|
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
|
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback, blocking);
|
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||||
void DCC::verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
|
|
||||||
if (bitNum >= 8) callback(-1);
|
if (bitNum >= 8) callback(-1);
|
||||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback, blocking);
|
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DCC::readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking) {
|
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||||
if (bitNum >= 8) callback(-1);
|
if (bitNum >= 8) callback(-1);
|
||||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback, blocking);
|
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::readCV(int cv, ACK_CALLBACK callback, bool blocking) {
|
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
|
||||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback, blocking);
|
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
|
void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
|
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) {
|
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||||
if (id<1 || id>10239) { //0x27FF according to standard
|
if (id<1 || id>10239) { //0x27FF according to standard
|
||||||
callback(-1);
|
callback(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id<=127)
|
if (id<=127)
|
||||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback, blocking);
|
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||||
else
|
else
|
||||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking);
|
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
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);
|
int reg=lookupSpeedTable(cab);
|
||||||
if (reg>=0) speedTable[reg].loco=0;
|
if (reg>=0) speedTable[reg].loco=0;
|
||||||
|
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||||
}
|
}
|
||||||
void DCC::forgetAllLocos() { // removes all speed reminders
|
void DCC::forgetAllLocos() { // removes all speed reminders
|
||||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
setThrottle2(0,1); // ESTOP all locos still on track
|
||||||
|
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte DCC::loopStatus=0;
|
byte DCC::loopStatus=0;
|
||||||
|
|
||||||
void DCC::loop() {
|
void DCC::loop() {
|
||||||
DCCWaveform::loop(); // power overload checks
|
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||||
ackManagerLoop(false); // maintain prog track ack manager
|
ackManagerLoop(); // maintain prog track ack manager
|
||||||
issueReminders();
|
issueReminders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +589,7 @@ bool DCC::issueReminder(int reg) {
|
|||||||
|
|
||||||
switch (loopStatus) {
|
switch (loopStatus) {
|
||||||
case 0:
|
case 0:
|
||||||
// DIAG(F("\nReminder %d speed %d"),loco,speedTable[reg].speedCode);
|
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||||
setThrottle2(loco, speedTable[reg].speedCode);
|
setThrottle2(loco, speedTable[reg].speedCode);
|
||||||
break;
|
break;
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
@@ -589,7 +647,7 @@ int DCC::lookupSpeedTable(int locoId) {
|
|||||||
}
|
}
|
||||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||||
if (reg >= MAX_LOCOS) {
|
if (reg >= MAX_LOCOS) {
|
||||||
DIAG(F("\nToo many locos\n"));
|
DIAG(F("Too many locos"));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (reg==firstEmpty){
|
if (reg==firstEmpty){
|
||||||
@@ -627,83 +685,93 @@ int DCC::ackManagerWord;
|
|||||||
int DCC::ackManagerCv;
|
int DCC::ackManagerCv;
|
||||||
byte DCC::ackManagerBitNum;
|
byte DCC::ackManagerBitNum;
|
||||||
bool DCC::ackReceived;
|
bool DCC::ackReceived;
|
||||||
|
bool DCC::ackManagerRejoin;
|
||||||
|
|
||||||
|
CALLBACK_STATE DCC::callbackState=READY;
|
||||||
|
|
||||||
ACK_CALLBACK DCC::ackManagerCallback;
|
ACK_CALLBACK DCC::ackManagerCallback;
|
||||||
|
|
||||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
|
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||||
|
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||||
|
callback(-2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||||
|
if (ackManagerRejoin ) {
|
||||||
|
// Change from JOIN must zero resets packet.
|
||||||
|
setProgTrackSyncMain(false);
|
||||||
|
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCCWaveform::progTrack.autoPowerOff=false;
|
||||||
|
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||||
|
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||||
|
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||||
|
}
|
||||||
|
|
||||||
ackManagerCv = cv;
|
ackManagerCv = cv;
|
||||||
ackManagerProg = program;
|
ackManagerProg = program;
|
||||||
ackManagerByte = byteValueOrBitnum;
|
ackManagerByte = byteValueOrBitnum;
|
||||||
ackManagerBitNum=byteValueOrBitnum;
|
ackManagerBitNum=byteValueOrBitnum;
|
||||||
ackManagerCallback = callback;
|
ackManagerCallback = callback;
|
||||||
if (blocking) ackManagerLoop(blocking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
|
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||||
ackManagerWord=wordval;
|
ackManagerWord=wordval;
|
||||||
ackManagerProg = program;
|
ackManagerSetup(0, 0, program, callback);
|
||||||
ackManagerCallback = callback;
|
}
|
||||||
if (blocking) ackManagerLoop(blocking);
|
|
||||||
}
|
|
||||||
|
|
||||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||||
|
|
||||||
// checkRessets return true if the caller should yield back to loop and try later.
|
// checkRessets return true if the caller should yield back to loop and try later.
|
||||||
bool DCC::checkResets(bool blocking, uint8_t numResets) {
|
bool DCC::checkResets(uint8_t numResets) {
|
||||||
if (blocking) {
|
|
||||||
// must block waiting for restest to be issued
|
|
||||||
while(DCCWaveform::progTrack.sentResetsSincePacket < numResets);
|
|
||||||
return false; // caller need not yield
|
|
||||||
}
|
|
||||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::ackManagerLoop(bool blocking) {
|
void DCC::ackManagerLoop() {
|
||||||
while (ackManagerProg) {
|
while (ackManagerProg) {
|
||||||
byte opcode=GETFLASH(ackManagerProg);
|
byte opcode=GETFLASH(ackManagerProg);
|
||||||
|
|
||||||
// breaks from this switch will step to next prog entry
|
// breaks from this switch will step to next prog entry
|
||||||
// returns from this switch will stay on same entry
|
// returns from this switch will stay on same entry
|
||||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||||
// if blocking then we must ONLY return AFTER callback issued
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case BASELINE:
|
case BASELINE:
|
||||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||||
if (Diag::ACK) DIAG(F("\nAuto Prog power on"));
|
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
|
||||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
|
||||||
DCCWaveform::progTrack.autoPowerOff=true;
|
|
||||||
if (!blocking) return;
|
|
||||||
}
|
|
||||||
if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
|
||||||
DCCWaveform::progTrack.setAckBaseline();
|
DCCWaveform::progTrack.setAckBaseline();
|
||||||
|
callbackState=READY;
|
||||||
break;
|
break;
|
||||||
case W0: // write 0 bit
|
case W0: // write 0 bit
|
||||||
case W1: // write 1 bit
|
case W1: // write 1 bit
|
||||||
{
|
{
|
||||||
if (checkResets(blocking, RESET_MIN)) return;
|
if (checkResets(RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("\nW%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WB: // write byte
|
case WB: // write byte
|
||||||
{
|
{
|
||||||
if (checkResets(blocking, RESET_MIN)) return;
|
if (checkResets( RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("\nWB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
|
callbackState=AFTER_WRITE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VB: // Issue validate Byte packet
|
case VB: // Issue validate Byte packet
|
||||||
{
|
{
|
||||||
if (checkResets(blocking, RESET_MIN)) return;
|
if (checkResets( RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("\nVB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
DCCWaveform::progTrack.setAckPending();
|
DCCWaveform::progTrack.setAckPending();
|
||||||
@@ -713,8 +781,8 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
case V0:
|
case V0:
|
||||||
case V1: // Issue validate bit=0 or bit=1 packet
|
case V1: // Issue validate bit=0 or bit=1 packet
|
||||||
{
|
{
|
||||||
if (checkResets(blocking, RESET_MIN)) return;
|
if (checkResets(RESET_MIN)) return;
|
||||||
if (Diag::ACK) DIAG(F("\nV%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||||
@@ -725,36 +793,29 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
case WACK: // wait for ack (or absence of ack)
|
case WACK: // wait for ack (or absence of ack)
|
||||||
{
|
{
|
||||||
byte ackState=2; // keep polling
|
byte ackState=2; // keep polling
|
||||||
if (blocking) {
|
|
||||||
while(ackState==2) ackState=DCCWaveform::progTrack.getAck();
|
ackState=DCCWaveform::progTrack.getAck();
|
||||||
}
|
if (ackState==2) return; // keep polling
|
||||||
else {
|
|
||||||
ackState=DCCWaveform::progTrack.getAck();
|
|
||||||
if (ackState==2) return; // keep polling
|
|
||||||
}
|
|
||||||
ackReceived=ackState==1;
|
ackReceived=ackState==1;
|
||||||
break; // we have a genuine ACK result
|
break; // we have a genuine ACK result
|
||||||
}
|
}
|
||||||
case ITC0:
|
case ITC0:
|
||||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
ackManagerProg = NULL; // all done now
|
callback(opcode==ITC0?0:1);
|
||||||
callback(opcode==ITC0?0:1);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ITCB: // If True callback(byte)
|
case ITCB: // If True callback(byte)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
ackManagerProg = NULL; // all done now
|
|
||||||
callback(ackManagerByte);
|
callback(ackManagerByte);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ITCB7: // If True callback(byte & 0xF)
|
case ITCB7: // If True callback(byte & 0x7F)
|
||||||
if (ackReceived) {
|
if (ackReceived) {
|
||||||
ackManagerProg = NULL; // all done now
|
|
||||||
callback(ackManagerByte & 0x7F);
|
callback(ackManagerByte & 0x7F);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -762,15 +823,13 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
|
|
||||||
case NAKFAIL: // If nack callback(-1)
|
case NAKFAIL: // If nack callback(-1)
|
||||||
if (!ackReceived) {
|
if (!ackReceived) {
|
||||||
ackManagerProg = NULL; // all done now
|
callback(-1);
|
||||||
callback(-1);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FAIL: // callback(-1)
|
case FAIL: // callback(-1)
|
||||||
ackManagerProg = NULL;
|
callback(-1);
|
||||||
callback(-1);
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case STARTMERGE:
|
case STARTMERGE:
|
||||||
@@ -814,7 +873,6 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
|
|
||||||
case COMBINELOCOID:
|
case COMBINELOCOID:
|
||||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||||
ackManagerProg=NULL;
|
|
||||||
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
|
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -829,8 +887,7 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
case SKIPTARGET:
|
case SKIPTARGET:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DIAG(F("\n!! ackOp %d FAULT!!"),opcode);
|
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||||
ackManagerProg=NULL;
|
|
||||||
callback( -1);
|
callback( -1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -838,25 +895,70 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::callback(int value) {
|
void DCC::callback(int value) {
|
||||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
static unsigned long callbackStart;
|
||||||
if (Diag::ACK) DIAG(F("\nAuto Prog power off"));
|
// We are about to leave programming mode
|
||||||
DCCWaveform::progTrack.doAutoPowerOff();
|
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||||
|
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||||
|
|
||||||
|
switch (callbackState) {
|
||||||
|
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||||
|
callbackStart=millis();
|
||||||
|
callbackState=WAITING_100;
|
||||||
|
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAITING_100: // waiting for 100mS
|
||||||
|
if (millis()-callbackStart < 100) break;
|
||||||
|
// stable after power maintained for 100mS
|
||||||
|
|
||||||
|
// If we are going to power off anyway, it doesnt matter
|
||||||
|
// but if we will keep the power on, we must off it for 30mS
|
||||||
|
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||||
|
else { // Need to cycle power off and on
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
callbackStart=millis();
|
||||||
|
callbackState=WAITING_30;
|
||||||
|
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAITING_30: // waiting for 30mS with power off
|
||||||
|
if (millis()-callbackStart < 30) break;
|
||||||
|
//power has been off for 30mS
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
callbackState=READY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case READY: // ready after read, or write after power delay and off period.
|
||||||
|
// power off if we powered it on
|
||||||
|
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||||
|
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||||
|
DCCWaveform::progTrack.doAutoPowerOff();
|
||||||
|
}
|
||||||
|
// Restore <1 JOIN> to state before BASELINE
|
||||||
|
if (ackManagerRejoin) {
|
||||||
|
setProgTrackSyncMain(true);
|
||||||
|
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ackManagerProg=NULL; // no more steps to execute
|
||||||
|
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||||
|
(ackManagerCallback)( value);
|
||||||
}
|
}
|
||||||
if (Diag::ACK) DIAG(F("\nCallback(%d)\n"),value);
|
|
||||||
(ackManagerCallback)( value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::displayCabList(Print * stream) {
|
void DCC::displayCabList(Print * stream) {
|
||||||
|
|
||||||
int used=0;
|
int used=0;
|
||||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
if (speedTable[reg].loco>0) {
|
if (speedTable[reg].loco>0) {
|
||||||
used ++;
|
used ++;
|
||||||
StringFormatter::send(stream,F("\ncab=%d, speed=%d, dir=%c "),
|
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||||
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("\nUsed=%d, max=%d\n"),used,MAX_LOCOS);
|
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
63
DCC.h
63
DCC.h
@@ -22,7 +22,8 @@
|
|||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "MotorDrivers.h"
|
#include "MotorDrivers.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
typedef void (*ACK_CALLBACK)(int result);
|
|
||||||
|
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||||
|
|
||||||
enum ackOp : byte
|
enum ackOp : byte
|
||||||
{ // Program opcodes for the ack Manager
|
{ // Program opcodes for the ack Manager
|
||||||
@@ -53,6 +54,14 @@ enum ackOp : byte
|
|||||||
SKIPTARGET = 0xFF // jump to target
|
SKIPTARGET = 0xFF // jump to target
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum CALLBACK_STATE : byte {
|
||||||
|
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||||
|
WAITING_100, // Waiting for 100mS of stable power
|
||||||
|
WAITING_30, // waiting to 30ms of power off gap.
|
||||||
|
READY, // Ready to complete callback
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Allocations with memory implications..!
|
// Allocations with memory implications..!
|
||||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||||
#ifdef ARDUINO_AVR_UNO
|
#ifdef ARDUINO_AVR_UNO
|
||||||
@@ -65,6 +74,7 @@ class DCC
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||||
|
static void setJoinRelayPin(byte joinRelayPin);
|
||||||
static void loop();
|
static void loop();
|
||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
@@ -74,25 +84,25 @@ public:
|
|||||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||||
static void setFunction(int cab, byte fByte, byte eByte);
|
static void setFunction(int cab, byte fByte, byte eByte);
|
||||||
static void setFn(int cab, byte functionNumber, bool on);
|
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||||
static int changeFn(int cab, byte functionNumber, bool pressed);
|
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
||||||
static int getFn(int cab, byte functionNumber);
|
static int getFn(int cab, int16_t functionNumber);
|
||||||
static void updateGroupflags(byte &flags, int functionNumber);
|
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||||
static bool writeTextPacket(byte *b, int nBytes);
|
static bool writeTextPacket(byte *b, int nBytes);
|
||||||
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
||||||
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
||||||
|
|
||||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||||
static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false);
|
static void readCV(int16_t cv, ACK_CALLBACK callback);
|
||||||
static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error
|
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
||||||
static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
static void writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
||||||
static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||||
static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
||||||
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||||
|
|
||||||
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
|
static void getLocoId(ACK_CALLBACK callback);
|
||||||
static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false);
|
static void setLocoId(int id,ACK_CALLBACK callback);
|
||||||
|
|
||||||
// Enhanced API functions
|
// Enhanced API functions
|
||||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||||
@@ -100,6 +110,9 @@ public:
|
|||||||
static void displayCabList(Print *stream);
|
static void displayCabList(Print *stream);
|
||||||
|
|
||||||
static FSH *getMotorShieldName();
|
static FSH *getMotorShieldName();
|
||||||
|
static inline void setGlobalSpeedsteps(byte s) {
|
||||||
|
globalSpeedsteps = s;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LOCO
|
struct LOCO
|
||||||
@@ -109,6 +122,7 @@ private:
|
|||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
unsigned long functions;
|
unsigned long functions;
|
||||||
};
|
};
|
||||||
|
static byte joinRelay;
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
static void updateLocoReminder(int loco, byte speedCode);
|
static void updateLocoReminder(int loco, byte speedCode);
|
||||||
@@ -116,6 +130,7 @@ private:
|
|||||||
static bool issueReminder(int reg);
|
static bool issueReminder(int reg);
|
||||||
static int nextLoco;
|
static int nextLoco;
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
@@ -132,13 +147,15 @@ private:
|
|||||||
static int ackManagerWord;
|
static int ackManagerWord;
|
||||||
static byte ackManagerStash;
|
static byte ackManagerStash;
|
||||||
static bool ackReceived;
|
static bool ackReceived;
|
||||||
|
static bool ackManagerRejoin;
|
||||||
static ACK_CALLBACK ackManagerCallback;
|
static ACK_CALLBACK ackManagerCallback;
|
||||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
static CALLBACK_STATE callbackState;
|
||||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||||
static void ackManagerLoop(bool blocking);
|
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||||
static bool checkResets(bool blocking, uint8_t numResets);
|
static void ackManagerLoop();
|
||||||
|
static bool checkResets( uint8_t numResets);
|
||||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||||
|
|
||||||
// NMRA codes #
|
// NMRA codes #
|
||||||
static const byte SET_SPEED = 0x3f;
|
static const byte SET_SPEED = 0x3f;
|
||||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||||
@@ -164,6 +181,16 @@ private:
|
|||||||
#define ARDUINO_TYPE "MEGA"
|
#define ARDUINO_TYPE "MEGA"
|
||||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||||
#define ARDUINO_TYPE "MEGAAVR"
|
#define ARDUINO_TYPE "MEGAAVR"
|
||||||
|
#elif defined(ARDUINO_TEENSY32)
|
||||||
|
#define ARDUINO_TYPE "TEENSY32"
|
||||||
|
#elif defined(ARDUINO_TEENSY35)
|
||||||
|
#define ARDUINO_TYPE "TEENSY35"
|
||||||
|
#elif defined(ARDUINO_TEENSY36)
|
||||||
|
#define ARDUINO_TYPE "TEENSY36"
|
||||||
|
#elif defined(ARDUINO_TEENSY40)
|
||||||
|
#define ARDUINO_TYPE "TEENSY40"
|
||||||
|
#elif defined(ARDUINO_TEENSY41)
|
||||||
|
#define ARDUINO_TYPE "TEENSY41"
|
||||||
#else
|
#else
|
||||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||||
#endif
|
#endif
|
||||||
|
7
DCCEX.h
7
DCCEX.h
@@ -9,15 +9,12 @@
|
|||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#ifdef ARDUINO_AVR_UNO_WIFI_REV2
|
#include "WifiInterface.h"
|
||||||
#include "WifiInterfaceRev2.h"
|
|
||||||
#else
|
|
||||||
#include "WifiInterface.h"
|
|
||||||
#endif
|
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
#endif
|
#endif
|
||||||
#include "LCD_Implementation.h"
|
#include "LCD_Implementation.h"
|
||||||
|
#include "LCN.h"
|
||||||
#include "freeMemory.h"
|
#include "freeMemory.h"
|
||||||
|
|
||||||
#if __has_include ( "myAutomation.h")
|
#if __has_include ( "myAutomation.h")
|
||||||
|
299
DCCEXParser.cpp
299
DCCEXParser.cpp
@@ -30,32 +30,40 @@
|
|||||||
|
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include <avr/wdt.h>
|
||||||
|
|
||||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
// These keywords are used in various commands. The number is what you get if you use the keyword as a parameter.
|
||||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||||
const int HASH_KEYWORD_PROG = -29718;
|
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||||
const int HASH_KEYWORD_MAIN = 11339;
|
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||||
const int HASH_KEYWORD_JOIN = -30750;
|
const int16_t HASH_KEYWORD_JOIN = -30750;
|
||||||
const int HASH_KEYWORD_CABS = -11981;
|
const int16_t HASH_KEYWORD_CABS = -11981;
|
||||||
const int HASH_KEYWORD_RAM = 25982;
|
const int16_t HASH_KEYWORD_RAM = 25982;
|
||||||
const int HASH_KEYWORD_CMD = 9962;
|
const int16_t HASH_KEYWORD_CMD = 9962;
|
||||||
const int HASH_KEYWORD_WIT = 31594;
|
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||||
const int HASH_KEYWORD_WIFI = -5583;
|
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||||
const int HASH_KEYWORD_ACK = 3113;
|
const int16_t HASH_KEYWORD_ACK = 3113;
|
||||||
const int HASH_KEYWORD_ON = 2657;
|
const int16_t HASH_KEYWORD_ON = 2657;
|
||||||
const int HASH_KEYWORD_DCC = 6436;
|
const int16_t HASH_KEYWORD_DCC = 6436;
|
||||||
const int HASH_KEYWORD_SLOW = -17209;
|
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||||
const int HASH_KEYWORD_PROGBOOST = -6353;
|
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
||||||
const int HASH_KEYWORD_EEPROM = -7168;
|
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
||||||
const int HASH_KEYWORD_LIMIT = 27413;
|
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
||||||
const int HASH_KEYWORD_ETHERNET = -30767;
|
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||||
const int HASH_KEYWORD_MAX = 16244;
|
const int16_t HASH_KEYWORD_MAX = 16244;
|
||||||
const int HASH_KEYWORD_MIN = 15978;
|
const int16_t HASH_KEYWORD_MIN = 15978;
|
||||||
|
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||||
|
const int16_t HASH_KEYWORD_RESET = 26133;
|
||||||
|
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||||
|
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||||
|
const int16_t HASH_KEYWORD_RAILCOM = -29097;
|
||||||
|
|
||||||
int DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
|
||||||
Print *DCCEXParser::stashStream = NULL;
|
Print *DCCEXParser::stashStream = NULL;
|
||||||
|
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||||
|
byte DCCEXParser::stashTarget=0;
|
||||||
|
|
||||||
// This is a JMRI command parser, one instance per incoming stream
|
// This is a JMRI command parser, one instance per incoming stream
|
||||||
// It doesnt know how the string got here, nor how it gets back.
|
// It doesnt know how the string got here, nor how it gets back.
|
||||||
@@ -67,7 +75,7 @@ DCCEXParser::DCCEXParser() {}
|
|||||||
void DCCEXParser::flush()
|
void DCCEXParser::flush()
|
||||||
{
|
{
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("\nBuffer flush"));
|
DIAG(F("Buffer flush"));
|
||||||
bufferLength = 0;
|
bufferLength = 0;
|
||||||
inCommandPayload = false;
|
inCommandPayload = false;
|
||||||
}
|
}
|
||||||
@@ -90,7 +98,7 @@ void DCCEXParser::loop(Stream &stream)
|
|||||||
else if (ch == '>')
|
else if (ch == '>')
|
||||||
{
|
{
|
||||||
buffer[bufferLength] = '\0';
|
buffer[bufferLength] = '\0';
|
||||||
parse(&stream, buffer, false); // Parse this allowing async responses
|
parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
|
||||||
inCommandPayload = false;
|
inCommandPayload = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -102,16 +110,16 @@ void DCCEXParser::loop(Stream &stream)
|
|||||||
Sensor::checkAll(&stream); // Update and print changes
|
Sensor::checkAll(&stream); // Update and print changes
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
|
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||||
{
|
{
|
||||||
byte state = 1;
|
byte state = 1;
|
||||||
byte parameterCount = 0;
|
byte parameterCount = 0;
|
||||||
int runningValue = 0;
|
int16_t runningValue = 0;
|
||||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||||
bool signNegative = false;
|
bool signNegative = false;
|
||||||
|
|
||||||
// clear all parameters in case not enough found
|
// clear all parameters in case not enough found
|
||||||
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
|
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||||
result[i] = 0;
|
result[i] = 0;
|
||||||
|
|
||||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||||
@@ -161,15 +169,15 @@ int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
|
|||||||
return parameterCount;
|
return parameterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCEXParser::splitHexValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
|
int16_t DCCEXParser::splitHexValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||||
{
|
{
|
||||||
byte state = 1;
|
byte state = 1;
|
||||||
byte parameterCount = 0;
|
byte parameterCount = 0;
|
||||||
int runningValue = 0;
|
int16_t runningValue = 0;
|
||||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||||
|
|
||||||
// clear all parameters in case not enough found
|
// clear all parameters in case not enough found
|
||||||
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
|
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||||
result[i] = 0;
|
result[i] = 0;
|
||||||
|
|
||||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||||
@@ -242,16 +250,16 @@ void DCCEXParser::parse(const FSH * cmd) {
|
|||||||
int size=strlen_P((char *)cmd)+1;
|
int size=strlen_P((char *)cmd)+1;
|
||||||
char buffer[size];
|
char buffer[size];
|
||||||
strcpy_P(buffer,(char *)cmd);
|
strcpy_P(buffer,(char *)cmd);
|
||||||
parse(&Serial,(byte *)buffer,true);
|
parse(&Serial,(byte *)buffer,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See documentation on DCC class for info on this section
|
// See documentation on DCC class for info on this section
|
||||||
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
{
|
{
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("\nPARSING:%s\n"), com);
|
DIAG(F("PARSING:%s"), com);
|
||||||
int p[MAX_COMMAND_PARAMS];
|
int16_t p[MAX_COMMAND_PARAMS];
|
||||||
while (com[0] == '<' || com[0] == ' ')
|
while (com[0] == '<' || com[0] == ' ')
|
||||||
com++; // strip off any number of < or spaces
|
com++; // strip off any number of < or spaces
|
||||||
byte params = splitValues(p, com);
|
byte params = splitValues(p, com);
|
||||||
@@ -269,9 +277,9 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
return; // filterCallback asked us to ignore
|
return; // filterCallback asked us to ignore
|
||||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||||
{
|
{
|
||||||
int cab;
|
int16_t cab;
|
||||||
int tspeed;
|
int16_t tspeed;
|
||||||
int direction;
|
int16_t direction;
|
||||||
|
|
||||||
if (params == 4)
|
if (params == 4)
|
||||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||||
@@ -305,9 +313,9 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
|
|
||||||
DCC::setThrottle(cab, tspeed, direction);
|
DCC::setThrottle(cab, tspeed, direction);
|
||||||
if (params == 4)
|
if (params == 4)
|
||||||
StringFormatter::send(stream, F("<T %d %d %d>"), p[0], p[2], p[3]);
|
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
||||||
else
|
else
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||||
@@ -374,57 +382,57 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
byte packet[params];
|
byte packet[params];
|
||||||
for (int i=0;i<params;i++) {
|
for (int i=0;i<params;i++) {
|
||||||
packet[i]=(byte)p[i+1];
|
packet[i]=(byte)p[i+1];
|
||||||
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)\n"), i, packet[i], packet[i]);
|
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]);
|
||||||
}
|
}
|
||||||
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
|
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||||
DCC::setLocoId(p[0],callback_Wloco, blocking);
|
DCC::setLocoId(p[0],callback_Wloco);
|
||||||
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||||
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
|
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||||
if (params == 2)
|
if (params == 2)
|
||||||
{ // <V CV VALUE>
|
{ // <V CV VALUE>
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking);
|
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (params == 3)
|
if (params == 3)
|
||||||
{
|
{
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking);
|
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking);
|
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'R': // READ CV ON PROG
|
case 'R': // READ CV ON PROG
|
||||||
if (params == 3)
|
if (params == 3)
|
||||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::readCV(p[0], callback_R, blocking);
|
DCC::readCV(p[0], callback_R);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (params == 0)
|
if (params == 0)
|
||||||
{ // <R> New read loco id
|
{ // <R> New read loco id
|
||||||
if (!stashCallback(stream, p))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::getLocoId(callback_Rloco, blocking);
|
DCC::getLocoId(callback_Rloco);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -436,27 +444,28 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
{
|
{
|
||||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
||||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
||||||
if (params == 0)
|
if (params == 0 ||
|
||||||
|
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
||||||
{
|
{
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
DCCWaveform::progTrack.setPowerMode(mode);
|
||||||
if (mode == POWERMODE::OFF)
|
if (mode == POWERMODE::OFF)
|
||||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||||
StringFormatter::send(stream, F("<p%c>"), opcode);
|
StringFormatter::send(stream, F("<p%c>\n"), opcode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (p[0])
|
switch (p[0])
|
||||||
{
|
{
|
||||||
case HASH_KEYWORD_MAIN:
|
case HASH_KEYWORD_MAIN:
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||||
StringFormatter::send(stream, F("<p%c MAIN>"), opcode);
|
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_PROG:
|
case HASH_KEYWORD_PROG:
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
DCCWaveform::progTrack.setPowerMode(mode);
|
||||||
if (mode == POWERMODE::OFF)
|
if (mode == POWERMODE::OFF)
|
||||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||||
StringFormatter::send(stream, F("<p%c PROG>"), opcode);
|
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
|
||||||
return;
|
return;
|
||||||
case HASH_KEYWORD_JOIN:
|
case HASH_KEYWORD_JOIN:
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||||
@@ -464,21 +473,25 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
if (mode == POWERMODE::ON)
|
if (mode == POWERMODE::ON)
|
||||||
{
|
{
|
||||||
DCC::setProgTrackSyncMain(true);
|
DCC::setProgTrackSyncMain(true);
|
||||||
StringFormatter::send(stream, F("<p1 JOIN>"), opcode);
|
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
StringFormatter::send(stream, F("<p0>"));
|
StringFormatter::send(stream, F("<p0>\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case '!': // ESTOP ALL <!>
|
||||||
|
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||||
|
return;
|
||||||
|
|
||||||
case 'c': // SEND METER RESPONSES <c>
|
case 'c': // SEND METER RESPONSES <c>
|
||||||
// <c MeterName value C/V unit min max res warn>
|
// <c MeterName value C/V unit min max res warn>
|
||||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>"), DCCWaveform::mainTrack.getCurrentmA(),
|
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||||
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'Q': // SENSORS <Q>
|
case 'Q': // SENSORS <Q>
|
||||||
@@ -486,8 +499,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case 's': // <s>
|
case 's': // <s>
|
||||||
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
Turnout::printAll(stream); //send all Turnout states
|
Turnout::printAll(stream); //send all Turnout states
|
||||||
Output::printAll(stream); //send all Output states
|
Output::printAll(stream); //send all Output states
|
||||||
Sensor::printAll(stream); //send all Sensor states
|
Sensor::printAll(stream); //send all Sensor states
|
||||||
@@ -496,12 +509,12 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
|
|
||||||
case 'E': // STORE EPROM <E>
|
case 'E': // STORE EPROM <E>
|
||||||
EEStore::store();
|
EEStore::store();
|
||||||
StringFormatter::send(stream, F("<e %d %d %d>"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'e': // CLEAR EPROM <e>
|
case 'e': // CLEAR EPROM <e>
|
||||||
EEStore::clear();
|
EEStore::clear();
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ' ': // < >
|
case ' ': // < >
|
||||||
@@ -514,7 +527,13 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||||
StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS);
|
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case '-': // Forget Loco <- [cab]>
|
||||||
|
if (params > 1 || p[0]<0) break;
|
||||||
|
if (p[0]==0) DCC::forgetAllLocos();
|
||||||
|
else DCC::forgetLoco(p[0]);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
||||||
@@ -533,18 +552,18 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default: //anything else will diagnose and drop out to <X>
|
default: //anything else will diagnose and drop out to <X>
|
||||||
DIAG(F("\nOpcode=%c params=%d\n"), opcode, params);
|
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||||
for (int i = 0; i < params; i++)
|
for (int i = 0; i < params; i++)
|
||||||
DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]);
|
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} // end of opcode switch
|
} // end of opcode switch
|
||||||
|
|
||||||
// Any fallout here sends an <X>
|
// Any fallout here sends an <X>
|
||||||
StringFormatter::send(stream, F("<X>"));
|
StringFormatter::send(stream, F("<X>\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
|
|
||||||
switch (params)
|
switch (params)
|
||||||
@@ -556,20 +575,20 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
|||||||
if (o == NULL)
|
if (o == NULL)
|
||||||
return false;
|
return false;
|
||||||
o->activate(p[1]);
|
o->activate(p[1]);
|
||||||
StringFormatter::send(stream, F("<Y %d %d>"), p[0], p[1]);
|
StringFormatter::send(stream, F("<Y %d %d>\n"), p[0], p[1]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 3: // <Z ID PIN INVERT>
|
case 3: // <Z ID PIN INVERT>
|
||||||
if (!Output::create(p[0], p[1], p[2], 1))
|
if (!Output::create(p[0], p[1], p[2], 1))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 1: // <Z ID>
|
case 1: // <Z ID>
|
||||||
if (!Output::remove(p[0]))
|
if (!Output::remove(p[0]))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 0: // <Z> list Output definitions
|
case 0: // <Z> list Output definitions
|
||||||
@@ -578,7 +597,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
|||||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||||
{
|
{
|
||||||
gotone = true;
|
gotone = true;
|
||||||
StringFormatter::send(stream, F("<Y %d %d %d %d>"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
|
StringFormatter::send(stream, F("<Y %d %d %d %d>\n"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
|
||||||
}
|
}
|
||||||
return gotone;
|
return gotone;
|
||||||
}
|
}
|
||||||
@@ -588,7 +607,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
//===================================
|
//===================================
|
||||||
bool DCCEXParser::parsef(Print *stream, int params, int p[])
|
bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
// JMRI sends this info in DCC message format but it's not exactly
|
// JMRI sends this info in DCC message format but it's not exactly
|
||||||
// convenient for other processing
|
// convenient for other processing
|
||||||
@@ -620,9 +639,9 @@ bool DCCEXParser::parsef(Print *stream, int params, int p[])
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop)
|
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||||
{
|
{
|
||||||
for (int i = fstart; i <= fstop; i++)
|
for (int16_t i = fstart; i <= fstop; i++)
|
||||||
{
|
{
|
||||||
DCC::setFn(cab, i, value & 1);
|
DCC::setFn(cab, i, value & 1);
|
||||||
value >>= 1;
|
value >>= 1;
|
||||||
@@ -630,7 +649,7 @@ void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//===================================
|
//===================================
|
||||||
bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
switch (params)
|
switch (params)
|
||||||
{
|
{
|
||||||
@@ -640,7 +659,7 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
|||||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||||
{
|
{
|
||||||
gotOne = true;
|
gotOne = true;
|
||||||
StringFormatter::send(stream, F("<H %d %d %d %d>"), tt->data.id, tt->data.address,
|
StringFormatter::send(stream, F("<H %d %d %d %d>\n"), tt->data.id, tt->data.address,
|
||||||
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||||
}
|
}
|
||||||
return gotOne; // will <X> if none found
|
return gotOne; // will <X> if none found
|
||||||
@@ -649,7 +668,7 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
|||||||
case 1: // <T id> delete turnout
|
case 1: // <T id> delete turnout
|
||||||
if (!Turnout::remove(p[0]))
|
if (!Turnout::remove(p[0]))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 2: // <T id 0|1> activate turnout
|
case 2: // <T id 0|1> activate turnout
|
||||||
@@ -658,14 +677,14 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
|||||||
if (!tt)
|
if (!tt)
|
||||||
return false;
|
return false;
|
||||||
tt->activate(p[1]);
|
tt->activate(p[1]);
|
||||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 3: // <T id addr subaddr> define turnout
|
case 3: // <T id addr subaddr> define turnout
|
||||||
if (!Turnout::create(p[0], p[1], p[2]))
|
if (!Turnout::create(p[0], p[1], p[2]))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -673,7 +692,7 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
|
|
||||||
switch (params)
|
switch (params)
|
||||||
@@ -681,13 +700,13 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
|||||||
case 3: // <S id pin pullup> create sensor. pullUp indicator (0=LOW/1=HIGH)
|
case 3: // <S id pin pullup> create sensor. pullUp indicator (0=LOW/1=HIGH)
|
||||||
if (!Sensor::create(p[0], p[1], p[2]))
|
if (!Sensor::create(p[0], p[1], p[2]))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 1: // S id> remove sensor
|
case 1: // S id> remove sensor
|
||||||
if (!Sensor::remove(p[0]))
|
if (!Sensor::remove(p[0]))
|
||||||
return false;
|
return false;
|
||||||
StringFormatter::send(stream, F("<O>"));
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 0: // <S> list sensor definitions
|
case 0: // <S> list sensor definitions
|
||||||
@@ -695,7 +714,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
|||||||
return false;
|
return false;
|
||||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||||
{
|
{
|
||||||
StringFormatter::send(stream, F("<Q %d %d %d>"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
StringFormatter::send(stream, F("<Q %d %d %d>\n"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -705,7 +724,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
if (params == 0)
|
if (params == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -717,23 +736,23 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HASH_KEYWORD_RAM: // <D RAM>
|
case HASH_KEYWORD_RAM: // <D RAM>
|
||||||
StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory());
|
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
|
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
|
||||||
if (params >= 3) {
|
if (params >= 3) {
|
||||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||||
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
|
StringFormatter::send(stream, F("Ack limit=%dmA\n"), p[2]);
|
||||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
||||||
StringFormatter::send(stream, F("\nAck min=%dus\n"), p[2]);
|
StringFormatter::send(stream, F("Ack min=%dus\n"), p[2]);
|
||||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
||||||
StringFormatter::send(stream, F("\nAck max=%dus\n"), p[2]);
|
StringFormatter::send(stream, F("Ack max=%dus\n"), p[2]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
StringFormatter::send(stream, F("\nAck diag %S\n"), onOff ? F("on") : F("off"));
|
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||||
Diag::ACK = onOff;
|
Diag::ACK = onOff;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -753,16 +772,40 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
|||||||
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
|
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
|
||||||
Diag::WITHROTTLE = onOff;
|
Diag::WITHROTTLE = onOff;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_LCN: // <D LCN ON/OFF>
|
||||||
|
Diag::LCN = onOff;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_RAILCOM: // <D RAILCOM ON/OFF>
|
||||||
|
return DCCWaveform::setUseRailcom(onOff);
|
||||||
|
|
||||||
case HASH_KEYWORD_PROGBOOST:
|
case HASH_KEYWORD_PROGBOOST:
|
||||||
DCC::setProgTrackBoost(true);
|
DCC::setProgTrackBoost(true);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_RESET:
|
||||||
|
{
|
||||||
|
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||||
|
delay(50); // wait for the prescaller time to expire
|
||||||
|
break; // and <X> if we didnt restart
|
||||||
|
}
|
||||||
|
|
||||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||||
if (params >= 2)
|
if (params >= 2)
|
||||||
EEStore::dump(p[1]);
|
EEStore::dump(p[1]);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_SPEED28:
|
||||||
|
DCC::setGlobalSpeedsteps(28);
|
||||||
|
StringFormatter::send(stream, F("28 Speedsteps"));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_SPEED128:
|
||||||
|
DCC::setGlobalSpeedsteps(128);
|
||||||
|
StringFormatter::send(stream, F("128 Speedsteps"));
|
||||||
|
return true;
|
||||||
|
|
||||||
default: // invalid/unknown
|
default: // invalid/unknown
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -770,52 +813,70 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CALLBACKS must be static
|
// CALLBACKS must be static
|
||||||
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS])
|
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||||
{
|
{
|
||||||
if (stashBusy )
|
if (stashBusy )
|
||||||
return false;
|
return false;
|
||||||
stashBusy = true;
|
stashBusy = true;
|
||||||
stashStream = stream;
|
stashStream = stream;
|
||||||
|
stashRingStream=ringStream;
|
||||||
|
if (ringStream) stashTarget= ringStream->peekTargetMark();
|
||||||
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
|
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void DCCEXParser::callback_W(int result)
|
|
||||||
{
|
Print * DCCEXParser::getAsyncReplyStream() {
|
||||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
if (stashRingStream) {
|
||||||
stashBusy = false;
|
stashRingStream->mark(stashTarget);
|
||||||
|
return stashRingStream;
|
||||||
|
}
|
||||||
|
return stashStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_B(int result)
|
void DCCEXParser::commitAsyncReplyStream() {
|
||||||
{
|
if (stashRingStream) stashRingStream->commit();
|
||||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
|
stashBusy = false;
|
||||||
stashBusy = false;
|
|
||||||
}
|
|
||||||
void DCCEXParser::callback_Vbit(int result)
|
|
||||||
{
|
|
||||||
StringFormatter::send(stashStream, F("<v %d %d %d>"), stashP[0], stashP[1], result);
|
|
||||||
stashBusy = false;
|
|
||||||
}
|
|
||||||
void DCCEXParser::callback_Vbyte(int result)
|
|
||||||
{
|
|
||||||
StringFormatter::send(stashStream, F("<v %d %d>"), stashP[0], result);
|
|
||||||
stashBusy = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_R(int result)
|
void DCCEXParser::callback_W(int16_t result)
|
||||||
{
|
{
|
||||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
|
StringFormatter::send(getAsyncReplyStream(),
|
||||||
stashBusy = false;
|
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||||
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_Rloco(int result)
|
void DCCEXParser::callback_B(int16_t result)
|
||||||
{
|
{
|
||||||
StringFormatter::send(stashStream, F("<r %d>"), result);
|
StringFormatter::send(getAsyncReplyStream(),
|
||||||
stashBusy = false;
|
F("<r%d|%d|%d %d %d>\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
void DCCEXParser::callback_Vbit(int16_t result)
|
||||||
|
{
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d %d>\n"), stashP[0], stashP[1], result);
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
void DCCEXParser::callback_Vbyte(int16_t result)
|
||||||
|
{
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d>\n"), stashP[0], result);
|
||||||
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_Wloco(int result)
|
void DCCEXParser::callback_R(int16_t result)
|
||||||
|
{
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), F("<r%d|%d|%d %d>\n"), stashP[1], stashP[2], stashP[0], result);
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::callback_Rloco(int16_t result)
|
||||||
|
{
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), F("<r %d>\n"), result);
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::callback_Wloco(int16_t result)
|
||||||
{
|
{
|
||||||
if (result==1) result=stashP[0]; // pick up original requested id from command
|
if (result==1) result=stashP[0]; // pick up original requested id from command
|
||||||
StringFormatter::send(stashStream, F("<w %d>"), result);
|
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
|
||||||
stashBusy = false;
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
@@ -20,15 +20,16 @@
|
|||||||
#define DCCEXParser_h
|
#define DCCEXParser_h
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
#include "RingStream.h"
|
||||||
|
|
||||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||||
|
|
||||||
struct DCCEXParser
|
struct DCCEXParser
|
||||||
{
|
{
|
||||||
DCCEXParser();
|
DCCEXParser();
|
||||||
void loop(Stream & stream);
|
void loop(Stream & stream);
|
||||||
void parse(Print * stream, byte * command, bool blocking);
|
void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||||
void parse(const FSH * cmd);
|
void parse(const FSH * cmd);
|
||||||
void flush();
|
void flush();
|
||||||
static void setFilter(FILTER_CALLBACK filter);
|
static void setFilter(FILTER_CALLBACK filter);
|
||||||
@@ -38,36 +39,40 @@ struct DCCEXParser
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static const int MAX_BUFFER=50; // longest command sent in
|
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||||
byte bufferLength=0;
|
byte bufferLength=0;
|
||||||
bool inCommandPayload=false;
|
bool inCommandPayload=false;
|
||||||
byte buffer[MAX_BUFFER+2];
|
byte buffer[MAX_BUFFER+2];
|
||||||
int splitValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
int splitHexValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
|
|
||||||
bool parseT(Print * stream, int params, int p[]);
|
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseZ(Print * stream, int params, int p[]);
|
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseS(Print * stream, int params, int p[]);
|
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parsef(Print * stream, int params, int p[]);
|
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseD(Print * stream, int params, int p[]);
|
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
|
||||||
|
static Print * getAsyncReplyStream();
|
||||||
|
static void commitAsyncReplyStream();
|
||||||
|
|
||||||
|
|
||||||
static bool stashBusy;
|
static bool stashBusy;
|
||||||
|
static byte stashTarget;
|
||||||
static Print * stashStream;
|
static Print * stashStream;
|
||||||
static int stashP[MAX_COMMAND_PARAMS];
|
static RingStream * stashRingStream;
|
||||||
bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS]);
|
|
||||||
static void callback_W(int result);
|
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||||
static void callback_B(int result);
|
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||||
static void callback_R(int result);
|
static void callback_W(int16_t result);
|
||||||
static void callback_Rloco(int result);
|
static void callback_B(int16_t result);
|
||||||
static void callback_Wloco(int result);
|
static void callback_R(int16_t result);
|
||||||
static void callback_Vbit(int result);
|
static void callback_Rloco(int16_t result);
|
||||||
static void callback_Vbyte(int result);
|
static void callback_Wloco(int16_t result);
|
||||||
|
static void callback_Vbit(int16_t result);
|
||||||
|
static void callback_Vbyte(int16_t result);
|
||||||
static FILTER_CALLBACK filterCallback;
|
static FILTER_CALLBACK filterCallback;
|
||||||
static FILTER_CALLBACK filterRMFTCallback;
|
static FILTER_CALLBACK filterRMFTCallback;
|
||||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||||
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
243
DCCTimer.cpp
243
DCCTimer.cpp
@@ -25,6 +25,21 @@
|
|||||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||||
* The DCCWaveform class generates the signals to the motor shield
|
* The DCCWaveform class generates the signals to the motor shield
|
||||||
* based on this timer.
|
* based on this timer.
|
||||||
|
*
|
||||||
|
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||||
|
* (see isPWMPin() function. )
|
||||||
|
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||||
|
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||||
|
* the required pin state. (see setPWM())
|
||||||
|
* This is more accurate than the software interrupt but at the expense of
|
||||||
|
* limiting the choice of available pins.
|
||||||
|
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||||
|
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||||
|
*
|
||||||
|
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||||
|
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||||
|
* problems for <1 JOIN> etc.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
@@ -35,13 +50,14 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
|||||||
|
|
||||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||||
// Arduino unoWifi Rev2 and nanoEvery architectire
|
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||||
TCB0.CNT = 0;
|
TCB0.CNT = 0;
|
||||||
@@ -55,30 +71,247 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
|||||||
interruptHandler();
|
interruptHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
memcpy(mac,&SIGROW.SERNUM0,6); // serial number
|
(void) pin;
|
||||||
|
return false; // TODO what are the relevant pins?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
|
(void) pin;
|
||||||
|
return false; // TODO what are the relevant pins?
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
(void) pin;
|
||||||
|
(void) high;
|
||||||
|
// TODO what are the relevant pins?
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
|
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||||
|
mac[0] &= 0xFE;
|
||||||
|
mac[0] |= 0x02;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(TEENSYDUINO)
|
||||||
|
IntervalTimer myDCCTimer;
|
||||||
|
bool interruptFlipflop=false;
|
||||||
|
byte railcomPin[2]={0,0];
|
||||||
|
enum RAILCOM_NEXT:byte {SKIP,CUT_OUT,CUT_IN);
|
||||||
|
RAILCOM_NEXT railcom1Next[]={SKIP,SKIP};
|
||||||
|
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
interruptHandler=callback;
|
||||||
|
myDCCTimer.begin(interruptFast, DCC_SIGNAL_TIME/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This interrupt happens every 29uS, and alternately calls the DCC waveform
|
||||||
|
// or handles any pending Railcom cutout pins.
|
||||||
|
void interruptFast() {
|
||||||
|
nterruptFlipflop=!interruptFlipflop;
|
||||||
|
if (interruptFiliflop) {
|
||||||
|
interruptHandler();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Railcom interrupt, half way between DCC interruots
|
||||||
|
for (byte channel=0;channel<2;channel++) {
|
||||||
|
byte pin=railcomPin[channel;
|
||||||
|
if (pin) {
|
||||||
|
switch (railcomNext[channel]) {
|
||||||
|
case CUT_OUT:
|
||||||
|
digitalWrite(pin,HIGH);
|
||||||
|
break;
|
||||||
|
case CUT_IN:
|
||||||
|
digitalWrite(pin,HIGH);
|
||||||
|
break;
|
||||||
|
case IGNORE: break;
|
||||||
|
}
|
||||||
|
railcomNext[channel]=IGNORE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
|
(void) pin;
|
||||||
|
return true; // We are so fast we can pretend we do support this
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DCCTimer::isRailcomPin(byte pin) {
|
||||||
|
(void) pin;
|
||||||
|
if (railcomPin[0]==0) railcomPin[0]=pin;
|
||||||
|
else if (railcomPin[1]==0) railcomPin[1]=pin;
|
||||||
|
else return false;
|
||||||
|
return true; // We are so fast we can pretend we do support this
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
// setting pwm on a railcom pin is deferred to the next railcom interruyupt.
|
||||||
|
for (byte channel=0;channel<2;channel++) {
|
||||||
|
if (pin==railcomPin[channel]) {
|
||||||
|
railcomNext[channel]=high?CUT_OUT:CUT_IN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
digitalWrite(pin,high?HIGH:LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
|
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||||
|
uint32_t m1 = HW_OCOTP_MAC1;
|
||||||
|
uint32_t m2 = HW_OCOTP_MAC0;
|
||||||
|
mac[0] = m1 >> 8;
|
||||||
|
mac[1] = m1 >> 0;
|
||||||
|
mac[2] = m2 >> 24;
|
||||||
|
mac[3] = m2 >> 16;
|
||||||
|
mac[4] = m2 >> 8;
|
||||||
|
mac[5] = m2 >> 0;
|
||||||
|
#else
|
||||||
|
read_mac(mac);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(__IMXRT1062__)
|
||||||
|
void DCCTimer::read_mac(byte mac[6]) {
|
||||||
|
read(0xe,mac,0);
|
||||||
|
read(0xf,mac,3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||||
|
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||||
|
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||||
|
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||||
|
|
||||||
|
// launch command and wait until complete
|
||||||
|
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||||
|
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||||
|
|
||||||
|
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||||
|
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||||
|
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Arduino nano, uno, mega etc
|
// Arduino nano, uno, mega etc
|
||||||
|
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||||
|
#define TIMER1_A_PIN 11
|
||||||
|
#define TIMER1_B_PIN 12
|
||||||
|
#define TIMER1_C_PIN 13
|
||||||
|
//railcom timer facility
|
||||||
|
#define TIMER4_A_PIN 6
|
||||||
|
#define TIMER4_B_PIN 7
|
||||||
|
#define TIMER4_C_PIN 8
|
||||||
|
#else
|
||||||
|
#define TIMER1_A_PIN 9
|
||||||
|
#define TIMER1_B_PIN 10
|
||||||
|
#endif
|
||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
ICR1 = CLOCK_CYCLES;
|
ICR1 = CLOCK_CYCLES;
|
||||||
TCNT1 = 0;
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
|
TCNT1 = 0;
|
||||||
|
|
||||||
|
#if defined(TIMER4_A_PIN)
|
||||||
|
//railcom timer facility
|
||||||
|
TCCR4A = 0;
|
||||||
|
ICR4 = CLOCK_CYCLES;
|
||||||
|
TCCR4B = _BV(WGM43) | _BV(CS40); // Mode 8, clock select 1
|
||||||
|
TIMSK4 = 0; // Disable Software interrupt
|
||||||
|
delayMicroseconds(DCC_SIGNAL_TIME/2);
|
||||||
|
TCNT4 = 0; // this timer fires half cycle after Timer 1 (no idea why /4 !)
|
||||||
|
#endif
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISR called by timer interrupt every 58uS
|
// ISR called by timer interrupt every 58uS
|
||||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||||
|
|
||||||
|
// Alternative pin manipulation via PWM control.
|
||||||
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
|
return pin==TIMER1_A_PIN
|
||||||
|
|| pin==TIMER1_B_PIN
|
||||||
|
#ifdef TIMER1_C_PIN
|
||||||
|
|| pin==TIMER1_C_PIN
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
// Alternative pin manipulation via PWM control.
|
||||||
|
bool DCCTimer::isRailcomPin(byte pin) {
|
||||||
|
return
|
||||||
|
#ifdef TIMER4_A_PIN
|
||||||
|
pin==TIMER4_A_PIN ||
|
||||||
|
pin==TIMER4_B_PIN ||
|
||||||
|
pin==TIMER4_C_PIN ||
|
||||||
|
#endif
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::onoffPWM(byte pin, bool on) {
|
||||||
|
if (pin==TIMER1_A_PIN) {
|
||||||
|
if (on)
|
||||||
|
TCCR1A |= _BV(COM1A1);
|
||||||
|
else
|
||||||
|
TCCR1A &= ~(_BV(COM1A1));
|
||||||
|
}
|
||||||
|
else if (pin==TIMER1_B_PIN) {
|
||||||
|
if (on)
|
||||||
|
TCCR1A |= _BV(COM1B1);
|
||||||
|
else
|
||||||
|
TCCR1A &= ~(_BV(COM1B1));
|
||||||
|
}
|
||||||
|
#ifdef TIMER1_C_PIN
|
||||||
|
else if (pin==TIMER1_C_PIN) {
|
||||||
|
if (on)
|
||||||
|
TCCR1A |= _BV(COM1C1);
|
||||||
|
else
|
||||||
|
TCCR1A &= ~(_BV(COM1C1));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
uint16_t val=high?1024:0;
|
||||||
|
if (pin==TIMER1_A_PIN) {
|
||||||
|
OCR1A= val;
|
||||||
|
}
|
||||||
|
else if (pin==TIMER1_B_PIN) {
|
||||||
|
OCR1B= val;
|
||||||
|
}
|
||||||
|
#ifdef TIMER1_C_PIN
|
||||||
|
else if (pin==TIMER1_C_PIN) {
|
||||||
|
OCR1C= val;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef TIMER4_A_PIN
|
||||||
|
else if (pin==TIMER4_A_PIN) {
|
||||||
|
TCCR4A |= _BV(COM4A1);
|
||||||
|
OCR4A= val;
|
||||||
|
}
|
||||||
|
else if (pin==TIMER4_B_PIN) {
|
||||||
|
TCCR4A |= _BV(COM4B1);
|
||||||
|
OCR4B= val;
|
||||||
|
}
|
||||||
|
else if (pin==TIMER4_C_PIN) {
|
||||||
|
TCCR4A |= _BV(COM4C1);
|
||||||
|
OCR4C= val;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#include <avr/boot.h>
|
#include <avr/boot.h>
|
||||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
for (byte i=0; i<6; i++) mac[i]=boot_signature_byte_get(0x0E + i);
|
for (byte i=0; i<6; i++) {
|
||||||
|
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||||
|
}
|
||||||
|
mac[0] &= 0xFE;
|
||||||
|
mac[0] |= 0x02;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -8,6 +8,14 @@ class DCCTimer {
|
|||||||
public:
|
public:
|
||||||
static void begin(INTERRUPT_CALLBACK interrupt);
|
static void begin(INTERRUPT_CALLBACK interrupt);
|
||||||
static void getSimulatedMacAddress(byte mac[6]);
|
static void getSimulatedMacAddress(byte mac[6]);
|
||||||
|
static bool isPWMPin(byte pin);
|
||||||
|
static bool isRailcomPin(byte pin);
|
||||||
|
static void setPWM(byte pin, bool high);
|
||||||
|
static void onoffPWM(byte pin, bool on);
|
||||||
|
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||||
|
static void read_mac(byte mac[6]);
|
||||||
|
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||||
|
#endif
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
138
DCCWaveform.cpp
138
DCCWaveform.cpp
@@ -23,28 +23,49 @@
|
|||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include "freeMemory.h"
|
||||||
|
|
||||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
|
|
||||||
|
bool DCCWaveform::useRailcom=false;
|
||||||
|
bool DCCWaveform::supportsRailcom=false;
|
||||||
bool DCCWaveform::progTrackSyncMain=false;
|
bool DCCWaveform::progTrackSyncMain=false;
|
||||||
bool DCCWaveform::progTrackBoosted=false;
|
bool DCCWaveform::progTrackBoosted=false;
|
||||||
int DCCWaveform::progTripValue=0;
|
int DCCWaveform::progTripValue=0;
|
||||||
|
volatile uint8_t DCCWaveform::numAckGaps=0;
|
||||||
|
volatile uint8_t DCCWaveform::numAckSamples=0;
|
||||||
|
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
mainTrack.motorDriver=mainDriver;
|
mainTrack.motorDriver=mainDriver;
|
||||||
progTrack.motorDriver=progDriver;
|
progTrack.motorDriver=progDriver;
|
||||||
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
progTrack.setPowerMode(POWERMODE::OFF);
|
progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
// Fault pin config for odd motor boards (example pololu)
|
||||||
|
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||||
|
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||||
|
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||||
|
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||||
|
supportsRailcom= MotorDriver::usePWM && mainDriver->isRailcomCapable() && progDriver->isRailcomCapable();
|
||||||
|
|
||||||
|
// supportsRailcom depends on hardware caopability
|
||||||
|
// useRailcom is user switchable at run time.
|
||||||
|
useRailcom=supportsRailcom;
|
||||||
|
|
||||||
|
if (MotorDriver::usePWM){
|
||||||
|
DIAG(F("Signal pin config: high accuracy waveform"));
|
||||||
|
if (supportsRailcom) DIAG(F("Railcom cutout enabled"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DIAG(F("Signal pin config: normal accuracy waveform"));
|
||||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::loop() {
|
void DCCWaveform::loop(bool ackManagerActive) {
|
||||||
mainTrack.checkPowerOverload();
|
mainTrack.checkPowerOverload(false);
|
||||||
progTrack.checkPowerOverload();
|
progTrack.checkPowerOverload(ackManagerActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
@@ -62,8 +83,16 @@ void DCCWaveform::interruptHandler() {
|
|||||||
progTrack.state=stateTransform[progTrack.state];
|
progTrack.state=stateTransform[progTrack.state];
|
||||||
|
|
||||||
|
|
||||||
|
// WAVE_START is at start of bit where we need to find
|
||||||
|
// out if this is an railcom start or stop time
|
||||||
|
if (useRailcom) {
|
||||||
|
if (mainTrack.state==WAVE_START) mainTrack.railcom2();
|
||||||
|
if (progTrack.state==WAVE_START) progTrack.railcom2();
|
||||||
|
}
|
||||||
|
|
||||||
// WAVE_PENDING means we dont yet know what the next bit is
|
// WAVE_PENDING means we dont yet know what the next bit is
|
||||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
// so call interrupt2 to set it
|
||||||
|
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||||
else if (progTrack.ackPending) progTrack.checkAck();
|
else if (progTrack.ackPending) progTrack.checkAck();
|
||||||
|
|
||||||
@@ -105,12 +134,22 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
|
|||||||
motorDriver->setPower( ison);
|
motorDriver->setPower( ison);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DCCWaveform::setUseRailcom(bool on) {
|
||||||
|
if (!supportsRailcom) return false;
|
||||||
|
useRailcom=on;
|
||||||
|
if (!on) {
|
||||||
|
// turn off any existing cutout
|
||||||
|
mainTrack.motorDriver->setRailcomCutout(false);
|
||||||
|
progTrack.motorDriver->setRailcomCutout(false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void DCCWaveform::checkPowerOverload() {
|
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||||
lastSampleTaken = millis();
|
lastSampleTaken = millis();
|
||||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||||
if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
|
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
|
||||||
tripValue=progTripValue;
|
tripValue=progTripValue;
|
||||||
|
|
||||||
switch (powerMode) {
|
switch (powerMode) {
|
||||||
@@ -122,10 +161,23 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
lastCurrent=motorDriver->getCurrentRaw();
|
lastCurrent=motorDriver->getCurrentRaw();
|
||||||
if (lastCurrent < 0) {
|
if (lastCurrent < 0) {
|
||||||
// We have a fault pin condition to take care of
|
// We have a fault pin condition to take care of
|
||||||
DIAG(F("\n*** %S FAULT PIN ACTIVE TOGGLE POWER ON THIS OR BOTH TRACKS ***\n"), isMainTrack ? F("MAIN") : F("PROG"));
|
|
||||||
lastCurrent = -lastCurrent;
|
lastCurrent = -lastCurrent;
|
||||||
|
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||||
|
if (MotorDriver::commonFaultPin) {
|
||||||
|
if (lastCurrent <= tripValue) {
|
||||||
|
setPowerMode(POWERMODE::ON); // maybe other track
|
||||||
|
}
|
||||||
|
// Write this after the fact as we want to turn on as fast as possible
|
||||||
|
// because we don't know which output actually triggered the fault pin
|
||||||
|
DIAG(F("*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||||
|
} else {
|
||||||
|
DIAG(F("*** %S FAULT PIN ACTIVE - OVERLOAD ***"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||||
|
if (lastCurrent < tripValue) {
|
||||||
|
lastCurrent = tripValue; // exaggerate
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (lastCurrent <= tripValue) {
|
if (lastCurrent < tripValue) {
|
||||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||||
if(power_good_counter<100)
|
if(power_good_counter<100)
|
||||||
power_good_counter++;
|
power_good_counter++;
|
||||||
@@ -137,7 +189,7 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||||
power_good_counter=0;
|
power_good_counter=0;
|
||||||
sampleDelay = power_sample_overload_wait;
|
sampleDelay = power_sample_overload_wait;
|
||||||
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
|
DIAG(F("*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
|
||||||
if (power_sample_overload_wait >= 10000)
|
if (power_sample_overload_wait >= 10000)
|
||||||
power_sample_overload_wait = 10000;
|
power_sample_overload_wait = 10000;
|
||||||
else
|
else
|
||||||
@@ -149,7 +201,7 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
setPowerMode(POWERMODE::ON);
|
setPowerMode(POWERMODE::ON);
|
||||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||||
// Debug code....
|
// Debug code....
|
||||||
DIAG(F("\n*** %S TRACK POWER RESET delay=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay);
|
DIAG(F("*** %S TRACK POWER RESET delay=%d ***"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||||
@@ -172,7 +224,27 @@ const bool DCCWaveform::signalTransform[]={
|
|||||||
/* WAVE_MID_0 -> */ LOW,
|
/* WAVE_MID_0 -> */ LOW,
|
||||||
/* WAVE_LOW_0 -> */ LOW,
|
/* WAVE_LOW_0 -> */ LOW,
|
||||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||||
|
|
||||||
|
void DCCWaveform::railcom2() {
|
||||||
|
bool cutout;
|
||||||
|
if (remainingPreambles==(requiredPreambles-2)) {
|
||||||
|
cutout=true;
|
||||||
|
} else if (remainingPreambles==(requiredPreambles-6)) {
|
||||||
|
cutout=false;
|
||||||
|
} else {
|
||||||
|
return; // neiter start or end of cutout, do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMainTrack) {
|
||||||
|
if (progTrackSyncMain) // we are main track and synced so we take care of prog track as well
|
||||||
|
progTrack.motorDriver->setRailcomCutout(cutout);
|
||||||
|
mainTrack.motorDriver->setRailcomCutout(cutout);
|
||||||
|
} else {
|
||||||
|
if (!progTrackSyncMain) // we are prog track and not synced so we take care of ourselves
|
||||||
|
progTrack.motorDriver->setRailcomCutout(cutout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DCCWaveform::interrupt2() {
|
void DCCWaveform::interrupt2() {
|
||||||
// calculate the next bit to be sent:
|
// calculate the next bit to be sent:
|
||||||
// set state WAVE_MID_1 for a 1=bit
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
@@ -181,6 +253,12 @@ void DCCWaveform::interrupt2() {
|
|||||||
if (remainingPreambles > 0 ) {
|
if (remainingPreambles > 0 ) {
|
||||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||||
remainingPreambles--;
|
remainingPreambles--;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Don't need to do that more than once per packet
|
||||||
|
if (remainingPreambles == 3)
|
||||||
|
updateMinimumFreeMemory(22);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +308,7 @@ void DCCWaveform::interrupt2() {
|
|||||||
|
|
||||||
// Wait until there is no packet pending, then make this pending
|
// Wait until there is no packet pending, then make this pending
|
||||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||||
if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
|
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||||
while (packetPending);
|
while (packetPending);
|
||||||
|
|
||||||
byte checksum = 0;
|
byte checksum = 0;
|
||||||
@@ -238,6 +316,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||||||
checksum ^= buffer[b];
|
checksum ^= buffer[b];
|
||||||
pendingPacket[b] = buffer[b];
|
pendingPacket[b] = buffer[b];
|
||||||
}
|
}
|
||||||
|
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||||
pendingPacket[byteCount] = checksum;
|
pendingPacket[byteCount] = checksum;
|
||||||
pendingLength = byteCount + 1;
|
pendingLength = byteCount + 1;
|
||||||
pendingRepeats = repeats;
|
pendingRepeats = repeats;
|
||||||
@@ -252,7 +331,7 @@ void DCCWaveform::setAckBaseline() {
|
|||||||
if (isMainTrack) return;
|
if (isMainTrack) return;
|
||||||
int baseline=motorDriver->getCurrentRaw();
|
int baseline=motorDriver->getCurrentRaw();
|
||||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||||
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
|
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %dus and %dus"),
|
||||||
baseline,motorDriver->raw2mA(baseline),
|
baseline,motorDriver->raw2mA(baseline),
|
||||||
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
||||||
minAckPulseDuration, maxAckPulseDuration);
|
minAckPulseDuration, maxAckPulseDuration);
|
||||||
@@ -265,13 +344,15 @@ void DCCWaveform::setAckPending() {
|
|||||||
ackPulseDuration=0;
|
ackPulseDuration=0;
|
||||||
ackDetected=false;
|
ackDetected=false;
|
||||||
ackCheckStart=millis();
|
ackCheckStart=millis();
|
||||||
|
numAckSamples=0;
|
||||||
|
numAckGaps=0;
|
||||||
ackPending=true; // interrupt routines will now take note
|
ackPending=true; // interrupt routines will now take note
|
||||||
}
|
}
|
||||||
|
|
||||||
byte DCCWaveform::getAck() {
|
byte DCCWaveform::getAck() {
|
||||||
if (ackPending) return (2); // still waiting
|
if (ackPending) return (2); // still waiting
|
||||||
if (Diag::ACK) DIAG(F("\n%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
|
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||||
if (ackDetected) return (1); // Yes we had an ack
|
if (ackDetected) return (1); // Yes we had an ack
|
||||||
return(0); // pending set off but not detected means no ACK.
|
return(0); // pending set off but not detected means no ACK.
|
||||||
}
|
}
|
||||||
@@ -285,10 +366,15 @@ void DCCWaveform::checkAck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int current=motorDriver->getCurrentRaw();
|
int current=motorDriver->getCurrentRaw();
|
||||||
|
numAckSamples++;
|
||||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||||
|
|
||||||
if (current>ackThreshold) {
|
if (current>ackThreshold) {
|
||||||
|
if (trailingEdgeCounter > 0) {
|
||||||
|
numAckGaps++;
|
||||||
|
trailingEdgeCounter = 0;
|
||||||
|
}
|
||||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -296,9 +382,21 @@ void DCCWaveform::checkAck() {
|
|||||||
// not in pulse
|
// not in pulse
|
||||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||||
|
|
||||||
|
// if we reach to this point, we have
|
||||||
// detected trailing edge of pulse
|
// detected trailing edge of pulse
|
||||||
ackPulseDuration=micros()-ackPulseStart;
|
if (trailingEdgeCounter == 0) {
|
||||||
|
ackPulseDuration=micros()-ackPulseStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// but we do not trust it yet and return (which will force another
|
||||||
|
// measurement) and first the third time around with low current
|
||||||
|
// the ack detection will be finalized.
|
||||||
|
if (trailingEdgeCounter < 2) {
|
||||||
|
trailingEdgeCounter++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trailingEdgeCounter = 0;
|
||||||
|
|
||||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||||
ackCheckDuration=millis()-ackCheckStart;
|
ackCheckDuration=millis()-ackCheckStart;
|
||||||
ackDetected=true;
|
ackDetected=true;
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef DCCWaveform_h
|
#ifndef DCCWaveform_h
|
||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
|
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
|
|
||||||
// Wait times for power management. Unit: milliseconds
|
// Wait times for power management. Unit: milliseconds
|
||||||
@@ -29,7 +30,7 @@ const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
|||||||
// Number of preamble bits.
|
// Number of preamble bits.
|
||||||
const int PREAMBLE_BITS_MAIN = 16;
|
const int PREAMBLE_BITS_MAIN = 16;
|
||||||
const int PREAMBLE_BITS_PROG = 22;
|
const int PREAMBLE_BITS_PROG = 22;
|
||||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard exrtended packets
|
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||||
|
|
||||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||||
// to the transform array.
|
// to the transform array.
|
||||||
@@ -49,14 +50,18 @@ class DCCWaveform {
|
|||||||
public:
|
public:
|
||||||
DCCWaveform( byte preambleBits, bool isMain);
|
DCCWaveform( byte preambleBits, bool isMain);
|
||||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||||
static void loop();
|
static void loop(bool ackManagerActive);
|
||||||
static DCCWaveform mainTrack;
|
static DCCWaveform mainTrack;
|
||||||
static DCCWaveform progTrack;
|
static DCCWaveform progTrack;
|
||||||
|
static bool supportsRailcom;
|
||||||
|
static bool useRailcom;
|
||||||
|
|
||||||
void beginTrack();
|
void beginTrack();
|
||||||
void setPowerMode(POWERMODE);
|
void setPowerMode(POWERMODE);
|
||||||
POWERMODE getPowerMode();
|
POWERMODE getPowerMode();
|
||||||
void checkPowerOverload();
|
static bool setUseRailcom(bool on);
|
||||||
|
|
||||||
|
void checkPowerOverload(bool ackManagerActive);
|
||||||
inline int get1024Current() {
|
inline int get1024Current() {
|
||||||
if (powerMode == POWERMODE::ON)
|
if (powerMode == POWERMODE::ON)
|
||||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||||
@@ -94,6 +99,9 @@ class DCCWaveform {
|
|||||||
autoPowerOff=false;
|
autoPowerOff=false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
inline bool canMeasureCurrent() {
|
||||||
|
return motorDriver->canMeasureCurrent();
|
||||||
|
};
|
||||||
inline void setAckLimit(int mA) {
|
inline void setAckLimit(int mA) {
|
||||||
ackLimitmA = mA;
|
ackLimitmA = mA;
|
||||||
}
|
}
|
||||||
@@ -114,12 +122,13 @@ class DCCWaveform {
|
|||||||
|
|
||||||
static void interruptHandler();
|
static void interruptHandler();
|
||||||
void interrupt2();
|
void interrupt2();
|
||||||
|
void railcom2();
|
||||||
void checkAck();
|
void checkAck();
|
||||||
|
|
||||||
bool isMainTrack;
|
bool isMainTrack;
|
||||||
MotorDriver* motorDriver;
|
MotorDriver* motorDriver;
|
||||||
// Transmission controller
|
// Transmission controller
|
||||||
byte transmitPacket[MAX_PACKET_SIZE]; // packet being transmitted
|
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte transmitLength;
|
byte transmitLength;
|
||||||
byte transmitRepeats; // remaining repeats of transmission
|
byte transmitRepeats; // remaining repeats of transmission
|
||||||
byte remainingPreambles;
|
byte remainingPreambles;
|
||||||
@@ -127,7 +136,7 @@ class DCCWaveform {
|
|||||||
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
|
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
|
||||||
byte bytes_sent; // number of bytes sent from transmitPacket
|
byte bytes_sent; // number of bytes sent from transmitPacket
|
||||||
WAVE_STATE state; // wave generator state machine
|
WAVE_STATE state; // wave generator state machine
|
||||||
byte pendingPacket[MAX_PACKET_SIZE];
|
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
byte pendingRepeats;
|
||||||
int lastCurrent;
|
int lastCurrent;
|
||||||
@@ -157,8 +166,11 @@ class DCCWaveform {
|
|||||||
unsigned int ackPulseDuration; // micros
|
unsigned int ackPulseDuration; // micros
|
||||||
unsigned long ackPulseStart; // micros
|
unsigned long ackPulseStart; // micros
|
||||||
|
|
||||||
unsigned int minAckPulseDuration = 2000; // micros
|
unsigned int minAckPulseDuration = 4000; // micros
|
||||||
unsigned int maxAckPulseDuration = 8500; // micros
|
unsigned int maxAckPulseDuration = 8500; // micros
|
||||||
|
|
||||||
|
volatile static uint8_t numAckGaps;
|
||||||
|
volatile static uint8_t numAckSamples;
|
||||||
|
static uint8_t trailingEdgeCounter;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -94,10 +94,10 @@ int EEStore::pointer(){
|
|||||||
|
|
||||||
void EEStore::dump(int num) {
|
void EEStore::dump(int num) {
|
||||||
byte b;
|
byte b;
|
||||||
DIAG(F("\nAddr 0x char\n"));
|
DIAG(F("Addr 0x char"));
|
||||||
for (int n=0 ; n<num; n++) {
|
for (int n=0 ; n<num; n++) {
|
||||||
EEPROM.get(n, b);
|
EEPROM.get(n, b);
|
||||||
DIAG(F("%d %x %c\n"),n,b,isprint(b) ? b : ' ');
|
DIAG(F("%d %x %c"),n,b,isprint(b) ? b : ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@@ -52,12 +52,6 @@ EthernetInterface::EthernetInterface()
|
|||||||
{
|
{
|
||||||
byte mac[6];
|
byte mac[6];
|
||||||
DCCTimer::getSimulatedMacAddress(mac);
|
DCCTimer::getSimulatedMacAddress(mac);
|
||||||
DIAG(F("\n+++++ Ethernet Setup. Simulatd mac="));
|
|
||||||
for (byte i=0;i<sizeof(mac); i++) {
|
|
||||||
DIAG(F("%x:"),mac[i]);
|
|
||||||
}
|
|
||||||
DIAG(F("\n"));
|
|
||||||
|
|
||||||
connected=false;
|
connected=false;
|
||||||
|
|
||||||
#ifdef IP_ADDRESS
|
#ifdef IP_ADDRESS
|
||||||
@@ -65,17 +59,17 @@ EthernetInterface::EthernetInterface()
|
|||||||
#else
|
#else
|
||||||
if (Ethernet.begin(mac) == 0)
|
if (Ethernet.begin(mac) == 0)
|
||||||
{
|
{
|
||||||
DIAG(F("begin FAILED\n"));
|
DIAG(F("Ethernet.begin FAILED"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
DIAG(F("begin OK."));
|
DIAG(F("begin OK."));
|
||||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||||
DIAG(F("shield not found\n"));
|
DIAG(F("Ethernet shield not found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Ethernet.linkStatus() == LinkOFF) {
|
if (Ethernet.linkStatus() == LinkOFF) {
|
||||||
DIAG(F("cable not connected\n"));
|
DIAG(F("Ethernet cable not connected"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +77,11 @@ EthernetInterface::EthernetInterface()
|
|||||||
|
|
||||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||||
|
|
||||||
server = new EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
|
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||||
server->begin();
|
server->begin();
|
||||||
|
|
||||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||||
LCD(5,F("Port:%d"), LISTEN_PORT);
|
LCD(5,F("Port:%d"), IP_PORT);
|
||||||
|
|
||||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||||
}
|
}
|
||||||
@@ -104,13 +98,13 @@ void EthernetInterface::loop()
|
|||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
//renewed fail
|
//renewed fail
|
||||||
DIAG(F("\nEthernet Error: renewed fail\n"));
|
DIAG(F("Ethernet Error: renewed fail"));
|
||||||
singleton=NULL;
|
singleton=NULL;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
//rebind fail
|
//rebind fail
|
||||||
DIAG(F("Ethernet Error: rebind fail\n"));
|
DIAG(F("Ethernet Error: rebind fail"));
|
||||||
singleton=NULL;
|
singleton=NULL;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -131,7 +125,7 @@ void EthernetInterface::loop()
|
|||||||
// check for new client
|
// check for new client
|
||||||
if (client)
|
if (client)
|
||||||
{
|
{
|
||||||
if (Diag::ETHERNET) DIAG(F("\nEthernet: New client "));
|
if (Diag::ETHERNET) DIAG(F("Ethernet: New client "));
|
||||||
byte socket;
|
byte socket;
|
||||||
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
|
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||||
{
|
{
|
||||||
@@ -139,12 +133,12 @@ void EthernetInterface::loop()
|
|||||||
{
|
{
|
||||||
// On accept() the EthernetServer doesn't track the client anymore
|
// On accept() the EthernetServer doesn't track the client anymore
|
||||||
// so we store it in our client array
|
// so we store it in our client array
|
||||||
if (Diag::ETHERNET) DIAG(F("%d\n"),socket);
|
if (Diag::ETHERNET) DIAG(F("Socket %d"),socket);
|
||||||
clients[socket] = client;
|
clients[socket] = client;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW\n"));
|
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for incoming data from all possible clients
|
// check for incoming data from all possible clients
|
||||||
@@ -154,11 +148,11 @@ void EthernetInterface::loop()
|
|||||||
|
|
||||||
int available=clients[socket].available();
|
int available=clients[socket].available();
|
||||||
if (available > 0) {
|
if (available > 0) {
|
||||||
if (Diag::ETHERNET) DIAG(F("\nEthernet: available socket=%d,avail=%d,count="), socket, available);
|
if (Diag::ETHERNET) DIAG(F("Ethernet: available socket=%d,avail=%d"), socket, available);
|
||||||
// read bytes from a client
|
// read bytes from a client
|
||||||
int count = clients[socket].read(buffer, MAX_ETH_BUFFER);
|
int count = clients[socket].read(buffer, MAX_ETH_BUFFER);
|
||||||
buffer[count] = '\0'; // terminate the string properly
|
buffer[count] = '\0'; // terminate the string properly
|
||||||
if (Diag::ETHERNET) DIAG(F("%d:%e\n"), socket,buffer);
|
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
|
||||||
// execute with data going directly back
|
// execute with data going directly back
|
||||||
outboundRing->mark(socket);
|
outboundRing->mark(socket);
|
||||||
CommandDistributor::parse(socket,buffer,outboundRing);
|
CommandDistributor::parse(socket,buffer,outboundRing);
|
||||||
@@ -172,7 +166,7 @@ void EthernetInterface::loop()
|
|||||||
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||||
if (clients[socket] && !clients[socket].connected()) {
|
if (clients[socket] && !clients[socket].connected()) {
|
||||||
clients[socket].stop();
|
clients[socket].stop();
|
||||||
if (Diag::ETHERNET) DIAG(F("\nEthernet: disconnect %d \n"), socket);
|
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +174,7 @@ void EthernetInterface::loop()
|
|||||||
int socketOut=outboundRing->read();
|
int socketOut=outboundRing->read();
|
||||||
if (socketOut>=0) {
|
if (socketOut>=0) {
|
||||||
int count=outboundRing->count();
|
int count=outboundRing->count();
|
||||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d\n"), socketOut,count);
|
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
|
||||||
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
|
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
|
||||||
clients[socketOut].flush(); //maybe
|
clients[socketOut].flush(); //maybe
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,12 @@
|
|||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include <Ethernet.h>
|
#if defined (ARDUINO_TEENSY41)
|
||||||
|
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||||
|
#include <NativeEthernetUdp.h>
|
||||||
|
#else
|
||||||
|
#include "Ethernet.h"
|
||||||
|
#endif
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +44,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define LISTEN_PORT 2560 // standard listen port for the server
|
|
||||||
#define MAX_ETH_BUFFER 512
|
#define MAX_ETH_BUFFER 512
|
||||||
#define OUTBOUND_RING_SIZE 2048
|
#define OUTBOUND_RING_SIZE 2048
|
||||||
|
|
||||||
|
@@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "43e7c18"
|
#define GITHUB_SHA "90487d2"
|
||||||
|
129
I2CManager.cpp
Normal file
129
I2CManager.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Neil McKechnie. 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "I2CManager.h"
|
||||||
|
|
||||||
|
// If not already initialised, initialise I2C (wire).
|
||||||
|
void I2CManagerClass::begin(void) {
|
||||||
|
if (!_beginCompleted) {
|
||||||
|
Wire.begin();
|
||||||
|
_beginCompleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set clock speed to the lowest requested one. If none requested,
|
||||||
|
// the Wire default is 100kHz.
|
||||||
|
void I2CManagerClass::setClock(uint32_t speed) {
|
||||||
|
if (speed < _clockSpeed && !_clockSpeedFixed) {
|
||||||
|
_clockSpeed = speed;
|
||||||
|
Wire.setClock(_clockSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force clock speed to that specified. It can then only
|
||||||
|
// be overridden by calling Wire.setClock directly.
|
||||||
|
void I2CManagerClass::forceClock(uint32_t speed) {
|
||||||
|
if (!_clockSpeedFixed) {
|
||||||
|
_clockSpeed = speed;
|
||||||
|
_clockSpeedFixed = true;
|
||||||
|
Wire.setClock(_clockSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if specified I2C address is responding.
|
||||||
|
// Returns 0 if OK, or error code.
|
||||||
|
uint8_t I2CManagerClass::checkAddress(uint8_t address) {
|
||||||
|
begin();
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
return Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2CManagerClass::exists(uint8_t address) {
|
||||||
|
return checkAddress(address)==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a complete transmission to I2C using a supplied buffer of data
|
||||||
|
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size) {
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
Wire.write(buffer, size);
|
||||||
|
return Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a complete transmission to I2C using a supplied buffer of data in Flash
|
||||||
|
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size) {
|
||||||
|
uint8_t ramBuffer[size];
|
||||||
|
memcpy_P(ramBuffer, buffer, size);
|
||||||
|
return write(address, ramBuffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Write a complete transmission to I2C using a list of data
|
||||||
|
uint8_t I2CManagerClass::write(uint8_t address, int nBytes, ...) {
|
||||||
|
uint8_t buffer[nBytes];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, nBytes);
|
||||||
|
for (uint8_t i=0; i<nBytes; i++)
|
||||||
|
buffer[i] = va_arg(args, int);
|
||||||
|
va_end(args);
|
||||||
|
return write(address, buffer, nBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a command and read response, returns number of bytes received.
|
||||||
|
// Different modules use different ways of accessing registers:
|
||||||
|
// PCF8574 I/O expander justs needs the address (no data);
|
||||||
|
// PCA9685 needs a two byte command to select the register(s) to be read;
|
||||||
|
// MCP23016 needs a one-byte command to select the register.
|
||||||
|
// Some devices use 8-bit registers exclusively and some have 16-bit registers.
|
||||||
|
// Therefore the following function is general purpose, to apply to any
|
||||||
|
// type of I2C device.
|
||||||
|
//
|
||||||
|
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||||
|
uint8_t writeBuffer[], uint8_t writeSize) {
|
||||||
|
if (writeSize > 0) {
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
Wire.write(writeBuffer, writeSize);
|
||||||
|
Wire.endTransmission(false); // Don't free bus yet
|
||||||
|
}
|
||||||
|
Wire.requestFrom(address, readSize);
|
||||||
|
uint8_t nBytes = 0;
|
||||||
|
while (Wire.available() && nBytes < readSize)
|
||||||
|
readBuffer[nBytes++] = Wire.read();
|
||||||
|
return nBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload of read() to allow command to be specified as a series of bytes.
|
||||||
|
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||||
|
uint8_t writeSize, ...) {
|
||||||
|
va_list args;
|
||||||
|
// Copy the series of bytes into an array.
|
||||||
|
va_start(args, writeSize);
|
||||||
|
uint8_t writeBuffer[writeSize];
|
||||||
|
for (uint8_t i=0; i<writeSize; i++)
|
||||||
|
writeBuffer[i] = va_arg(args, int);
|
||||||
|
va_end(args);
|
||||||
|
return read(address, readBuffer, readSize, writeBuffer, writeSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize) {
|
||||||
|
return read(address, readBuffer, readSize, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
I2CManagerClass I2CManager = I2CManagerClass();
|
76
I2CManager.h
Normal file
76
I2CManager.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Neil McKechnie. 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 I2CManager_h
|
||||||
|
#define I2CManager_h
|
||||||
|
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper class to manage access to the I2C 'Wire' subsystem.
|
||||||
|
*
|
||||||
|
* Helps to avoid calling Wire.begin() multiple times (which is not)
|
||||||
|
* entirely benign as it reinitialises).
|
||||||
|
*
|
||||||
|
* Also helps to avoid the Wire clock from being set, by another device
|
||||||
|
* driver, to a speed which is higher than a device supports.
|
||||||
|
*
|
||||||
|
* Thirdly, it provides a convenient way to check whether there is a
|
||||||
|
* device on a particular I2C address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class I2CManagerClass {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
I2CManagerClass() {}
|
||||||
|
|
||||||
|
// If not already initialised, initialise I2C (wire).
|
||||||
|
void begin(void);
|
||||||
|
// Set clock speed to the lowest requested one.
|
||||||
|
void setClock(uint32_t speed);
|
||||||
|
// Force clock speed
|
||||||
|
void forceClock(uint32_t speed);
|
||||||
|
// Check if specified I2C address is responding.
|
||||||
|
uint8_t checkAddress(uint8_t address);
|
||||||
|
bool exists(uint8_t address);
|
||||||
|
// Write a complete transmission to I2C from an array in RAM
|
||||||
|
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||||
|
// Write a complete transmission to I2C from an array in Flash
|
||||||
|
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||||
|
// Write a transmission to I2C from a list of bytes.
|
||||||
|
uint8_t write(uint8_t address, int nBytes, ...);
|
||||||
|
// Write a command from an array in RAM and read response
|
||||||
|
uint8_t read(uint8_t address, uint8_t writeBuffer[], uint8_t writeSize,
|
||||||
|
uint8_t readBuffer[], uint8_t readSize);
|
||||||
|
// Write a command from an arbitrary list of bytes and read response
|
||||||
|
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||||
|
uint8_t writeSize, ...);
|
||||||
|
// Write a null command and read the response.
|
||||||
|
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _beginCompleted = false;
|
||||||
|
bool _clockSpeedFixed = false;
|
||||||
|
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
|
||||||
|
};
|
||||||
|
|
||||||
|
extern I2CManagerClass I2CManager;
|
||||||
|
|
||||||
|
#endif
|
191
LCDDisplay.cpp
191
LCDDisplay.cpp
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -17,63 +17,146 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// CAUTION: the device dependent parts of this class are created in the .ini using LCD_Implementation.h
|
// CAUTION: the device dependent parts of this class are created in the .ini
|
||||||
|
// using LCD_Implementation.h
|
||||||
|
|
||||||
|
/* The strategy for drawing the screen is as follows.
|
||||||
|
* 1) There are up to eight rows of text to be displayed.
|
||||||
|
* 2) Blank rows of text are ignored.
|
||||||
|
* 3) If there are more non-blank rows than screen lines,
|
||||||
|
* then all of the rows are displayed, with the rest of the
|
||||||
|
* screen being blank.
|
||||||
|
* 4) If there are fewer non-blank rows than screen lines,
|
||||||
|
* then a scrolling strategy is adopted so that, on each screen
|
||||||
|
* refresh, a different subset of the rows is presented.
|
||||||
|
* 5) On each entry into loop2(), a single operation is sent to the
|
||||||
|
* screen; this may be a position command or a character for
|
||||||
|
* display. This spreads the onerous work of updating the screen
|
||||||
|
* and ensures that other loop() functions in the application are
|
||||||
|
* not held up significantly. The exception to this is when
|
||||||
|
* the loop2() function is called with force=true, where
|
||||||
|
* a screen update is executed to completion. This is normally
|
||||||
|
* only done during start-up.
|
||||||
|
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
|
||||||
|
* in the config.h.
|
||||||
|
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||||
|
* #define SCROLLMODE 1 is by page (alternate between pages),
|
||||||
|
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
#include "LCDDisplay.h"
|
#include "LCDDisplay.h"
|
||||||
|
|
||||||
void LCDDisplay::clear() {
|
void LCDDisplay::clear() {
|
||||||
clearNative();
|
clearNative();
|
||||||
for (byte row=0;row<MAX_LCD_ROWS; row++) rowBuffer[row][0]='\0';
|
for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0';
|
||||||
topRow=-1; // loop2 will fill from row 0
|
topRow = -1; // loop2 will fill from row 0
|
||||||
}
|
}
|
||||||
|
|
||||||
void LCDDisplay::setRow(byte line) {
|
void LCDDisplay::setRow(byte line) {
|
||||||
hotRow=line;
|
hotRow = line;
|
||||||
hotCol=0;
|
hotCol = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t LCDDisplay::write(uint8_t b) {
|
size_t LCDDisplay::write(uint8_t b) {
|
||||||
if (hotRow>=MAX_LCD_ROWS || hotCol>=MAX_LCD_COLS) return -1;
|
if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1;
|
||||||
rowBuffer[hotRow][hotCol]=b;
|
rowBuffer[hotRow][hotCol] = b;
|
||||||
hotCol++;
|
hotCol++;
|
||||||
rowBuffer[hotRow][hotCol]=0;
|
rowBuffer[hotRow][hotCol] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LCDDisplay::loop() {
|
void LCDDisplay::loop() {
|
||||||
if (!lcdDisplay) return;
|
if (!lcdDisplay) return;
|
||||||
lcdDisplay->loop2(false);
|
lcdDisplay->loop2(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LCDDisplay* LCDDisplay::loop2(bool force) {
|
LCDDisplay *LCDDisplay::loop2(bool force) {
|
||||||
if ((!force) && (millis() - lastScrollTime)< LCD_SCROLL_TIME) return NULL;
|
if (!lcdDisplay) return NULL;
|
||||||
lastScrollTime=millis();
|
unsigned long currentMillis = millis();
|
||||||
clearNative();
|
|
||||||
int rowFirst=nextFilledRow();
|
if (!force) {
|
||||||
if (rowFirst<0)return NULL; // No filled rows
|
// See if we're in the time between updates
|
||||||
setRowNative(0);
|
if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME)
|
||||||
writeNative(rowBuffer[rowFirst]);
|
return NULL;
|
||||||
for (int slot=1;slot<lcdRows;slot++) {
|
} else {
|
||||||
int rowNext=nextFilledRow();
|
// force full screen update from the beginning.
|
||||||
if (rowNext==rowFirst){
|
rowFirst = -1;
|
||||||
// we have wrapped around and not filled the screen
|
rowNext = 0;
|
||||||
topRow=-1; // start again at first row next time.
|
bufferPointer = 0;
|
||||||
break;
|
done = false;
|
||||||
|
slot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (bufferPointer == 0) {
|
||||||
|
// Find a line of data to write to the screen.
|
||||||
|
if (rowFirst < 0) rowFirst = rowNext;
|
||||||
|
skipBlankRows();
|
||||||
|
if (!done) {
|
||||||
|
// Non-blank line found, so copy it.
|
||||||
|
for (uint8_t i = 0; i < sizeof(buffer); i++)
|
||||||
|
buffer[i] = rowBuffer[rowNext][i];
|
||||||
|
} else
|
||||||
|
buffer[0] = '\0'; // Empty line
|
||||||
|
setRowNative(slot); // Set position for display
|
||||||
|
charIndex = 0;
|
||||||
|
bufferPointer = &buffer[0];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Write next character, or a space to erase current position.
|
||||||
|
char ch = *bufferPointer;
|
||||||
|
if (ch) {
|
||||||
|
writeNative(ch);
|
||||||
|
bufferPointer++;
|
||||||
|
} else
|
||||||
|
writeNative(' ');
|
||||||
|
|
||||||
|
if (++charIndex >= MAX_LCD_COLS) {
|
||||||
|
// Screen slot completed, move to next slot on screen
|
||||||
|
slot++;
|
||||||
|
bufferPointer = 0;
|
||||||
|
if (!done) {
|
||||||
|
moveToNextRow();
|
||||||
|
skipBlankRows();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setRowNative(slot);
|
|
||||||
writeNative(rowBuffer[rowNext]);
|
|
||||||
}
|
|
||||||
displayNative();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LCDDisplay::nextFilledRow() {
|
if (slot >= lcdRows) {
|
||||||
for (int rx=1;rx<=MAX_LCD_ROWS;rx++) {
|
// Last slot finished, reset ready for next screen update.
|
||||||
topRow++;
|
#if SCROLLMODE==2
|
||||||
topRow %= MAX_LCD_ROWS;
|
if (!done) {
|
||||||
if (rowBuffer[topRow][0]) return topRow;
|
// On next refresh, restart one row on from previous start.
|
||||||
}
|
rowNext = rowFirst;
|
||||||
return -1; // No slots filled
|
moveToNextRow();
|
||||||
}
|
skipBlankRows();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
done = false;
|
||||||
|
slot = 0;
|
||||||
|
rowFirst = -1;
|
||||||
|
lastScrollTime = currentMillis;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (force);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDDisplay::moveToNextRow() {
|
||||||
|
rowNext = (rowNext + 1) % MAX_LCD_ROWS;
|
||||||
|
#if SCROLLMODE == 1
|
||||||
|
// Finished if we've looped back to row 0
|
||||||
|
if (rowNext == 0) done = true;
|
||||||
|
#else
|
||||||
|
// Finished if we're back to the first one shown
|
||||||
|
if (rowNext == rowFirst) done = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDDisplay::skipBlankRows() {
|
||||||
|
while (!done && rowBuffer[rowNext][0] == 0)
|
||||||
|
moveToNextRow();
|
||||||
|
}
|
87
LCDDisplay.h
87
LCDDisplay.h
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -20,44 +20,61 @@
|
|||||||
#define LCDDisplay_h
|
#define LCDDisplay_h
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#if __has_include ( "config.h")
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Allow maximum message length to be overridden from config.h
|
||||||
|
#if !defined(MAX_MSG_SIZE)
|
||||||
|
#define MAX_MSG_SIZE 16
|
||||||
|
#endif
|
||||||
|
|
||||||
// This class is created in LCDisplay_Implementation.h
|
// This class is created in LCDisplay_Implementation.h
|
||||||
|
|
||||||
class LCDDisplay : public Print {
|
class LCDDisplay : public Print {
|
||||||
|
public:
|
||||||
|
static const int MAX_LCD_ROWS = 8;
|
||||||
|
static const int MAX_LCD_COLS = MAX_MSG_SIZE;
|
||||||
|
static const long LCD_SCROLL_TIME = 3000; // 3 seconds
|
||||||
|
|
||||||
public:
|
static LCDDisplay* lcdDisplay;
|
||||||
static const int MAX_LCD_ROWS=8;
|
LCDDisplay();
|
||||||
static const int MAX_LCD_COLS=16;
|
void interfake(int p1, int p2, int p3);
|
||||||
static const long LCD_SCROLL_TIME=3000; // 3 seconds
|
|
||||||
|
|
||||||
static LCDDisplay* lcdDisplay;
|
|
||||||
LCDDisplay();
|
|
||||||
void interfake(int p1, int p2, int p3);
|
|
||||||
|
|
||||||
// Internally handled functions
|
// Internally handled functions
|
||||||
static void loop();
|
static void loop();
|
||||||
LCDDisplay* loop2(bool force);
|
LCDDisplay* loop2(bool force);
|
||||||
void setRow(byte line);
|
void setRow(byte line);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
virtual size_t write(uint8_t b);
|
virtual size_t write(uint8_t b);
|
||||||
using Print::write;
|
using Print::write;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int nextFilledRow();
|
void moveToNextRow();
|
||||||
|
void skipBlankRows();
|
||||||
// Relay functions to the live driver
|
|
||||||
void clearNative();
|
// Relay functions to the live driver
|
||||||
void displayNative();
|
void clearNative();
|
||||||
void setRowNative(byte line);
|
void displayNative();
|
||||||
void writeNative(char * b);
|
void setRowNative(byte line);
|
||||||
|
void writeNative(char b);
|
||||||
unsigned long lastScrollTime=0;
|
|
||||||
int hotRow=0;
|
unsigned long lastScrollTime = 0;
|
||||||
int hotCol=0;
|
int8_t hotRow = 0;
|
||||||
int topRow=0;
|
int8_t hotCol = 0;
|
||||||
int lcdRows;
|
int8_t topRow = 0;
|
||||||
void renderRow(byte row);
|
uint8_t lcdRows;
|
||||||
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS+1];
|
uint8_t lcdCols;
|
||||||
|
int8_t slot = 0;
|
||||||
|
int8_t rowFirst = -1;
|
||||||
|
int8_t rowNext = 0;
|
||||||
|
int8_t charIndex = 0;
|
||||||
|
char buffer[MAX_LCD_COLS + 1];
|
||||||
|
char* bufferPointer = 0;
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
//
|
//
|
||||||
// It will create a driver implemntation and a shim class implementation.
|
// It will create a driver implemntation and a shim class implementation.
|
||||||
// This means that other classes can reference the shim without knowing
|
// This means that other classes can reference the shim without knowing
|
||||||
// which libraray is involved.
|
// which library is involved.
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#ifndef LCD_Implementation_h
|
#ifndef LCD_Implementation_h
|
||||||
@@ -49,7 +49,7 @@ LCDDisplay * LCDDisplay::lcdDisplay=0;
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
#include "LCD_NONE.h"
|
#include "LCD_NONE.h"
|
||||||
#define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */
|
#define CONDITIONAL_LCD_START if (true) /* NO LCD CONFIG, but do the LCD macros to get DIAGS */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // LCD_Implementation_h
|
#endif // LCD_Implementation_h
|
||||||
|
12
LCD_LCD.h
12
LCD_LCD.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include <LiquidCrystal_I2C.h>
|
#include "LiquidCrystal_I2C.h"
|
||||||
LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows
|
LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows
|
||||||
// DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER
|
// DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER
|
||||||
LCDDisplay::LCDDisplay() {
|
LCDDisplay::LCDDisplay() {
|
||||||
@@ -28,10 +28,6 @@
|
|||||||
}
|
}
|
||||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; }
|
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; }
|
||||||
void LCDDisplay::clearNative() {LCDDriver.clear();}
|
void LCDDisplay::clearNative() {LCDDriver.clear();}
|
||||||
void LCDDisplay::setRowNative(byte row) {
|
void LCDDisplay::setRowNative(byte row) { LCDDriver.setCursor(0, row); }
|
||||||
LCDDriver.setCursor(0, row);
|
void LCDDisplay::writeNative(char b){ LCDDriver.write(b); }
|
||||||
LCDDriver.print(F(" "));
|
|
||||||
LCDDriver.setCursor(0, row);
|
|
||||||
}
|
|
||||||
void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); }
|
|
||||||
void LCDDisplay::displayNative() { LCDDriver.display(); }
|
void LCDDisplay::displayNative() { LCDDriver.display(); }
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@@ -22,6 +22,6 @@
|
|||||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;}
|
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;}
|
||||||
void LCDDisplay::setRowNative(byte row) { (void)row;}
|
void LCDDisplay::setRowNative(byte row) { (void)row;}
|
||||||
void LCDDisplay::clearNative() {}
|
void LCDDisplay::clearNative() {}
|
||||||
void LCDDisplay::writeNative(char * b){ (void)b;} //
|
void LCDDisplay::writeNative(char b){ (void)b;} //
|
||||||
void LCDDisplay::displayNative(){}
|
void LCDDisplay::displayNative(){}
|
||||||
|
|
||||||
|
80
LCD_OLED.h
80
LCD_OLED.h
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
@@ -17,41 +17,57 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// OLED Implementation of LCDDisplay class
|
// OLED Implementation of LCDDisplay class
|
||||||
// Note: this file is optionally included by LCD_Implenentation.h
|
// Note: this file is optionally included by LCD_Implementation.h
|
||||||
// It is NOT a .cpp file to prevent it being compiled and demanding libraraies even when not needed.
|
// It is NOT a .cpp file to prevent it being compiled and demanding libraries
|
||||||
|
// even when not needed.
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
Adafruit_SSD1306 LCDDriver(OLED_DRIVER);
|
#include "I2CManager.h"
|
||||||
|
#include "SSD1306Ascii.h"
|
||||||
|
SSD1306AsciiWire LCDDriver;
|
||||||
|
|
||||||
// DEVICE SPECIFIC LCDDisplay Implementation for OLED
|
// DEVICE SPECIFIC LCDDisplay Implementation for OLED
|
||||||
|
|
||||||
LCDDisplay::LCDDisplay() {
|
LCDDisplay::LCDDisplay() {
|
||||||
if(LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3C) || LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
|
// Scan for device on 0x3c and 0x3d.
|
||||||
DIAG(F("\nOLED display found"));
|
I2CManager.begin();
|
||||||
delay(2000); // painful Adafruit splash pants!
|
I2CManager.setClock(400000L); // Set max supported I2C speed
|
||||||
lcdDisplay=this;
|
for (byte address = 0x3c; address <= 0x3d; address++) {
|
||||||
LCDDriver.setTextSize(1); // Normal 1:1 pixel scale
|
if (I2CManager.exists(address)) {
|
||||||
LCDDriver.setTextColor(SSD1306_WHITE); // Draw white text
|
// Device found
|
||||||
interfake(OLED_DRIVER,0);
|
DIAG(F("OLED display found at 0x%x"), address);
|
||||||
|
interfake(OLED_DRIVER, 0);
|
||||||
|
const DevType *devType;
|
||||||
|
if (lcdCols == 132)
|
||||||
|
devType = &SH1106_128x64; // Actually 132x64 but treated as 128x64
|
||||||
|
else if (lcdCols == 128 && lcdRows == 4)
|
||||||
|
devType = &Adafruit128x32;
|
||||||
|
else
|
||||||
|
devType = &Adafruit128x64;
|
||||||
|
LCDDriver.begin(devType, address);
|
||||||
|
lcdDisplay = this;
|
||||||
|
LCDDriver.setFont(System5x7); // Normal 1:1 pixel scale, 8 bits high
|
||||||
clear();
|
clear();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
DIAG(F("\nOLED display not found\n"));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
DIAG(F("OLED display not found"));
|
||||||
|
}
|
||||||
|
|
||||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; lcdRows=p2/8; (void)p3;}
|
void LCDDisplay::interfake(int p1, int p2, int p3) {
|
||||||
|
lcdCols = p1;
|
||||||
|
lcdRows = p2 / 8;
|
||||||
|
(void)p3;
|
||||||
|
}
|
||||||
|
|
||||||
void LCDDisplay::clearNative() {LCDDriver.clearDisplay();}
|
void LCDDisplay::clearNative() { LCDDriver.clear(); }
|
||||||
|
|
||||||
void LCDDisplay::setRowNative(byte row) {
|
void LCDDisplay::setRowNative(byte row) {
|
||||||
// Positions text write to start of row 1..n and clears previous text
|
// Positions text write to start of row 1..n
|
||||||
int y=8*row;
|
int y = row;
|
||||||
LCDDriver.fillRect(0, y, LCDDriver.width(), 8, SSD1306_BLACK);
|
LCDDriver.setCursor(0, y);
|
||||||
LCDDriver.setCursor(0, y);
|
}
|
||||||
}
|
|
||||||
|
void LCDDisplay::writeNative(char b) { LCDDriver.write(b); }
|
||||||
void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); }
|
|
||||||
|
void LCDDisplay::displayNative() {}
|
||||||
void LCDDisplay::displayNative() { LCDDriver.display(); }
|
|
||||||
|
|
||||||
|
74
LCN.cpp
Normal file
74
LCN.cpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Chris Harlow. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-EX CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "LCN.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "Turnouts.h"
|
||||||
|
#include "Sensors.h"
|
||||||
|
|
||||||
|
int LCN::id = 0;
|
||||||
|
Stream * LCN::stream=NULL;
|
||||||
|
bool LCN::firstLoop=true;
|
||||||
|
|
||||||
|
void LCN::init(Stream & lcnstream) {
|
||||||
|
stream=&lcnstream;
|
||||||
|
DIAG(F("LCN connection setup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Inbound LCN traffic is postfix notation... nnnX where nnn is an id, X is the opcode
|
||||||
|
void LCN::loop() {
|
||||||
|
if (!stream) return;
|
||||||
|
if (firstLoop) {
|
||||||
|
firstLoop=false;
|
||||||
|
stream->println('X');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (stream->available()) {
|
||||||
|
int ch = stream->read();
|
||||||
|
if (ch >= 0 && ch <= '9') { // accumulate id value
|
||||||
|
id = 10 * id + ch - '0';
|
||||||
|
}
|
||||||
|
else if (ch == 't' || ch == 'T') { // Turnout opcodes
|
||||||
|
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||||
|
Turnout * tt = Turnout::get(id);
|
||||||
|
if (!tt) Turnout::create(id, LCN_TURNOUT_ADDRESS, 0);
|
||||||
|
if (ch == 't') tt->data.tStatus |= STATUS_ACTIVE;
|
||||||
|
else tt->data.tStatus &= ~STATUS_ACTIVE;
|
||||||
|
Turnout::turnoutlistHash++; // signals ED update of turnout data
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
else if (ch == 'S' || ch == 's') {
|
||||||
|
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||||
|
Sensor * ss = Sensor::get(id);
|
||||||
|
if (!ss) ss = Sensor::create(id, 255,0); // impossible pin
|
||||||
|
ss->active = ch == 'S';
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
else id = 0; // ignore any other garbage from LCN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCN::send(char opcode, int id, bool state) {
|
||||||
|
if (stream) {
|
||||||
|
StringFormatter::send(stream,F("%c/%d/%d"), opcode, id , state);
|
||||||
|
if (Diag::LCN) DIAG(F("LCN OUT %c/%d/%d"), opcode, id , state);
|
||||||
|
}
|
||||||
|
}
|
16
LCN.h
Normal file
16
LCN.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef LCN_h
|
||||||
|
#define LCN_h
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class LCN {
|
||||||
|
public:
|
||||||
|
static void init(Stream & lcnstream);
|
||||||
|
static void loop();
|
||||||
|
static void send(char opcode, int id, bool state);
|
||||||
|
private :
|
||||||
|
static bool firstLoop;
|
||||||
|
static Stream * stream;
|
||||||
|
static int id;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
218
LiquidCrystal_I2C.cpp
Normal file
218
LiquidCrystal_I2C.cpp
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Neil McKechnie. All rights reserved.
|
||||||
|
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
|
||||||
|
*
|
||||||
|
* 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-EX. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "LiquidCrystal_I2C.h"
|
||||||
|
#include "I2CManager.h"
|
||||||
|
|
||||||
|
// When the display powers up, it is configured as follows:
|
||||||
|
//
|
||||||
|
// 1. Display clear
|
||||||
|
// 2. Function set:
|
||||||
|
// DL = 1; 8-bit interface data
|
||||||
|
// N = 0; 1-line display
|
||||||
|
// F = 0; 5x8 dot character font
|
||||||
|
// 3. Display on/off control:
|
||||||
|
// D = 0; Display off
|
||||||
|
// C = 0; Cursor off
|
||||||
|
// B = 0; Blinking off
|
||||||
|
// 4. Entry mode set:
|
||||||
|
// I/D = 1; Increment by 1
|
||||||
|
// S = 0; No shift
|
||||||
|
//
|
||||||
|
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||||
|
// can't assume that its in that state when a sketch starts (and the
|
||||||
|
// LiquidCrystal constructor is called).
|
||||||
|
|
||||||
|
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols,
|
||||||
|
uint8_t lcd_rows) {
|
||||||
|
_Addr = lcd_Addr;
|
||||||
|
_cols = lcd_cols;
|
||||||
|
_rows = lcd_rows;
|
||||||
|
_backlightval = LCD_NOBACKLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init() { init_priv(); }
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::init_priv() {
|
||||||
|
I2CManager.begin();
|
||||||
|
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
|
||||||
|
|
||||||
|
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||||
|
begin(_cols, _rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines) {
|
||||||
|
if (lines > 1) {
|
||||||
|
_displayfunction |= LCD_2LINE;
|
||||||
|
}
|
||||||
|
_numlines = lines;
|
||||||
|
(void)cols; // Suppress compiler warning.
|
||||||
|
|
||||||
|
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||||
|
// before sending commands. Arduino can turn on way befer 4.5V so we'll allow
|
||||||
|
// 100 milliseconds after pulling both RS and R/W and backlight pin low
|
||||||
|
expanderWrite(
|
||||||
|
_backlightval); // reset expander and turn backlight off (Bit 8 =1)
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
// put the LCD into 4 bit mode
|
||||||
|
// this is according to the hitachi HD44780 datasheet
|
||||||
|
// figure 24, pg 46
|
||||||
|
|
||||||
|
// we start in 8bit mode, try to set 4 bit mode
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// second try
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(4500); // wait min 4.1ms
|
||||||
|
|
||||||
|
// third go!
|
||||||
|
write4bits(0x03 << 4);
|
||||||
|
delayMicroseconds(150);
|
||||||
|
|
||||||
|
// finally, set to 4-bit interface
|
||||||
|
write4bits(0x02 << 4);
|
||||||
|
|
||||||
|
// set # lines, font size, etc.
|
||||||
|
command(LCD_FUNCTIONSET | _displayfunction);
|
||||||
|
|
||||||
|
// turn the display on with no cursor or blinking default
|
||||||
|
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||||
|
display();
|
||||||
|
|
||||||
|
// clear it off
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// Initialize to default text direction (for roman languages)
|
||||||
|
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||||
|
|
||||||
|
// set the entry mode
|
||||||
|
command(LCD_ENTRYMODESET | _displaymode);
|
||||||
|
|
||||||
|
setCursor(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/********** high level commands, for the user! */
|
||||||
|
void LiquidCrystal_I2C::clear() {
|
||||||
|
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
|
||||||
|
delayMicroseconds(2000); // this command takes 1.52ms
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row) {
|
||||||
|
int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
|
||||||
|
if (row > _numlines) {
|
||||||
|
row = _numlines - 1; // we count rows starting w/0
|
||||||
|
}
|
||||||
|
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the display on/off (quickly)
|
||||||
|
void LiquidCrystal_I2C::noDisplay() {
|
||||||
|
_displaycontrol &= ~LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::display() {
|
||||||
|
_displaycontrol |= LCD_DISPLAYON;
|
||||||
|
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the (optional) backlight off/on
|
||||||
|
void LiquidCrystal_I2C::noBacklight(void) {
|
||||||
|
_backlightval = LCD_NOBACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiquidCrystal_I2C::backlight(void) {
|
||||||
|
_backlightval = LCD_BACKLIGHT;
|
||||||
|
expanderWrite(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||||
|
send(value, Rs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********** mid level commands, for sending data/cmds */
|
||||||
|
|
||||||
|
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||||
|
send(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/************ low level data pushing commands **********/
|
||||||
|
|
||||||
|
/* According to the NXP Datasheet for the PCF8574 section 8.2:
|
||||||
|
* "The master (microcontroller) sends the START condition and slave address
|
||||||
|
* setting the last bit of the address byte to logic 0 for the write mode.
|
||||||
|
* The PCF8574/74A acknowledges and the master then sends the data byte for
|
||||||
|
* P7 to P0 to the port register. As the clock line goes HIGH, the 8-bit
|
||||||
|
* data is presented on the port lines after it has been acknowledged by the
|
||||||
|
* PCF8574/74A. [...] The master can then send a STOP or ReSTART condition
|
||||||
|
* or continue sending data. The number of data bytes that can be sent
|
||||||
|
* successively is not limited and the previous data is overwritten every
|
||||||
|
* time a data byte has been sent and acknowledged."
|
||||||
|
*
|
||||||
|
* This driver takes advantage of this by sending multiple data bytes in succession
|
||||||
|
* within a single I2C transmission. With a fast clock rate of 400kHz, the time
|
||||||
|
* between successive updates of the PCF8574 outputs will be at least 2.5us. With
|
||||||
|
* the default clock rate of 100kHz the time between updates will be at least 10us.
|
||||||
|
*
|
||||||
|
* The LCD controller HD44780, according to its datasheet, needs nominally 37us
|
||||||
|
* (up to 50us) to execute a command (i.e. write to gdram, reposition, etc.). Each
|
||||||
|
* command is sent in a separate I2C transmission here. The time taken to end a
|
||||||
|
* transmission and start another one is a stop bit, a start bit, 8 address bits,
|
||||||
|
* an ack, 8 data bits and another ack; this is at least 20 bits, i.e. >50us
|
||||||
|
* at 400kHz and >200us at 100kHz. Therefore, we don't need additional delay.
|
||||||
|
*
|
||||||
|
* Similarly, the Enable must be set/reset for at least 450ns. This is
|
||||||
|
* well within the I2C clock cycle time of 2.5us at 400kHz. Data is clocked in
|
||||||
|
* to the HD44780 on the trailing edge of the Enable pin, so we set the Enable
|
||||||
|
* as we present the data, then in the next byte we reset Enable without changing
|
||||||
|
* the data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// write either command or data (8 bits) to the HD44780 LCD controller as
|
||||||
|
// a single I2C transmission.
|
||||||
|
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||||
|
mode |= _backlightval;
|
||||||
|
uint8_t highnib = (value & 0xf0) | mode;
|
||||||
|
uint8_t lownib = ((value << 4) & 0xf0) | mode;
|
||||||
|
// Send both nibbles
|
||||||
|
byte buffer[] = {(byte)(highnib|En), highnib, (byte)(lownib|En), lownib};
|
||||||
|
I2CManager.write(_Addr, buffer, sizeof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write 4 bits to the HD44780 LCD controller.
|
||||||
|
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||||
|
uint8_t _data = value | _backlightval;
|
||||||
|
// Enable must be set/reset for at least 450ns. This is well within the
|
||||||
|
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
|
||||||
|
// HD44780 on the trailing edge of the Enable pin.
|
||||||
|
byte buffer[] = {(byte)(_data|En), _data};
|
||||||
|
I2CManager.write(_Addr, buffer, sizeof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a byte to the PCF8574 I2C interface. We don't need to set
|
||||||
|
// the enable pin for this.
|
||||||
|
void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
|
||||||
|
I2CManager.write(_Addr, 1, value | _backlightval);
|
||||||
|
}
|
102
LiquidCrystal_I2C.h
Normal file
102
LiquidCrystal_I2C.h
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Neil McKechnie. All rights reserved.
|
||||||
|
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
|
||||||
|
*
|
||||||
|
* 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 LiquidCrystal_I2C_h
|
||||||
|
#define LiquidCrystal_I2C_h
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// commands
|
||||||
|
#define LCD_CLEARDISPLAY 0x01
|
||||||
|
#define LCD_RETURNHOME 0x02
|
||||||
|
#define LCD_ENTRYMODESET 0x04
|
||||||
|
#define LCD_DISPLAYCONTROL 0x08
|
||||||
|
#define LCD_CURSORSHIFT 0x10
|
||||||
|
#define LCD_FUNCTIONSET 0x20
|
||||||
|
#define LCD_SETCGRAMADDR 0x40
|
||||||
|
#define LCD_SETDDRAMADDR 0x80
|
||||||
|
|
||||||
|
// flags for display entry mode
|
||||||
|
#define LCD_ENTRYRIGHT 0x00
|
||||||
|
#define LCD_ENTRYLEFT 0x02
|
||||||
|
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||||
|
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||||
|
|
||||||
|
// flags for display on/off control
|
||||||
|
#define LCD_DISPLAYON 0x04
|
||||||
|
#define LCD_DISPLAYOFF 0x00
|
||||||
|
#define LCD_CURSORON 0x02
|
||||||
|
#define LCD_CURSOROFF 0x00
|
||||||
|
#define LCD_BLINKON 0x01
|
||||||
|
#define LCD_BLINKOFF 0x00
|
||||||
|
|
||||||
|
// flags for display/cursor shift
|
||||||
|
#define LCD_DISPLAYMOVE 0x08
|
||||||
|
#define LCD_CURSORMOVE 0x00
|
||||||
|
#define LCD_MOVERIGHT 0x04
|
||||||
|
#define LCD_MOVELEFT 0x00
|
||||||
|
|
||||||
|
// flags for function set
|
||||||
|
#define LCD_8BITMODE 0x10
|
||||||
|
#define LCD_4BITMODE 0x00
|
||||||
|
#define LCD_2LINE 0x08
|
||||||
|
#define LCD_1LINE 0x00
|
||||||
|
#define LCD_5x10DOTS 0x04
|
||||||
|
#define LCD_5x8DOTS 0x00
|
||||||
|
|
||||||
|
// flags for backlight control
|
||||||
|
#define LCD_BACKLIGHT 0x08
|
||||||
|
#define LCD_NOBACKLIGHT 0x00
|
||||||
|
|
||||||
|
#define En 0b00000100 // Enable bit
|
||||||
|
#define Rw 0b00000010 // Read/Write bit
|
||||||
|
#define Rs 0b00000001 // Register select bit
|
||||||
|
|
||||||
|
class LiquidCrystal_I2C : public Print {
|
||||||
|
public:
|
||||||
|
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||||
|
void begin(uint8_t cols, uint8_t rows);
|
||||||
|
void clear();
|
||||||
|
void noDisplay();
|
||||||
|
void display();
|
||||||
|
void noBacklight();
|
||||||
|
void backlight();
|
||||||
|
|
||||||
|
void setCursor(uint8_t, uint8_t);
|
||||||
|
virtual size_t write(uint8_t);
|
||||||
|
void command(uint8_t);
|
||||||
|
void init();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init_priv();
|
||||||
|
void send(uint8_t, uint8_t);
|
||||||
|
void write4bits(uint8_t);
|
||||||
|
void expanderWrite(uint8_t);
|
||||||
|
uint8_t _Addr;
|
||||||
|
uint8_t _displayfunction;
|
||||||
|
uint8_t _displaycontrol;
|
||||||
|
uint8_t _displaymode;
|
||||||
|
uint8_t _numlines;
|
||||||
|
uint8_t _cols;
|
||||||
|
uint8_t _rows;
|
||||||
|
uint8_t _backlightval;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||||
@@ -25,6 +26,8 @@
|
|||||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||||
|
|
||||||
|
bool MotorDriver::usePWM=false;
|
||||||
|
bool MotorDriver::commonFaultPin=false;
|
||||||
|
|
||||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||||
@@ -53,9 +56,16 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
setBrake(false);
|
setBrake(false);
|
||||||
}
|
}
|
||||||
else brakePin=UNUSED_PIN;
|
else brakePin=UNUSED_PIN;
|
||||||
|
|
||||||
|
// Initiate state of railcom cutout as off. This must be done
|
||||||
|
// independent of if railcom is used later or not.
|
||||||
|
setRailcomCutout(false);
|
||||||
|
|
||||||
currentPin=current_pin;
|
currentPin=current_pin;
|
||||||
pinMode(currentPin, INPUT);
|
if (currentPin!=UNUSED_PIN) {
|
||||||
|
pinMode(currentPin, INPUT);
|
||||||
|
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||||
|
}
|
||||||
|
|
||||||
faultPin=fault_pin;
|
faultPin=fault_pin;
|
||||||
if (faultPin != UNUSED_PIN) {
|
if (faultPin != UNUSED_PIN) {
|
||||||
@@ -66,8 +76,23 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
senseFactor=sense_factor;
|
senseFactor=sense_factor;
|
||||||
tripMilliamps=trip_milliamps;
|
tripMilliamps=trip_milliamps;
|
||||||
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
||||||
|
|
||||||
|
if (currentPin==UNUSED_PIN)
|
||||||
|
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
||||||
|
else
|
||||||
|
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d"),
|
||||||
|
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MotorDriver::isPWMCapable() {
|
||||||
|
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotorDriver::isRailcomCapable() {
|
||||||
|
return (!dualSignal) && DCCTimer::isRailcomPin(brakePin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void MotorDriver::setPower(bool on) {
|
void MotorDriver::setPower(bool on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
// toggle brake before turning power on - resets overcurrent error
|
// toggle brake before turning power on - resets overcurrent error
|
||||||
@@ -93,22 +118,62 @@ void MotorDriver::setBrake(bool on) {
|
|||||||
else setLOW(fastBrakePin);
|
else setLOW(fastBrakePin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MotorDriver::setRailcomCutout(bool on) {
|
||||||
|
DCCTimer::onoffPWM(signalPin,!on);
|
||||||
|
DCCTimer::setPWM(brakePin,on ^ invertBrake);
|
||||||
|
}
|
||||||
|
|
||||||
void MotorDriver::setSignal( bool high) {
|
void MotorDriver::setSignal( bool high) {
|
||||||
if (high) {
|
if (usePWM) {
|
||||||
setHIGH(fastSignalPin);
|
DCCTimer::setPWM(signalPin,high);
|
||||||
if (dualSignal) setLOW(fastSignalPin2);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setLOW(fastSignalPin);
|
if (high) {
|
||||||
if (dualSignal) setHIGH(fastSignalPin2);
|
setHIGH(fastSignalPin);
|
||||||
|
if (dualSignal) setLOW(fastSignalPin2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setLOW(fastSignalPin);
|
||||||
|
if (dualSignal) setHIGH(fastSignalPin2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||||
|
volatile unsigned int overflow_count=0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool MotorDriver::canMeasureCurrent() {
|
||||||
|
return currentPin!=UNUSED_PIN;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Return the current reading as pin reading 0 to 1023. If the fault
|
||||||
|
* pin is activated return a negative current to show active fault pin.
|
||||||
|
* As there is no -0, create a little and return -1 in that case.
|
||||||
|
*
|
||||||
|
* senseOffset handles the case where a shield returns values above or below
|
||||||
|
* a central value depending on direction.
|
||||||
|
*/
|
||||||
int MotorDriver::getCurrentRaw() {
|
int MotorDriver::getCurrentRaw() {
|
||||||
int current = analogRead(currentPin);
|
if (currentPin==UNUSED_PIN) return 0;
|
||||||
if (faultPin != UNUSED_PIN && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
int current;
|
||||||
return -current;
|
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||||
|
bool irq = disableInterrupts();
|
||||||
|
current = analogRead(currentPin)-senseOffset;
|
||||||
|
enableInterrupts(irq);
|
||||||
|
#elif defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||||
|
unsigned char sreg_backup;
|
||||||
|
sreg_backup = SREG; /* save interrupt enable/disable state */
|
||||||
|
cli();
|
||||||
|
current = analogRead(currentPin)-senseOffset;
|
||||||
|
overflow_count = 0;
|
||||||
|
SREG = sreg_backup; /* restore interrupt state */
|
||||||
|
#else
|
||||||
|
current = analogRead(currentPin)-senseOffset;
|
||||||
|
#endif
|
||||||
|
if (current<0) current=0-current;
|
||||||
|
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||||
|
return (current == 0 ? -1 : -current);
|
||||||
return current;
|
return current;
|
||||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||||
// The default analogRead takes ~100uS which is catastrphic
|
// The default analogRead takes ~100uS which is catastrphic
|
||||||
@@ -123,7 +188,8 @@ int MotorDriver::mA2raw( unsigned int mA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||||
DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin);
|
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
||||||
|
(void) type; // avoid compiler warning if diag not used above.
|
||||||
uint8_t port = digitalPinToPort(pin);
|
uint8_t port = digitalPinToPort(pin);
|
||||||
if (input)
|
if (input)
|
||||||
result.inout = portInputRegister(port);
|
result.inout = portInputRegister(port);
|
||||||
@@ -131,5 +197,5 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
|||||||
result.inout = portOutputRegister(port);
|
result.inout = portOutputRegister(port);
|
||||||
result.maskHIGH = digitalPinToBitMask(pin);
|
result.maskHIGH = digitalPinToBitMask(pin);
|
||||||
result.maskLOW = ~result.maskHIGH;
|
result.maskLOW = ~result.maskHIGH;
|
||||||
DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x\n"),port, result.inout,input,result.maskHIGH);
|
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||||
}
|
}
|
||||||
|
@@ -26,15 +26,24 @@
|
|||||||
#define UNUSED_PIN 127 // inside int8_t
|
#define UNUSED_PIN 127 // inside int8_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__IMXRT1062__)
|
||||||
|
struct FASTPIN {
|
||||||
|
volatile uint32_t *inout;
|
||||||
|
uint32_t maskHIGH;
|
||||||
|
uint32_t maskLOW;
|
||||||
|
};
|
||||||
|
#else
|
||||||
struct FASTPIN {
|
struct FASTPIN {
|
||||||
volatile uint8_t *inout;
|
volatile uint8_t *inout;
|
||||||
uint8_t maskHIGH;
|
uint8_t maskHIGH;
|
||||||
uint8_t maskLOW;
|
uint8_t maskLOW;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
class MotorDriver {
|
class MotorDriver {
|
||||||
public:
|
public:
|
||||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
|
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||||
virtual void setPower( bool on);
|
virtual void setPower( bool on);
|
||||||
virtual void setSignal( bool high);
|
virtual void setSignal( bool high);
|
||||||
virtual void setBrake( bool on);
|
virtual void setBrake( bool on);
|
||||||
@@ -44,7 +53,15 @@ class MotorDriver {
|
|||||||
inline int getRawCurrentTripValue() {
|
inline int getRawCurrentTripValue() {
|
||||||
return rawCurrentTripValue;
|
return rawCurrentTripValue;
|
||||||
}
|
}
|
||||||
|
bool isPWMCapable();
|
||||||
|
bool isRailcomCapable();
|
||||||
|
bool canMeasureCurrent();
|
||||||
|
void setRailcomCutout(bool on);
|
||||||
|
static bool usePWM;
|
||||||
|
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||||
|
inline byte getFaultPin() {
|
||||||
|
return faultPin;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||||
@@ -55,7 +72,19 @@ class MotorDriver {
|
|||||||
bool dualSignal; // true to use signalPin2
|
bool dualSignal; // true to use signalPin2
|
||||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||||
float senseFactor;
|
float senseFactor;
|
||||||
|
int senseOffset;
|
||||||
unsigned int tripMilliamps;
|
unsigned int tripMilliamps;
|
||||||
int rawCurrentTripValue;
|
int rawCurrentTripValue;
|
||||||
|
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||||
|
static bool disableInterrupts() {
|
||||||
|
uint32_t primask;
|
||||||
|
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
|
||||||
|
__disable_irq();
|
||||||
|
return (primask == 0) ? true : false;
|
||||||
|
}
|
||||||
|
static void enableInterrupts(bool doit) {
|
||||||
|
if (doit) __enable_irq();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -21,16 +21,21 @@
|
|||||||
// If the brakePin is negative that means the sense
|
// If the brakePin is negative that means the sense
|
||||||
// of the brake pin on the motor bridge is inverted
|
// of the brake pin on the motor bridge is inverted
|
||||||
// (HIGH == release brake)
|
// (HIGH == release brake)
|
||||||
|
//
|
||||||
// Arduino standard Motor Shield
|
// Arduino standard Motor Shield
|
||||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
|
// Arduino standard Motor Shield with railcom (mega brakes on 6,7 require jumpers )
|
||||||
|
#define STANDARD_WITH_RAILCOM F("STANDARD_WITH_RAILCOM"), \
|
||||||
|
new MotorDriver(3, 12, UNUSED_PIN, 6, A0, 2.99, 2000, UNUSED_PIN), \
|
||||||
|
new MotorDriver(11, 13, UNUSED_PIN, 7, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
// Pololu Motor Shield
|
// Pololu Motor Shield
|
||||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||||
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
|
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
|
||||||
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, UNUSED_PIN)
|
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, 12)
|
||||||
//
|
//
|
||||||
// Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the
|
// Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the
|
||||||
// pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually
|
// pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually
|
||||||
@@ -38,10 +43,17 @@
|
|||||||
// version of the code always will be high. That means this config is not usable for generating
|
// version of the code always will be high. That means this config is not usable for generating
|
||||||
// a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define
|
// a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define
|
||||||
// the motor driver like this:
|
// the motor driver like this:
|
||||||
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
|
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
|
||||||
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, UNUSED_PIN)
|
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
|
||||||
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
||||||
|
|
||||||
|
// Pololu Dual TB9051FTG Motor Shield
|
||||||
|
// This is the shield without modifications which means
|
||||||
|
// no HA waveform and no RailCom on an Arduino Mega 2560
|
||||||
|
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
|
||||||
|
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
|
||||||
|
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
|
||||||
|
|
||||||
// Firebox Mk1
|
// Firebox Mk1
|
||||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||||
@@ -57,4 +69,9 @@
|
|||||||
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
|
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
|
||||||
|
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
|
||||||
|
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
|
||||||
|
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -88,7 +88,7 @@ the state of any outputs being monitored or controlled by a separate interface o
|
|||||||
// print all output states to stream
|
// print all output states to stream
|
||||||
void Output::printAll(Print *stream){
|
void Output::printAll(Print *stream){
|
||||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||||
StringFormatter::send(stream, F("<Y %d %d>"), tt->data.id, tt->data.oStatus);
|
StringFormatter::send(stream, F("<Y %d %d>\n"), tt->data.id, tt->data.oStatus);
|
||||||
} // Output::printAll
|
} // Output::printAll
|
||||||
|
|
||||||
void Output::activate(int s){
|
void Output::activate(int s){
|
||||||
|
@@ -23,9 +23,9 @@
|
|||||||
* BSD license, all text above must be included in any redistribution
|
* BSD license, all text above must be included in any redistribution
|
||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
|
||||||
#include "PWMServoDriver.h"
|
#include "PWMServoDriver.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include "I2CManager.h"
|
||||||
|
|
||||||
|
|
||||||
// REGISTER ADDRESSES
|
// REGISTER ADDRESSES
|
||||||
@@ -40,6 +40,7 @@ const byte MODE1_RESTART=0x80; /**< Restart enabled */
|
|||||||
const byte PCA9685_I2C_ADDRESS=0x40; /** First PCA9685 I2C Slave Address */
|
const byte PCA9685_I2C_ADDRESS=0x40; /** First PCA9685 I2C Slave Address */
|
||||||
const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
|
const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
|
||||||
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
|
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
|
||||||
|
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Sets the PWM frequency for a chip to 50Hz for servos
|
* @brief Sets the PWM frequency for a chip to 50Hz for servos
|
||||||
@@ -52,19 +53,20 @@ bool PWMServoDriver::setup(int board) {
|
|||||||
if (board>3 || (failFlags & (1<<board))) return false;
|
if (board>3 || (failFlags & (1<<board))) return false;
|
||||||
if (setupFlags & (1<<board)) return true;
|
if (setupFlags & (1<<board)) return true;
|
||||||
|
|
||||||
Wire.begin();
|
I2CManager.begin();
|
||||||
|
I2CManager.setClock(MAX_I2C_SPEED);
|
||||||
|
|
||||||
uint8_t i2caddr=PCA9685_I2C_ADDRESS + board;
|
uint8_t i2caddr=PCA9685_I2C_ADDRESS + board;
|
||||||
|
|
||||||
// Terst if device is available
|
// Test if device is available
|
||||||
Wire.beginTransmission(i2caddr);
|
byte error = I2CManager.checkAddress(i2caddr);
|
||||||
byte error = Wire.endTransmission();
|
if (error) {
|
||||||
if (error!=0) {
|
DIAG(F("I2C Servo device 0x%x Not Found %d"),i2caddr, error);
|
||||||
DIAG(F("\nI2C Servo device 0x%x Not Found %d\n"),i2caddr, error);
|
|
||||||
failFlags|=1<<board;
|
failFlags|=1<<board;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DIAG(F("\nPWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
|
//DIAG(F("PWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
|
||||||
writeRegister(i2caddr,PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
|
writeRegister(i2caddr,PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
|
||||||
writeRegister(i2caddr,PCA9685_PRESCALE, PRESCALE_50HZ);
|
writeRegister(i2caddr,PCA9685_PRESCALE, PRESCALE_50HZ);
|
||||||
writeRegister(i2caddr,PCA9685_MODE1,MODE1_AI);
|
writeRegister(i2caddr,PCA9685_MODE1,MODE1_AI);
|
||||||
@@ -81,22 +83,15 @@ void PWMServoDriver::setServo(byte servoNum, uint16_t value) {
|
|||||||
int pin=servoNum%16;
|
int pin=servoNum%16;
|
||||||
|
|
||||||
if (setup(board)) {
|
if (setup(board)) {
|
||||||
DIAG(F("\nSetServo %d %d\n"),servoNum,value);
|
DIAG(F("SetServo %d %d"),servoNum,value);
|
||||||
Wire.beginTransmission(PCA9685_I2C_ADDRESS + board);
|
uint8_t buffer[] = {(uint8_t)(PCA9685_FIRST_SERVO + 4 * pin), // 4 registers per pin
|
||||||
Wire.write(PCA9685_FIRST_SERVO + 4 * pin); // 4 registers per pin
|
0, 0, (uint8_t)(value & 0xff), (uint8_t)(value >> 8)};
|
||||||
Wire.write(0);
|
if (value == 4095) buffer[2] = 0x10; // Full on
|
||||||
Wire.write(0);
|
byte error=I2CManager.write(PCA9685_I2C_ADDRESS + board, buffer, sizeof(buffer));
|
||||||
Wire.write(value);
|
if (error!=0) DIAG(F("SetServo error %d"),error);
|
||||||
Wire.write(value >> 8);
|
|
||||||
byte error=Wire.endTransmission();
|
|
||||||
if (error!=0) DIAG(F("\nSetServo error %d\n"),error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) {
|
void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) {
|
||||||
Wire.beginTransmission(i2caddr);
|
I2CManager.write(i2caddr, 2, hardwareRegister, d);
|
||||||
Wire.write(hardwareRegister);
|
|
||||||
Wire.write(d);
|
|
||||||
Wire.endTransmission();
|
|
||||||
delay(5); // allow registers to settle before continuing
|
|
||||||
}
|
}
|
||||||
|
27
README.md
27
README.md
@@ -12,11 +12,13 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
|
|||||||
|
|
||||||
* simultaneous control of multiple locomotives
|
* simultaneous control of multiple locomotives
|
||||||
* 2-byte and 4-byte locomotive addressing
|
* 2-byte and 4-byte locomotive addressing
|
||||||
* 128-step speed throttling
|
* 28 or 128-step speed throttling
|
||||||
* Activate/de-activate all accessory function addresses 0-2048
|
* Activate/de-activate all accessory function addresses 0-2048
|
||||||
* Control of all cab functions F0-F28
|
* Control of all cab functions F0-F28 and F29-F68
|
||||||
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
|
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
|
||||||
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
||||||
|
* And manu more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
|
||||||
|
|
||||||
|
|
||||||
# What’s in this Repository?
|
# What’s in this Repository?
|
||||||
|
|
||||||
@@ -38,11 +40,11 @@ in config.h.
|
|||||||
|
|
||||||
## What's new in CommandStation-EX?
|
## What's new in CommandStation-EX?
|
||||||
|
|
||||||
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station
|
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station (or through JMRI as before)
|
||||||
* WiFi and Ethernet shield support
|
* WiFi and Ethernet shield support
|
||||||
* No more jumpers or soldering!
|
* No more jumpers or soldering!
|
||||||
* Direct support for all the most popular motor control boards
|
* Direct support for all the most popular motor control boards including single pin (Arduino) or dual pin (IBT_2) type PWM inputs without the need for an adapter circuit
|
||||||
* I2C Display support
|
* I2C Display support (LCD and OLED)
|
||||||
* Improved short circuit detection and automatic reset from an overload
|
* Improved short circuit detection and automatic reset from an overload
|
||||||
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
|
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
|
||||||
* Improved adherence to the NMRA DCC specification
|
* Improved adherence to the NMRA DCC specification
|
||||||
@@ -50,6 +52,21 @@ in config.h.
|
|||||||
* Railcom cutout (beta)
|
* Railcom cutout (beta)
|
||||||
* Simpler, modular, faster code with an API Library for developers for easy expansion
|
* Simpler, modular, faster code with an API Library for developers for easy expansion
|
||||||
* New features and functions in JMRI
|
* New features and functions in JMRI
|
||||||
|
* Ability to join MAIN and PROG tracks into one MAIN track to run your locos
|
||||||
|
* "Drive-Away" feature - Throttles with support, like Engine Driver, can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||||
|
* Diagnostic commands to test decoders that aren't reading or writing correctly
|
||||||
|
* Support for Uno, Nano, Mega, Nano Every and Teensy microcontrollers
|
||||||
|
* User Functions: Filter regular commands (like a turnout or output command) and pass it to your own function or accessory
|
||||||
|
* Support for LCN (layout control nodes)
|
||||||
|
* mySetup.h file that acts like an Autoexec.Bat command to send startup commands to the CS
|
||||||
|
* High Accuracty Waveform option for rock steady DCC signals
|
||||||
|
* New current response outputs current in mA, overlimit current, and maximum board capable current. Support for new current meter in JMRI
|
||||||
|
* USB Browser based EX-WebThrottle
|
||||||
|
* New, simpler, function control command
|
||||||
|
* Number of locos discovery command `<#>`
|
||||||
|
* Emergency stop command <!>
|
||||||
|
* Release cabs from memory command <-> all cabs, <- CAB> for just one loco address
|
||||||
|
* Automatic slot (register) management
|
||||||
* Automation (coming soon)
|
* Automation (coming soon)
|
||||||
|
|
||||||
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
|
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
|
||||||
|
85
Release_Notes/release_notes_v3.0.0.md
Normal file
85
Release_Notes/release_notes_v3.0.0.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
|
||||||
|
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. Download the compressed files here:
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)
|
||||||
|
|
||||||
|
|
||||||
|
**Known Bugs:**
|
||||||
|
- **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket.
|
||||||
|
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
||||||
|
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.0:**
|
||||||
|
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||||
|
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
|
||||||
|
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||||
|
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||||
|
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||||
|
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||||
|
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||||
|
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
|
||||||
|
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||||
|
- **Functions to F28** - All NMRA functions are now supported
|
||||||
|
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
|
||||||
|
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
|
||||||
|
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||||
|
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||||
|
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||||
|
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||||
|
- **Fix EEPROM bugs**
|
||||||
|
- **Number of locos discovery command** - ```<#>``` command
|
||||||
|
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||||
|
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
|
||||||
|
|
||||||
|
|
||||||
|
**Key Contributors**
|
||||||
|
|
||||||
|
**Project Lead**
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
|
||||||
|
**CommandStation-EX Developers**
|
||||||
|
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||||
|
- Harald Barth - Stockholm, Sweden (Haba)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- M Steve Todd - - Engine Driver and JMRI Interface
|
||||||
|
- Scott Catalanno - Pennsylvania
|
||||||
|
- Gregor Baues - Île-de-France, France (grbba)
|
||||||
|
|
||||||
|
**exInstaller Software**
|
||||||
|
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||||
|
|
||||||
|
**Website and Documentation**
|
||||||
|
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||||
|
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||||
|
-Kevin Smith - (KCSmith)
|
||||||
|
|
||||||
|
**Beta Testing / Release Management / Support**
|
||||||
|
- Larry Dribin - Release Management
|
||||||
|
- Keith Ledbetter
|
||||||
|
- BradVan der Elst
|
||||||
|
- Andrew Pye
|
||||||
|
- Mike Bowers
|
||||||
|
- Randy McKenzie
|
||||||
|
- Roberto Bravin
|
||||||
|
- Sim Brigden
|
||||||
|
- Alan Lautenslager
|
||||||
|
- Martin Bafver
|
||||||
|
- Mário André Silva
|
||||||
|
- Anthony Kochevar
|
||||||
|
- Gajanatha Kobbekaduwe
|
||||||
|
- Sumner Patterson
|
||||||
|
- Paul - Virginia, USA
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)
|
||||||
|
|
206
Release_Notes/release_notes_v3.1.0.md
Normal file
206
Release_Notes/release_notes_v3.1.0.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||||
|
|
||||||
|
**Known Issues**
|
||||||
|
|
||||||
|
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||||
|
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
|
||||||
|
|
||||||
|
#### Summary of key features and/or bug fixes by Point Release
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||||
|
|
||||||
|
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||||
|
|
||||||
|
- Send function commands just once instead of repeating them 4 times
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||||
|
|
||||||
|
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||||
|
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||||
|
|
||||||
|
- Fix for CAB Functions greater than 127
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||||
|
|
||||||
|
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||||
|
|
||||||
|
- Reorganized files for support of 128 speed steps
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||||
|
|
||||||
|
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||||
|
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||||
|
|
||||||
|
- Rearranges serial newlines for the benefit of JMRI
|
||||||
|
- Major update for efficiencies in displays (LCD, OLED)
|
||||||
|
- Add I2C Support functions
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||||
|
|
||||||
|
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||||
|
|
||||||
|
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||||
|
- Improved overload messages with raw values (relative to offset)
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||||
|
|
||||||
|
- Prevent compiler warning about deprecated B constants
|
||||||
|
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||||
|
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||||
|
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||||
|
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||||
|
- `<D RESET>` command to reboot Arduino
|
||||||
|
- Automatic sensor offset detect
|
||||||
|
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||||
|
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||||
|
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||||
|
|
||||||
|
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||||
|
- Removed ethernet mac config and made it automatic
|
||||||
|
- Show wifi ip and port on lcd
|
||||||
|
- Auto load config.example.h with warning
|
||||||
|
- Dropped example .ino files
|
||||||
|
- Corrected .ino comments
|
||||||
|
- Add Pololu fault pin handling
|
||||||
|
- Waveform speed/simplicity improvements
|
||||||
|
- Improved pin speed in waveform
|
||||||
|
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||||
|
- Analog read speed improvements
|
||||||
|
- Drop need for DIO2 library
|
||||||
|
- Improved current check code
|
||||||
|
- Linear command
|
||||||
|
- Removed need for ArduinoTimers files
|
||||||
|
- Removed option to choose different timer
|
||||||
|
- Added EX-RAIL hooks for automation in future version
|
||||||
|
- Fixed Turnout list
|
||||||
|
- Allow command keywords in mixed case
|
||||||
|
- Dropped unused memstream
|
||||||
|
- PWM pin accuracy if requirements met
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||||
|
|
||||||
|
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||||
|
- WiFi Startup Fixes
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||||
|
|
||||||
|
- Command to write loco address and clear consist
|
||||||
|
- Command will allow for consist address
|
||||||
|
- Startup commands implemented
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||||
|
|
||||||
|
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||||
|
- Simultaneously update JMRI to handle new current meter
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||||
|
|
||||||
|
- Add back fix for jitter
|
||||||
|
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||||
|
|
||||||
|
**CommandStation-EX V3.0.0:**
|
||||||
|
|
||||||
|
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||||
|
|
||||||
|
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||||
|
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||||
|
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||||
|
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||||
|
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||||
|
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||||
|
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||||
|
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||||
|
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||||
|
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||||
|
- **Functions to F28** - All NMRA functions are now supported
|
||||||
|
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||||
|
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||||
|
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||||
|
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||||
|
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||||
|
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||||
|
- **Fix EEPROM bugs**
|
||||||
|
- **Number of locos discovery command** - `<#>` command
|
||||||
|
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||||
|
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||||
|
|
||||||
|
**Key Contributors**
|
||||||
|
|
||||||
|
**Project Lead**
|
||||||
|
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
|
||||||
|
**CommandStation-EX Developers**
|
||||||
|
|
||||||
|
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||||
|
- Harald Barth - Stockholm, Sweden (Haba)
|
||||||
|
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- M Steve Todd -
|
||||||
|
- Scott Catalano - Pennsylvania
|
||||||
|
- Gregor Baues - Île-de-France, France (grbba)
|
||||||
|
|
||||||
|
**Engine Driver and JMRI Interface**
|
||||||
|
|
||||||
|
- M Steve Todd
|
||||||
|
|
||||||
|
**exInstaller Software**
|
||||||
|
|
||||||
|
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||||
|
|
||||||
|
**Website and Documentation**
|
||||||
|
|
||||||
|
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||||
|
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||||
|
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||||
|
|
||||||
|
**WebThrotle-EX**
|
||||||
|
|
||||||
|
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||||
|
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||||
|
- Matt H - Somewhere in Europe
|
||||||
|
|
||||||
|
**Beta Testing / Release Management / Support**
|
||||||
|
|
||||||
|
- Larry Dribin - Release Management
|
||||||
|
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||||
|
- Keith Ledbetter
|
||||||
|
- BradVan der Elst
|
||||||
|
- Andrew Pye
|
||||||
|
- Mike Bowers
|
||||||
|
- Randy McKenzie
|
||||||
|
- Roberto Bravin
|
||||||
|
- Sim Brigden
|
||||||
|
- Alan Lautenslager
|
||||||
|
- Martin Bafver
|
||||||
|
- Mário André Silva
|
||||||
|
- Anthony Kochevar
|
||||||
|
- Gajanatha Kobbekaduwe
|
||||||
|
- Sumner Patterson
|
||||||
|
- Paul - Virginia, USA
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
@@ -75,9 +75,15 @@ void RingStream::mark(uint8_t b) {
|
|||||||
_count=0;
|
_count=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peekTargetMark is used by the parser stash routines to know which client
|
||||||
|
// to send a callback response to some time later.
|
||||||
|
uint8_t RingStream::peekTargetMark() {
|
||||||
|
return _buffer[_mark];
|
||||||
|
}
|
||||||
|
|
||||||
bool RingStream::commit() {
|
bool RingStream::commit() {
|
||||||
if (_overflow) {
|
if (_overflow) {
|
||||||
DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count);
|
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||||
// just throw it away
|
// just throw it away
|
||||||
_pos_write=_mark;
|
_pos_write=_mark;
|
||||||
_overflow=false;
|
_overflow=false;
|
||||||
|
@@ -33,7 +33,8 @@ class RingStream : public Print {
|
|||||||
int freeSpace();
|
int freeSpace();
|
||||||
void mark(uint8_t b);
|
void mark(uint8_t b);
|
||||||
bool commit();
|
bool commit();
|
||||||
|
uint8_t peekTargetMark();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _len;
|
int _len;
|
||||||
int _pos_write;
|
int _pos_write;
|
||||||
|
108
SSD1306Ascii.cpp
Normal file
108
SSD1306Ascii.cpp
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||||
|
* Modifications (C) 2021 Neil McKechnie
|
||||||
|
*
|
||||||
|
* This Library is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library 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 the Arduino SSD1306Ascii Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "SSD1306Ascii.h"
|
||||||
|
#include "I2CManager.h"
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Maximum number of bytes we can send per transmission is 32.
|
||||||
|
const uint8_t SSD1306AsciiWire::blankPixels[16] =
|
||||||
|
{0x40, // First byte specifies data mode
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// SSD1306AsciiWire Method Definitions
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::clear() {
|
||||||
|
clear(0, displayWidth() - 1, 0, displayRows() - 1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::clear(uint8_t columnStart, uint8_t columnEnd,
|
||||||
|
uint8_t rowStart, uint8_t rowEnd) {
|
||||||
|
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire
|
||||||
|
// Ensure only rows on display will be cleared.
|
||||||
|
if (rowEnd >= displayRows()) rowEnd = displayRows() - 1;
|
||||||
|
for (uint8_t r = rowStart; r <= rowEnd; r++) {
|
||||||
|
setCursor(columnStart, r); // Position at start of row to be erased
|
||||||
|
for (uint8_t c = columnStart; c <= columnEnd; c += maxBytes-1) {
|
||||||
|
uint8_t len = min((uint8_t)(columnEnd-c+1), maxBytes-1) + 1;
|
||||||
|
I2CManager.write(m_i2cAddr, blankPixels, len); // Write up to 15 blank columns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
|
||||||
|
m_i2cAddr = i2cAddr;
|
||||||
|
m_col = 0;
|
||||||
|
m_row = 0;
|
||||||
|
#ifdef __AVR__
|
||||||
|
const uint8_t* table = (const uint8_t*)pgm_read_word(&dev->initcmds);
|
||||||
|
#else // __AVR__
|
||||||
|
const uint8_t* table = dev->initcmds;
|
||||||
|
#endif // __AVR
|
||||||
|
uint8_t size = readFontByte(&dev->initSize);
|
||||||
|
m_displayWidth = readFontByte(&dev->lcdWidth);
|
||||||
|
m_displayHeight = readFontByte(&dev->lcdHeight);
|
||||||
|
m_colOffset = readFontByte(&dev->colOffset);
|
||||||
|
I2CManager.write_P(m_i2cAddr, table, size);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::setContrast(uint8_t value) {
|
||||||
|
I2CManager.write(m_i2cAddr, 2,
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_SETCONTRAST, value);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::setCursor(uint8_t col, uint8_t row) {
|
||||||
|
if (row < displayRows() && col < m_displayWidth) {
|
||||||
|
m_row = row;
|
||||||
|
m_col = col + m_colOffset;
|
||||||
|
I2CManager.write(m_i2cAddr, 4,
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_SETLOWCOLUMN | (col & 0XF),
|
||||||
|
SSD1306_SETHIGHCOLUMN | (col >> 4),
|
||||||
|
SSD1306_SETSTARTPAGE | m_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SSD1306AsciiWire::setFont(const uint8_t* font) {
|
||||||
|
m_font = font;
|
||||||
|
m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR);
|
||||||
|
m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
size_t SSD1306AsciiWire::write(uint8_t ch) {
|
||||||
|
const uint8_t* base = m_font + FONT_WIDTH_TABLE;
|
||||||
|
|
||||||
|
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
|
||||||
|
return 0;
|
||||||
|
ch -= m_fontFirstChar;
|
||||||
|
base += fontWidth * ch;
|
||||||
|
uint8_t buffer[1+fontWidth+letterSpacing];
|
||||||
|
buffer[0] = 0x40; // set SSD1306 controller to data mode
|
||||||
|
uint8_t bufferPos = 1;
|
||||||
|
// Copy character pixel columns
|
||||||
|
for (uint8_t i = 0; i < fontWidth; i++)
|
||||||
|
buffer[bufferPos++] = readFontByte(base++);
|
||||||
|
// Add blank pixels between letters
|
||||||
|
for (uint8_t i = 0; i < letterSpacing; i++)
|
||||||
|
buffer[bufferPos++] = 0;
|
||||||
|
// Write the data to I2C display
|
||||||
|
I2CManager.write(m_i2cAddr, buffer, bufferPos);
|
||||||
|
return 1;
|
||||||
|
}
|
97
SSD1306Ascii.h
Normal file
97
SSD1306Ascii.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||||
|
* Modifications (C) 2021 Neil McKechnie
|
||||||
|
*
|
||||||
|
* This Library is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this software. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SSD1306Ascii_h
|
||||||
|
#define SSD1306Ascii_h
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "SSD1306font.h"
|
||||||
|
#include "SSD1306init.h"
|
||||||
|
|
||||||
|
class SSD1306AsciiWire : public Print {
|
||||||
|
public:
|
||||||
|
using Print::write;
|
||||||
|
SSD1306AsciiWire() {}
|
||||||
|
// Initialize the display controller.
|
||||||
|
void begin(const DevType* dev, uint8_t i2cAddr);
|
||||||
|
// Clear the display and set the cursor to (0, 0).
|
||||||
|
void clear();
|
||||||
|
// Clear a region of the display.
|
||||||
|
void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1);
|
||||||
|
// The current column in pixels.
|
||||||
|
inline uint8_t col() const { return m_col; }
|
||||||
|
// The display hight in pixels.
|
||||||
|
inline uint8_t displayHeight() const { return m_displayHeight; }
|
||||||
|
// The display height in rows with eight pixels to a row.
|
||||||
|
inline uint8_t displayRows() const { return m_displayHeight / 8; }
|
||||||
|
// The display width in pixels.
|
||||||
|
inline uint8_t displayWidth() const { return m_displayWidth; }
|
||||||
|
// Set the cursor position to (0, 0).
|
||||||
|
inline void home() { setCursor(0, 0); }
|
||||||
|
// Initialize the display controller.
|
||||||
|
void init(const DevType* dev);
|
||||||
|
// the current row number with eight pixels to a row.
|
||||||
|
inline uint8_t row() const { return m_row; }
|
||||||
|
/**
|
||||||
|
* @brief Set the display contrast.
|
||||||
|
*
|
||||||
|
* @param[in] value The contrast level in th range 0 to 255.
|
||||||
|
*/
|
||||||
|
void setContrast(uint8_t value);
|
||||||
|
/**
|
||||||
|
* @brief Set the cursor position.
|
||||||
|
*
|
||||||
|
* @param[in] col The column number in pixels.
|
||||||
|
* @param[in] row the row number in eight pixel rows.
|
||||||
|
*/
|
||||||
|
void setCursor(uint8_t col, uint8_t row);
|
||||||
|
/**
|
||||||
|
* @brief Set the current font.
|
||||||
|
*
|
||||||
|
* @param[in] font Pointer to a font table.
|
||||||
|
*/
|
||||||
|
void setFont(const uint8_t* font);
|
||||||
|
/**
|
||||||
|
* @brief Display a character.
|
||||||
|
*
|
||||||
|
* @param[in] c The character to display.
|
||||||
|
* @return one for success else zero.
|
||||||
|
*/
|
||||||
|
size_t write(uint8_t c);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t m_col; // Cursor column.
|
||||||
|
uint8_t m_row; // Cursor RAM row.
|
||||||
|
uint8_t m_displayWidth; // Display width.
|
||||||
|
uint8_t m_displayHeight; // Display height.
|
||||||
|
uint8_t m_colOffset; // Column offset RAM to SEG.
|
||||||
|
const uint8_t* m_font = NULL; // Current font.
|
||||||
|
|
||||||
|
// Only fixed size 5x7 fonts in a 6x8 cell are supported.
|
||||||
|
const uint8_t fontWidth = 5;
|
||||||
|
const uint8_t fontHeight = 7;
|
||||||
|
const uint8_t letterSpacing = 1;
|
||||||
|
uint8_t m_fontFirstChar;
|
||||||
|
uint8_t m_fontCharCount;
|
||||||
|
|
||||||
|
uint8_t m_i2cAddr;
|
||||||
|
|
||||||
|
static const uint8_t blankPixels[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SSD1306Ascii_h
|
180
SSD1306font.h
Normal file
180
SSD1306font.h
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* System5x7
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* File Name : System5x7.h
|
||||||
|
* Date : 28 Oct 2008
|
||||||
|
* Font size in bytes : 470
|
||||||
|
* Font width : 5
|
||||||
|
* Font height : 7
|
||||||
|
* Font first char : 32
|
||||||
|
* Font last char : 127
|
||||||
|
* Font used chars : 94
|
||||||
|
*
|
||||||
|
* The font data are defined as
|
||||||
|
*
|
||||||
|
* struct _FONT_ {
|
||||||
|
* uint16_t font_Size_in_Bytes_over_all_included_Size_it_self;
|
||||||
|
* uint8_t font_Width_in_Pixel_for_fixed_drawing;
|
||||||
|
* uint8_t font_Height_in_Pixel_for_all_characters;
|
||||||
|
* unit8_t font_First_Char;
|
||||||
|
* uint8_t font_Char_Count;
|
||||||
|
*
|
||||||
|
* uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1];
|
||||||
|
* // for each character the separate width in pixels,
|
||||||
|
* // characters < 128 have an implicit virtual right empty row
|
||||||
|
*
|
||||||
|
* uint8_t font_data[];
|
||||||
|
* // bit field of all characters
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SSD1306font_H
|
||||||
|
#define SSD1306font_H
|
||||||
|
|
||||||
|
#define SYSTEM5x7_WIDTH 5
|
||||||
|
#define SYSTEM5x7_HEIGHT 7
|
||||||
|
|
||||||
|
#ifdef __AVR__
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
/** declare a font for AVR. */
|
||||||
|
#define GLCDFONTDECL(_n) static const uint8_t __attribute__((progmem)) _n[]
|
||||||
|
#define readFontByte(addr) pgm_read_byte(addr)
|
||||||
|
#else // __AVR__
|
||||||
|
/** declare a font. */
|
||||||
|
#define GLCDFONTDECL(_n) static const uint8_t _n[]
|
||||||
|
/** Fake read from flash. */
|
||||||
|
#define readFontByte(addr) (*(const unsigned char *)(addr))
|
||||||
|
#endif // __AVR__
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Font Indices
|
||||||
|
/** No longer used Big Endian length field. Now indicates font type.
|
||||||
|
*
|
||||||
|
* 00 00 (fixed width font with 1 padding pixel on right and below)
|
||||||
|
*
|
||||||
|
* 00 01 (fixed width font with no padding pixels)
|
||||||
|
*/
|
||||||
|
#define FONT_LENGTH 0
|
||||||
|
/** Maximum character width. */
|
||||||
|
#define FONT_WIDTH 2
|
||||||
|
/** Font hight in pixels */
|
||||||
|
#define FONT_HEIGHT 3
|
||||||
|
/** Ascii value of first character */
|
||||||
|
#define FONT_FIRST_CHAR 4
|
||||||
|
/** count of characters in font. */
|
||||||
|
#define FONT_CHAR_COUNT 5
|
||||||
|
/** Offset to width table. */
|
||||||
|
#define FONT_WIDTH_TABLE 6
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GLCDFONTDECL(System5x7) = {
|
||||||
|
0x0, 0x0, // size of zero indicates fixed width font,
|
||||||
|
0x05, // width
|
||||||
|
0x07, // height
|
||||||
|
0x20, // first char
|
||||||
|
0x61, // char count
|
||||||
|
|
||||||
|
// Fixed width; char width table not used !!!!
|
||||||
|
|
||||||
|
// font data
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // (space)
|
||||||
|
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||||
|
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||||
|
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||||
|
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||||
|
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||||
|
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||||
|
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||||
|
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||||
|
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||||
|
0x08, 0x2A, 0x1C, 0x2A, 0x08, // *
|
||||||
|
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||||
|
0x00, 0x50, 0x30, 0x00, 0x00, // ,
|
||||||
|
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||||
|
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||||
|
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||||
|
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||||
|
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||||
|
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||||
|
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||||
|
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||||
|
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||||
|
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||||
|
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||||
|
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||||
|
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||||
|
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||||
|
0x00, 0x56, 0x36, 0x00, 0x00, // ;
|
||||||
|
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||||
|
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||||
|
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||||
|
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||||
|
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||||
|
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||||
|
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||||
|
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||||
|
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||||
|
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||||
|
0x7F, 0x09, 0x09, 0x01, 0x01, // F
|
||||||
|
0x3E, 0x41, 0x41, 0x51, 0x32, // G
|
||||||
|
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||||
|
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||||
|
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||||
|
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||||
|
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||||
|
0x7F, 0x02, 0x04, 0x02, 0x7F, // M
|
||||||
|
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||||
|
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||||
|
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||||
|
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||||
|
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||||
|
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||||
|
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||||
|
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||||
|
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||||
|
0x7F, 0x20, 0x18, 0x20, 0x7F, // W
|
||||||
|
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||||
|
0x03, 0x04, 0x78, 0x04, 0x03, // Y
|
||||||
|
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||||
|
0x00, 0x00, 0x7F, 0x41, 0x41, // [
|
||||||
|
0x02, 0x04, 0x08, 0x10, 0x20, // "\"
|
||||||
|
0x41, 0x41, 0x7F, 0x00, 0x00, // ]
|
||||||
|
0x04, 0x02, 0x01, 0x02, 0x04, // ^
|
||||||
|
0x40, 0x40, 0x40, 0x40, 0x40, // _
|
||||||
|
0x00, 0x01, 0x02, 0x04, 0x00, // `
|
||||||
|
0x20, 0x54, 0x54, 0x54, 0x78, // a
|
||||||
|
0x7F, 0x48, 0x44, 0x44, 0x38, // b
|
||||||
|
0x38, 0x44, 0x44, 0x44, 0x20, // c
|
||||||
|
0x38, 0x44, 0x44, 0x48, 0x7F, // d
|
||||||
|
0x38, 0x54, 0x54, 0x54, 0x18, // e
|
||||||
|
0x08, 0x7E, 0x09, 0x01, 0x02, // f
|
||||||
|
0x08, 0x14, 0x54, 0x54, 0x3C, // g
|
||||||
|
0x7F, 0x08, 0x04, 0x04, 0x78, // h
|
||||||
|
0x00, 0x44, 0x7D, 0x40, 0x00, // i
|
||||||
|
0x20, 0x40, 0x44, 0x3D, 0x00, // j
|
||||||
|
0x00, 0x7F, 0x10, 0x28, 0x44, // k
|
||||||
|
0x00, 0x41, 0x7F, 0x40, 0x00, // l
|
||||||
|
0x7C, 0x04, 0x18, 0x04, 0x78, // m
|
||||||
|
0x7C, 0x08, 0x04, 0x04, 0x78, // n
|
||||||
|
0x38, 0x44, 0x44, 0x44, 0x38, // o
|
||||||
|
0x7C, 0x14, 0x14, 0x14, 0x08, // p
|
||||||
|
0x08, 0x14, 0x14, 0x18, 0x7C, // q
|
||||||
|
0x7C, 0x08, 0x04, 0x04, 0x08, // r
|
||||||
|
0x48, 0x54, 0x54, 0x54, 0x20, // s
|
||||||
|
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
||||||
|
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
||||||
|
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
|
||||||
|
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
|
||||||
|
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||||
|
0x0C, 0x50, 0x50, 0x50, 0x3C, // y
|
||||||
|
0x44, 0x64, 0x54, 0x4C, 0x44, // z
|
||||||
|
0x00, 0x08, 0x36, 0x41, 0x00, // {
|
||||||
|
0x00, 0x00, 0x7F, 0x00, 0x00, // |
|
||||||
|
0x00, 0x41, 0x36, 0x08, 0x00, // }
|
||||||
|
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
|
||||||
|
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
|
||||||
|
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
207
SSD1306init.h
Normal file
207
SSD1306init.h
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||||
|
* Modifications (C) 2021 Neil McKechnie
|
||||||
|
*
|
||||||
|
* This Library is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library 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 the Arduino SSD1306Ascii Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file SSD1306init.h
|
||||||
|
* @brief Display controller initialization commands.
|
||||||
|
*/
|
||||||
|
#ifndef SSD1306init_h
|
||||||
|
#define SSD1306init_h
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
#ifndef __AVR__
|
||||||
|
/** Handle AVR flash addressing. */
|
||||||
|
#define MEM_TYPE
|
||||||
|
#else // __AVR__
|
||||||
|
#define MEM_TYPE __attribute__ ((progmem))
|
||||||
|
#endif // __AVR__
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Set Lower Column Start Address for Page Addressing Mode. */
|
||||||
|
#define SSD1306_SETLOWCOLUMN 0x00
|
||||||
|
/** Set Higher Column Start Address for Page Addressing Mode. */
|
||||||
|
#define SSD1306_SETHIGHCOLUMN 0x10
|
||||||
|
/** Set Memory Addressing Mode. */
|
||||||
|
#define SSD1306_MEMORYMODE 0x20
|
||||||
|
/** Set display RAM display start line register from 0 - 63. */
|
||||||
|
#define SSD1306_SETSTARTLINE 0x40
|
||||||
|
/** Set Display Contrast to one of 256 steps. */
|
||||||
|
#define SSD1306_SETCONTRAST 0x81
|
||||||
|
/** Enable or disable charge pump. Follow with 0X14 enable, 0X10 disable. */
|
||||||
|
#define SSD1306_CHARGEPUMP 0x8D
|
||||||
|
/** Set Segment Re-map between data column and the segment driver. */
|
||||||
|
#define SSD1306_SEGREMAP 0xA0
|
||||||
|
/** Resume display from GRAM content. */
|
||||||
|
#define SSD1306_DISPLAYALLON_RESUME 0xA4
|
||||||
|
/** Force display on regardless of GRAM content. */
|
||||||
|
#define SSD1306_DISPLAYALLON 0xA5
|
||||||
|
/** Set Normal Display. */
|
||||||
|
#define SSD1306_NORMALDISPLAY 0xA6
|
||||||
|
/** Set Inverse Display. */
|
||||||
|
#define SSD1306_INVERTDISPLAY 0xA7
|
||||||
|
/** Set Multiplex Ratio from 16 to 63. */
|
||||||
|
#define SSD1306_SETMULTIPLEX 0xA8
|
||||||
|
/** Set Display off. */
|
||||||
|
#define SSD1306_DISPLAYOFF 0xAE
|
||||||
|
/** Set Display on. */
|
||||||
|
#define SSD1306_DISPLAYON 0xAF
|
||||||
|
/**Set GDDRAM Page Start Address. */
|
||||||
|
#define SSD1306_SETSTARTPAGE 0XB0
|
||||||
|
/** Set COM output scan direction normal. */
|
||||||
|
#define SSD1306_COMSCANINC 0xC0
|
||||||
|
/** Set COM output scan direction reversed. */
|
||||||
|
#define SSD1306_COMSCANDEC 0xC8
|
||||||
|
/** Set Display Offset. */
|
||||||
|
#define SSD1306_SETDISPLAYOFFSET 0xD3
|
||||||
|
/** Sets COM signals pin configuration to match the OLED panel layout. */
|
||||||
|
#define SSD1306_SETCOMPINS 0xDA
|
||||||
|
/** This command adjusts the VCOMH regulator output. */
|
||||||
|
#define SSD1306_SETVCOMDETECT 0xDB
|
||||||
|
/** Set Display Clock Divide Ratio/ Oscillator Frequency. */
|
||||||
|
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
|
||||||
|
/** Set Pre-charge Period */
|
||||||
|
#define SSD1306_SETPRECHARGE 0xD9
|
||||||
|
/** Deactivate scroll */
|
||||||
|
#define SSD1306_DEACTIVATE_SCROLL 0x2E
|
||||||
|
/** No Operation Command. */
|
||||||
|
#define SSD1306_NOP 0XE3
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Set Pump voltage value: (30H~33H) 6.4, 7.4, 8.0 (POR), 9.0. */
|
||||||
|
#define SH1106_SET_PUMP_VOLTAGE 0X30
|
||||||
|
/** First byte of set charge pump mode */
|
||||||
|
#define SH1106_SET_PUMP_MODE 0XAD
|
||||||
|
/** Second byte charge pump on. */
|
||||||
|
#define SH1106_PUMP_ON 0X8B
|
||||||
|
/** Second byte charge pump off. */
|
||||||
|
#define SH1106_PUMP_OFF 0X8A
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @struct DevType
|
||||||
|
* @brief Device initialization structure.
|
||||||
|
*/
|
||||||
|
struct DevType {
|
||||||
|
/**
|
||||||
|
* Pointer to initialization command bytes.
|
||||||
|
*/
|
||||||
|
const uint8_t* initcmds;
|
||||||
|
/**
|
||||||
|
* Number of initialization bytes.
|
||||||
|
*/
|
||||||
|
const uint8_t initSize;
|
||||||
|
/**
|
||||||
|
* Width of the diaplay in pixels.
|
||||||
|
*/
|
||||||
|
const uint8_t lcdWidth;
|
||||||
|
/**
|
||||||
|
* Height of the display in pixels.
|
||||||
|
*/
|
||||||
|
const uint8_t lcdHeight;
|
||||||
|
/**
|
||||||
|
* Column offset RAM to display. Used to pick start column of SH1106.
|
||||||
|
*/
|
||||||
|
const uint8_t colOffset;
|
||||||
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
|
||||||
|
/** Initialization commands for a 128x32 SSD1306 oled display. */
|
||||||
|
static const uint8_t MEM_TYPE Adafruit128x32init[] = {
|
||||||
|
// Init sequence for Adafruit 128x32 OLED module
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_DISPLAYOFF,
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
||||||
|
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
||||||
|
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
||||||
|
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||||
|
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
||||||
|
SSD1306_MEMORYMODE, 0x02, // page mode
|
||||||
|
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
||||||
|
SSD1306_COMSCANDEC, // column scan direction reversed
|
||||||
|
SSD1306_SETCOMPINS, 0x02, // sequential COM pins, disable remap
|
||||||
|
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
||||||
|
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
||||||
|
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
||||||
|
SSD1306_DISPLAYALLON_RESUME,
|
||||||
|
SSD1306_NORMALDISPLAY,
|
||||||
|
SSD1306_DISPLAYON
|
||||||
|
};
|
||||||
|
/** Initialize a 128x32 SSD1306 oled display. */
|
||||||
|
static const DevType MEM_TYPE Adafruit128x32 = {
|
||||||
|
Adafruit128x32init,
|
||||||
|
sizeof(Adafruit128x32init),
|
||||||
|
128,
|
||||||
|
32,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// This section is based on https://github.com/adafruit/Adafruit_SSD1306
|
||||||
|
/** Initialization commands for a 128x64 SSD1306 oled display. */
|
||||||
|
static const uint8_t MEM_TYPE Adafruit128x64init[] = {
|
||||||
|
// Init sequence for Adafruit 128x64 OLED module
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_DISPLAYOFF,
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
||||||
|
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
||||||
|
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
||||||
|
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||||
|
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
||||||
|
SSD1306_MEMORYMODE, 0x02, // page mode
|
||||||
|
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
||||||
|
SSD1306_COMSCANDEC, // column scan direction reversed
|
||||||
|
SSD1306_SETCOMPINS, 0x12, // alt COM pins, disable remap
|
||||||
|
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
||||||
|
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
||||||
|
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
||||||
|
SSD1306_DISPLAYALLON_RESUME,
|
||||||
|
SSD1306_NORMALDISPLAY,
|
||||||
|
SSD1306_DISPLAYON
|
||||||
|
};
|
||||||
|
/** Initialize a 128x64 oled display. */
|
||||||
|
static const DevType MEM_TYPE Adafruit128x64 = {
|
||||||
|
Adafruit128x64init,
|
||||||
|
sizeof(Adafruit128x64init),
|
||||||
|
128,
|
||||||
|
64,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
|
||||||
|
/** Initialization commands for a 128x64 SH1106 oled display. */
|
||||||
|
static const uint8_t MEM_TYPE SH1106_128x64init[] = {
|
||||||
|
0x00, // Set to command mode
|
||||||
|
SSD1306_DISPLAYOFF,
|
||||||
|
SSD1306_SETSTARTPAGE | 0X0, // set page address
|
||||||
|
SSD1306_SETCONTRAST, 0x80, // 128
|
||||||
|
SSD1306_SEGREMAP | 0X1, // set segment remap
|
||||||
|
SSD1306_NORMALDISPLAY, // normal / reverse
|
||||||
|
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
||||||
|
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
|
||||||
|
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
|
||||||
|
SSD1306_COMSCANDEC, // Com scan direction
|
||||||
|
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
|
||||||
|
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
|
||||||
|
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
||||||
|
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
|
||||||
|
SSD1306_DISPLAYON
|
||||||
|
};
|
||||||
|
/** Initialize a 128x64 oled SH1106 display. */
|
||||||
|
static const DevType MEM_TYPE SH1106_128x64 = {
|
||||||
|
SH1106_128x64init,
|
||||||
|
sizeof(SH1106_128x64init),
|
||||||
|
128,
|
||||||
|
64,
|
||||||
|
2 // SH1106 is a 132x64 controller. Use middle 128 columns.
|
||||||
|
};
|
||||||
|
#endif // SSD1306init_h
|
@@ -101,7 +101,7 @@ void Sensor::checkAll(Print *stream){
|
|||||||
// make the change
|
// make the change
|
||||||
readingSensor->active = !sensorstate;
|
readingSensor->active = !sensorstate;
|
||||||
readingSensor->latchdelay=0; // reset
|
readingSensor->latchdelay=0; // reset
|
||||||
if (stream != NULL) StringFormatter::send(stream, F("<%c %d>"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
if (stream != NULL) StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
||||||
}
|
}
|
||||||
|
|
||||||
readingSensor=readingSensor->nextSensor;
|
readingSensor=readingSensor->nextSensor;
|
||||||
@@ -117,7 +117,7 @@ void Sensor::printAll(Print *stream){
|
|||||||
|
|
||||||
for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||||
if (stream != NULL)
|
if (stream != NULL)
|
||||||
StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum);
|
StringFormatter::send(stream, F("<%c %d>\n"), tt->active ? 'Q' : 'q', tt->data.snum);
|
||||||
} // loop over all sensors
|
} // loop over all sensors
|
||||||
} // Sensor::printAll
|
} // Sensor::printAll
|
||||||
|
|
||||||
|
@@ -34,23 +34,26 @@ bool Diag::CMD=false;
|
|||||||
bool Diag::WIFI=false;
|
bool Diag::WIFI=false;
|
||||||
bool Diag::WITHROTTLE=false;
|
bool Diag::WITHROTTLE=false;
|
||||||
bool Diag::ETHERNET=false;
|
bool Diag::ETHERNET=false;
|
||||||
|
bool Diag::LCN=false;
|
||||||
|
|
||||||
|
|
||||||
void StringFormatter::diag( const FSH* input...) {
|
void StringFormatter::diag( const FSH* input...) {
|
||||||
if (!diagSerial) return;
|
if (!diagSerial) return;
|
||||||
|
diagSerial->print(F("<* "));
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(diagSerial,input,args);
|
send2(diagSerial,input,args);
|
||||||
|
diagSerial->print(F(" *>\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
// Issue the LCD as a diag first
|
// Issue the LCD as a diag first
|
||||||
diag(F("\nLCD%d:"),row);
|
send(diagSerial,F("<* LCD%d:"),row);
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(diagSerial,input,args);
|
send2(diagSerial,input,args);
|
||||||
diag(F("\n"));
|
send(diagSerial,F(" *>\n"));
|
||||||
|
|
||||||
if (!LCDDisplay::lcdDisplay) return;
|
if (!LCDDisplay::lcdDisplay) return;
|
||||||
LCDDisplay::lcdDisplay->setRow(row);
|
LCDDisplay::lcdDisplay->setRow(row);
|
||||||
|
@@ -33,6 +33,7 @@ class Diag {
|
|||||||
static bool WIFI;
|
static bool WIFI;
|
||||||
static bool WITHROTTLE;
|
static bool WITHROTTLE;
|
||||||
static bool ETHERNET;
|
static bool ETHERNET;
|
||||||
|
static bool LCN;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
22
Turnouts.cpp
22
Turnouts.cpp
@@ -29,17 +29,16 @@
|
|||||||
// print all turnout states to stream
|
// print all turnout states to stream
|
||||||
void Turnout::printAll(Print *stream){
|
void Turnout::printAll(Print *stream){
|
||||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||||
} // Turnout::printAll
|
} // Turnout::printAll
|
||||||
|
|
||||||
bool Turnout::activate(int n,bool state){
|
bool Turnout::activate(int n,bool state){
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
DIAG(F("Turnout::activate(%d,%d)"),n,state);
|
||||||
#endif
|
#endif
|
||||||
Turnout * tt=get(n);
|
Turnout * tt=get(n);
|
||||||
if (tt==NULL) return false;
|
if (tt==NULL) return false;
|
||||||
tt->activate(state);
|
tt->activate(state);
|
||||||
EEStore::store();
|
|
||||||
turnoutlistHash++;
|
turnoutlistHash++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -53,8 +52,13 @@ bool Turnout::isActive(int n){
|
|||||||
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
|
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
|
||||||
void Turnout::activate(bool state) {
|
void Turnout::activate(bool state) {
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
DIAG(F("\nTurnout::activate(%d)\n"),state);
|
DIAG(F("Turnout::activate(%d)"),state);
|
||||||
#endif
|
#endif
|
||||||
|
if (data.address==LCN_TURNOUT_ADDRESS) {
|
||||||
|
// A LCN turnout is transmitted to the LCN master.
|
||||||
|
LCN::send('T',data.id,state);
|
||||||
|
return; // The tStatus will be updated by a message from the LCN master, later.
|
||||||
|
}
|
||||||
if (state)
|
if (state)
|
||||||
data.tStatus|=STATUS_ACTIVE;
|
data.tStatus|=STATUS_ACTIVE;
|
||||||
else
|
else
|
||||||
@@ -63,7 +67,9 @@ void Turnout::activate(bool state) {
|
|||||||
PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0)));
|
PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0)));
|
||||||
else
|
else
|
||||||
DCC::setAccessory(data.address,data.subAddress, state);
|
DCC::setAccessory(data.address,data.subAddress, state);
|
||||||
EEStore::store();
|
// Save state if stored in EEPROM
|
||||||
|
if (EEStore::eeStore->data.nTurnouts > 0 && num > 0)
|
||||||
|
EEPROM.put(num, data.tStatus);
|
||||||
}
|
}
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -102,6 +108,7 @@ void Turnout::load(){
|
|||||||
if (data.tStatus & STATUS_PWM) tt=create(data.id,data.tStatus & STATUS_PWMPIN, data.inactiveAngle,data.moveAngle);
|
if (data.tStatus & STATUS_PWM) tt=create(data.id,data.tStatus & STATUS_PWMPIN, data.inactiveAngle,data.moveAngle);
|
||||||
else tt=create(data.id,data.address,data.subAddress);
|
else tt=create(data.id,data.address,data.subAddress);
|
||||||
tt->data.tStatus=data.tStatus;
|
tt->data.tStatus=data.tStatus;
|
||||||
|
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to status byte within EEPROM
|
||||||
EEStore::advance(sizeof(tt->data));
|
EEStore::advance(sizeof(tt->data));
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
tt->print(tt);
|
tt->print(tt);
|
||||||
@@ -121,6 +128,7 @@ void Turnout::store(){
|
|||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
tt->print(tt);
|
tt->print(tt);
|
||||||
#endif
|
#endif
|
||||||
|
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to tstatus byte within EEPROM
|
||||||
EEPROM.put(EEStore::pointer(),tt->data);
|
EEPROM.put(EEStore::pointer(),tt->data);
|
||||||
EEStore::advance(sizeof(tt->data));
|
EEStore::advance(sizeof(tt->data));
|
||||||
tt=tt->nextTurnout;
|
tt=tt->nextTurnout;
|
||||||
@@ -165,9 +173,9 @@ Turnout *Turnout::create(int id){
|
|||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
void Turnout::print(Turnout *tt) {
|
void Turnout::print(Turnout *tt) {
|
||||||
if (tt->data.tStatus & STATUS_PWM )
|
if (tt->data.tStatus & STATUS_PWM )
|
||||||
DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d\n"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE);
|
DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE);
|
||||||
else
|
else
|
||||||
DIAG(F("Turnout %d Addr %d Subaddr %d Status %d\n"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE);
|
DIAG(F("Turnout %d Addr %d Subaddr %d Status %d"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -21,11 +21,12 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
|
#include "LCN.h"
|
||||||
|
|
||||||
const byte STATUS_ACTIVE=0x80; // Flag as activated
|
const byte STATUS_ACTIVE=0x80; // Flag as activated
|
||||||
const byte STATUS_PWM=0x40; // Flag as a PWM turnout
|
const byte STATUS_PWM=0x40; // Flag as a PWM turnout
|
||||||
const byte STATUS_PWMPIN=0x3F; // PWM pin 0-63
|
const byte STATUS_PWMPIN=0x3F; // PWM pin 0-63
|
||||||
|
const int LCN_TURNOUT_ADDRESS=-1; // spoof dcc address -1 indicates a LCN turnout
|
||||||
struct TurnoutData {
|
struct TurnoutData {
|
||||||
int id;
|
int id;
|
||||||
uint8_t tStatus; // has STATUS_ACTIVE, STATUS_PWM, STATUS_PWMPIN
|
uint8_t tStatus; // has STATUS_ACTIVE, STATUS_PWM, STATUS_PWMPIN
|
||||||
@@ -53,6 +54,8 @@ class Turnout {
|
|||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
void print(Turnout *tt);
|
void print(Turnout *tt);
|
||||||
#endif
|
#endif
|
||||||
|
private:
|
||||||
|
int num; // EEPROM address of tStatus in TurnoutData struct, or zero if not stored.
|
||||||
}; // Turnout
|
}; // Turnout
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -76,7 +76,7 @@ bool WiThrottle::areYouUsingThrottle(int cab) {
|
|||||||
// One instance of WiThrottle per connected client, so we know what the locos are
|
// One instance of WiThrottle per connected client, so we know what the locos are
|
||||||
|
|
||||||
WiThrottle::WiThrottle( int wificlientid) {
|
WiThrottle::WiThrottle( int wificlientid) {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("\n%l Creating new WiThrottle for client %d\n"),millis(),wificlientid);
|
if (Diag::WITHROTTLE) DIAG(F("%l Creating new WiThrottle for client %d"),millis(),wificlientid);
|
||||||
nextThrottle=firstThrottle;
|
nextThrottle=firstThrottle;
|
||||||
firstThrottle= this;
|
firstThrottle= this;
|
||||||
clientid=wificlientid;
|
clientid=wificlientid;
|
||||||
@@ -104,7 +104,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
byte * cmd=cmdx;
|
byte * cmd=cmdx;
|
||||||
|
|
||||||
heartBeat=millis();
|
heartBeat=millis();
|
||||||
if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd);
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||||
|
|
||||||
if (initSent) {
|
if (initSent) {
|
||||||
// Send power state if different than last sent
|
// Send power state if different than last sent
|
||||||
@@ -133,6 +133,8 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
case 'P':
|
case 'P':
|
||||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
|
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||||
|
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||||
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
|
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
|
||||||
}
|
}
|
||||||
@@ -182,7 +184,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit\n"),millis(),clientid);
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||||
delete this;
|
delete this;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -205,6 +207,7 @@ int WiThrottle::getLocoId(byte * cmd) {
|
|||||||
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
|
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
|
||||||
return getInt(cmd+1);
|
return getInt(cmd+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||||
char throttleChar=cmd[1];
|
char throttleChar=cmd[1];
|
||||||
int locoid=getLocoId(cmd+3); // -1 for *
|
int locoid=getLocoId(cmd+3); // -1 for *
|
||||||
@@ -212,9 +215,20 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
|||||||
while(*aval !=';' && *aval !='\0') aval++;
|
while(*aval !=';' && *aval !='\0') aval++;
|
||||||
if (*aval) aval+=2; // skip ;>
|
if (*aval) aval+=2; // skip ;>
|
||||||
|
|
||||||
// DIAG(F("\nMultithrottle aval=%c cab=%d"), aval[0],locoid);
|
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
|
||||||
switch(cmd[2]) {
|
switch(cmd[2]) {
|
||||||
case '+': // add loco request
|
case '+': // add loco request
|
||||||
|
if (cmd[3]=='*') {
|
||||||
|
// M+* means get loco from prog track, then join tracks ready to drive away
|
||||||
|
// Stash the things the callback will need later
|
||||||
|
stashStream= stream;
|
||||||
|
stashClient=stream->peekTargetMark();
|
||||||
|
stashThrottleChar=throttleChar;
|
||||||
|
stashInstance=this;
|
||||||
|
// ask DCC to call us back when the loco id has been read
|
||||||
|
DCC::getLocoId(getLocoCallback); // will remove any previous join
|
||||||
|
return; // return nothing in stream as response is sent later in the callback
|
||||||
|
}
|
||||||
//return error if address zero requested
|
//return error if address zero requested
|
||||||
if (locoid==0) {
|
if (locoid==0) {
|
||||||
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
|
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
|
||||||
@@ -234,7 +248,7 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
|||||||
//Get known Fn states from DCC
|
//Get known Fn states from DCC
|
||||||
for(int fKey=0; fKey<=28; fKey++) {
|
for(int fKey=0; fKey<=28; fKey++) {
|
||||||
int fstate=DCC::getFn(locoid,fKey);
|
int fstate=DCC::getFn(locoid,fKey);
|
||||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c<;>F%d%d\n"),throttleChar,cmd[3],fstate,fKey);
|
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
|
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
|
||||||
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
|
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
|
||||||
@@ -258,7 +272,7 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
|||||||
|
|
||||||
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
|
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
|
||||||
// Note cab=-1 for all cabs in the consist called throttleChar.
|
// Note cab=-1 for all cabs in the consist called throttleChar.
|
||||||
// DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
||||||
switch (aval[0]) {
|
switch (aval[0]) {
|
||||||
case 'V': // Vspeed
|
case 'V': // Vspeed
|
||||||
{
|
{
|
||||||
@@ -352,10 +366,10 @@ void WiThrottle::loop(RingStream * stream) {
|
|||||||
void WiThrottle::checkHeartbeat() {
|
void WiThrottle::checkHeartbeat() {
|
||||||
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
||||||
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("\n\n%l WiThrottle(%d) eStop(%ds) timeout, drop connection\n"), millis(), clientid, ESTOP_SECONDS);
|
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
||||||
LOOPLOCOS('*', -1) {
|
LOOPLOCOS('*', -1) {
|
||||||
if (myLocos[loco].throttle!='\0') {
|
if (myLocos[loco].throttle!='\0') {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d\n"),millis(),myLocos[loco].cab);
|
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
||||||
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
|
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,4 +379,24 @@ void WiThrottle::checkHeartbeat() {
|
|||||||
|
|
||||||
char WiThrottle::LorS(int cab) {
|
char WiThrottle::LorS(int cab) {
|
||||||
return (cab<127)?'S':'L';
|
return (cab<127)?'S':'L';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drive Away feature. Callback handling
|
||||||
|
|
||||||
|
RingStream * WiThrottle::stashStream;
|
||||||
|
WiThrottle * WiThrottle::stashInstance;
|
||||||
|
byte WiThrottle::stashClient;
|
||||||
|
char WiThrottle::stashThrottleChar;
|
||||||
|
|
||||||
|
void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||||
|
stashStream->mark(stashClient);
|
||||||
|
if (locoid<0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||||
|
else {
|
||||||
|
char addcmd[20]={'M',stashThrottleChar,'+',LorS(locoid) };
|
||||||
|
itoa(locoid,addcmd+4,10);
|
||||||
|
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||||
|
}
|
||||||
|
stashStream->commit();
|
||||||
|
}
|
||||||
|
10
WiThrottle.h
10
WiThrottle.h
@@ -60,6 +60,14 @@ class WiThrottle {
|
|||||||
void multithrottle(RingStream * stream, byte * cmd);
|
void multithrottle(RingStream * stream, byte * cmd);
|
||||||
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||||
void accessory(RingStream *, byte* cmd);
|
void accessory(RingStream *, byte* cmd);
|
||||||
void checkHeartbeat();
|
void checkHeartbeat();
|
||||||
|
|
||||||
|
// callback stuff to support prog track acquire
|
||||||
|
static RingStream * stashStream;
|
||||||
|
static WiThrottle * stashInstance;
|
||||||
|
static byte stashClient;
|
||||||
|
static char stashThrottleChar;
|
||||||
|
static void getLocoCallback(int16_t locoid);
|
||||||
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -64,7 +64,7 @@ void WifiInboundHandler::loop1() {
|
|||||||
|
|
||||||
|
|
||||||
if (pendingCipsend) {
|
if (pendingCipsend) {
|
||||||
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||||
pendingCipsend=false;
|
pendingCipsend=false;
|
||||||
return;
|
return;
|
||||||
@@ -75,11 +75,11 @@ void WifiInboundHandler::loop1() {
|
|||||||
int clientId=inboundRing->read();
|
int clientId=inboundRing->read();
|
||||||
if (clientId>=0) {
|
if (clientId>=0) {
|
||||||
int count=inboundRing->count();
|
int count=inboundRing->count();
|
||||||
if (Diag::WIFI) DIAG(F("\nWifi EXEC: %d %d:"),clientId,count);
|
if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"),clientId,count);
|
||||||
byte cmd[count+1];
|
byte cmd[count+1];
|
||||||
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
|
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
|
||||||
cmd[count]=0;
|
cmd[count]=0;
|
||||||
if (Diag::WIFI) DIAG(F("%e\n"),cmd);
|
if (Diag::WIFI) DIAG(F("%e"),cmd);
|
||||||
|
|
||||||
outboundRing->mark(clientId); // remember start of outbound data
|
outboundRing->mark(clientId); // remember start of outbound data
|
||||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||||
@@ -193,11 +193,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||||||
loopState=ANYTHING;
|
loopState=ANYTHING;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength);
|
if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength);
|
||||||
if (inboundRing->freeSpace()<=(dataLength+1)) {
|
if (inboundRing->freeSpace()<=(dataLength+1)) {
|
||||||
// This input would overflow the inbound ring, ignore it
|
// This input would overflow the inbound ring, ignore it
|
||||||
loopState=IPD_IGNORE_DATA;
|
loopState=IPD_IGNORE_DATA;
|
||||||
if (Diag::WIFI) DIAG(F("\nWifi OVERFLOW IGNORING:"));
|
if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
inboundRing->mark(runningClientId);
|
inboundRing->mark(runningClientId);
|
||||||
@@ -243,7 +243,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||||||
|
|
||||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||||
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d\n"),clientPendingCIPSEND,currentReplySize);
|
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||||
pendingCipsend=false;
|
pendingCipsend=false;
|
||||||
clientPendingCIPSEND=-1;
|
clientPendingCIPSEND=-1;
|
||||||
|
@@ -26,18 +26,18 @@
|
|||||||
|
|
||||||
#include "WifiInboundHandler.h"
|
#include "WifiInboundHandler.h"
|
||||||
|
|
||||||
const char FLASH READY_SEARCH[] = "\r\nready\r\n";
|
|
||||||
const char FLASH OK_SEARCH[] = "\r\nOK\r\n";
|
|
||||||
const char FLASH END_DETAIL_SEARCH[] = "@ 1000";
|
|
||||||
const char FLASH SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
|
|
||||||
const char FLASH IPD_SEARCH[] = "+IPD";
|
|
||||||
const unsigned long LOOP_TIMEOUT = 2000;
|
const unsigned long LOOP_TIMEOUT = 2000;
|
||||||
bool WifiInterface::connected = false;
|
bool WifiInterface::connected = false;
|
||||||
Stream * WifiInterface::wifiStream;
|
Stream * WifiInterface::wifiStream;
|
||||||
|
|
||||||
#ifndef WIFI_CONNECT_TIMEOUT
|
#ifndef WIFI_CONNECT_TIMEOUT
|
||||||
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
|
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
|
||||||
#define WIFI_CONNECT_TIMEOUT 14000
|
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
|
||||||
|
// or ot will cause issues with the following commands.
|
||||||
|
#define WIFI_CONNECT_TIMEOUT 16000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -60,7 +60,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
const FSH *wifiESSID,
|
const FSH *wifiESSID,
|
||||||
const FSH *wifiPassword,
|
const FSH *wifiPassword,
|
||||||
const FSH *hostname,
|
const FSH *hostname,
|
||||||
const int port) {
|
const int port,
|
||||||
|
const byte channel) {
|
||||||
|
|
||||||
wifiSerialState wifiUp = WIFI_NOAT;
|
wifiSerialState wifiUp = WIFI_NOAT;
|
||||||
|
|
||||||
@@ -71,11 +72,12 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
(void) wifiPassword;
|
(void) wifiPassword;
|
||||||
(void) hostname;
|
(void) hostname;
|
||||||
(void) port;
|
(void) port;
|
||||||
|
(void) channel;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 0
|
#if NUM_SERIAL > 0
|
||||||
Serial1.begin(serial_link_speed);
|
Serial1.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port);
|
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Other serials are tried, depending on hardware.
|
// Other serials are tried, depending on hardware.
|
||||||
@@ -83,7 +85,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial2.begin(serial_link_speed);
|
Serial2.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
|
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial3.begin(serial_link_speed);
|
Serial3.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
|
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -109,29 +111,29 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
}
|
}
|
||||||
|
|
||||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||||
const FSH* hostname, int port) {
|
const FSH* hostname, int port, byte channel) {
|
||||||
wifiSerialState wifiState;
|
wifiSerialState wifiState;
|
||||||
static uint8_t ntry = 0;
|
static uint8_t ntry = 0;
|
||||||
ntry++;
|
ntry++;
|
||||||
|
|
||||||
wifiStream = &setupStream;
|
wifiStream = &setupStream;
|
||||||
|
|
||||||
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
|
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
|
||||||
|
|
||||||
wifiState = setup2( SSid, password, hostname, port);
|
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||||
|
|
||||||
if (wifiState == WIFI_NOAT) {
|
if (wifiState == WIFI_NOAT) {
|
||||||
DIAG(F("\n++ Wifi Setup NO AT ++\n"));
|
DIAG(F("++ Wifi Setup NO AT ++"));
|
||||||
return wifiState;
|
return wifiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wifiState == WIFI_CONNECTED) {
|
if (wifiState == WIFI_CONNECTED) {
|
||||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||||
checkForOK(200, OK_SEARCH, true);
|
checkForOK(200, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DIAG(F("\n++ Wifi Setup %S ++\n"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
||||||
return wifiState;
|
return wifiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,45 +143,44 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
|||||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
#endif
|
#endif
|
||||||
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||||
const FSH* hostname, int port) {
|
const FSH* hostname, int port, byte channel) {
|
||||||
bool ipOK = false;
|
bool ipOK = false;
|
||||||
bool oldCmd = false;
|
bool oldCmd = false;
|
||||||
|
|
||||||
char macAddress[17]; // mac address extraction
|
char macAddress[17]; // mac address extraction
|
||||||
|
|
||||||
// First check... Restarting the Arduino does not restart the ES.
|
// First check... Restarting the Arduino does not restart the ES.
|
||||||
// There may alrerady be a connection with data in the pipeline.
|
// There may alrerady be a connection with data in the pipeline.
|
||||||
// If there is, just shortcut the setup and continue to read the data as normal.
|
// If there is, just shortcut the setup and continue to read the data as normal.
|
||||||
if (checkForOK(200,IPD_SEARCH, true)) {
|
if (checkForOK(200,F("+IPD"), true)) {
|
||||||
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
|
DIAG(F("Preconfigured Wifi already running with data waiting"));
|
||||||
// loopstate=4; // carry on from correct place... or not as the case may be
|
|
||||||
return WIFI_CONNECTED;
|
return WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
||||||
if(!checkForOK(200, OK_SEARCH, true))
|
if(!checkForOK(200, true))
|
||||||
return WIFI_NOAT; // No AT compatible WiFi module here
|
return WIFI_NOAT; // No AT compatible WiFi module here
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
|
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
|
||||||
checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console
|
checkForOK(2000, true); // Makes this visible on the console
|
||||||
|
|
||||||
// Display the AT version information
|
// Display the AT version information
|
||||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||||
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
|
checkForOK(2000, true, false); // Makes this visible on the console
|
||||||
|
|
||||||
#ifdef DONT_TOUCH_WIFI_CONF
|
#ifdef DONT_TOUCH_WIFI_CONF
|
||||||
DIAG(F("\nDONT_TOUCH_WIFI_CONF was set: Using existing config\n"));
|
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
|
||||||
#else
|
#else
|
||||||
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
|
||||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
|
||||||
|
|
||||||
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR?\r\n"));
|
||||||
if (checkForOK(2000, OK_SEARCH, true)) {
|
if (!(checkForOK(2000, true))) {
|
||||||
oldCmd=true;
|
oldCmd=true;
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringFormatter::send(wifiStream, F("AT+CWMODE%s=1\r\n"), oldCmd ? "" : "_CUR"); // configure as "station" = WiFi client
|
||||||
|
checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||||
|
|
||||||
const char *yourNetwork = "Your network ";
|
const char *yourNetwork = "Your network ";
|
||||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
||||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||||
@@ -191,55 +192,55 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||||||
// typical connect time approx 7 seconds
|
// typical connect time approx 7 seconds
|
||||||
delay(8000);
|
delay(8000);
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
|
||||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||||
ipOK = true;
|
ipOK = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// SSID was configured, so we assume station (client) mode.
|
// SSID was configured, so we assume station (client) mode.
|
||||||
if (oldCmd) {
|
if (oldCmd) {
|
||||||
// AT command early version supports CWJAP/CWSAP
|
// AT command early version supports CWJAP/CWSAP
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||||
} else {
|
} else {
|
||||||
// later version supports CWJAP_CUR
|
// later version supports CWJAP_CUR
|
||||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||||
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
checkForOK(2000, true); // dont care if not supported
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ipOK) {
|
if (ipOK) {
|
||||||
// But we really only have the ESSID and password correct
|
// But we really only have the ESSID and password correct
|
||||||
// Let's check for IP (via DHCP)
|
// Let's check for IP (via DHCP)
|
||||||
ipOK = false;
|
ipOK = false;
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
|
||||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||||
ipOK = true;
|
ipOK = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ipOK) {
|
if (!ipOK) {
|
||||||
// If we have not managed to get this going in station mode, go for AP mode
|
// If we have not managed to get this going in station mode, go for AP mode
|
||||||
|
|
||||||
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
||||||
// checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
// checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||||
|
|
||||||
int i=0;
|
int i=0;
|
||||||
do {
|
do {
|
||||||
// configure as AccessPoint. Try really hard as this is the
|
// configure as AccessPoint. Try really hard as this is the
|
||||||
// last way out to get any Wifi connectivity.
|
// last way out to get any Wifi connectivity.
|
||||||
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CWMODE%s=2\r\n"), oldCmd ? "" : "_CUR");
|
||||||
} while (!checkForOK(1000+i*500, OK_SEARCH, true) && i++<10);
|
} while (!checkForOK(1000+i*500, true) && i++<10);
|
||||||
|
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||||
|
|
||||||
// Figure out MAC addr
|
// Figure out MAC addr
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
|
||||||
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) {
|
||||||
// Copy 17 byte mac address
|
// Copy 17 byte mac address
|
||||||
for (int i=0; i<17;i++) {
|
for (int i=0; i<17;i++) {
|
||||||
while(!wifiStream->available());
|
while(!wifiStream->available());
|
||||||
@@ -251,39 +252,41 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||||||
}
|
}
|
||||||
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
||||||
|
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
checkForOK(1000, true, false); // suck up remainder of AT+CIFSR
|
||||||
|
|
||||||
i=0;
|
i=0;
|
||||||
do {
|
do {
|
||||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||||
// unconfigured
|
// unconfigured
|
||||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), oldCmd ? "" : "_CUR", macTail, macTail);
|
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||||
|
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||||
} else {
|
} else {
|
||||||
// password configured by user
|
// password configured by user
|
||||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",1,4\r\n"), oldCmd ? "" : "_CUR", macTail, password);
|
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||||
|
macTail, password, channel);
|
||||||
}
|
}
|
||||||
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||||
if (i >= 2)
|
if (i >= 2)
|
||||||
DIAG(F("\nWarning: Setting AP SSID and password failed\n")); // but issue warning
|
DIAG(F("Warning: Setting AP SSID and password failed")); // but issue warning
|
||||||
|
|
||||||
if (!oldCmd) {
|
if (!oldCmd) {
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
||||||
checkForOK(2000, OK_SEARCH, true);
|
checkForOK(2000, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||||
checkForOK(1000, OK_SEARCH, true); // ignore result in case it already was off
|
checkForOK(1000, true); // ignore result in case it already was off
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
|
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
|
||||||
if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||||
if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||||
#endif //DONT_TOUCH_WIFI_CONF
|
#endif //DONT_TOUCH_WIFI_CONF
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||||
if (!checkForOK(1000, (const char *)F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
||||||
// Copy the IP address
|
// Copy the IP address
|
||||||
{
|
{
|
||||||
const byte MAX_IP_LENGTH=15;
|
const byte MAX_IP_LENGTH=15;
|
||||||
@@ -302,8 +305,8 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
|||||||
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
|
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
|
||||||
}
|
}
|
||||||
// suck up anything after the IP.
|
// suck up anything after the IP.
|
||||||
if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED;
|
||||||
LCD(5,F("PORT=%d\n"),port);
|
LCD(5,F("PORT=%d"),port);
|
||||||
|
|
||||||
return WIFI_CONNECTED;
|
return WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
@@ -321,38 +324,42 @@ void WifiInterface::ATCommand(const byte * command) {
|
|||||||
command++;
|
command++;
|
||||||
if (*command=='X') {
|
if (*command=='X') {
|
||||||
connected = true;
|
connected = true;
|
||||||
DIAG(F("\n++++++ Wifi Connction forced on ++++++++\n"));
|
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
||||||
checkForOK(10000, OK_SEARCH, true);
|
checkForOK(10000, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
|
bool WifiInterface::checkForOK( const unsigned int timeout, bool echo, bool escapeEcho) {
|
||||||
|
return checkForOK(timeout,F("\r\nOK\r\n"),echo,escapeEcho);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, bool echo, bool escapeEcho) {
|
||||||
unsigned long startTime = millis();
|
unsigned long startTime = millis();
|
||||||
char const *locator = waitfor;
|
char *locator = (char *)waitfor;
|
||||||
DIAG(F("\nWifi Check: [%E]"), waitfor);
|
DIAG(F("Wifi Check: [%E]"), waitfor);
|
||||||
while ( millis() - startTime < timeout) {
|
while ( millis() - startTime < timeout) {
|
||||||
while (wifiStream->available()) {
|
while (wifiStream->available()) {
|
||||||
int ch = wifiStream->read();
|
int ch = wifiStream->read();
|
||||||
if (echo) {
|
if (echo) {
|
||||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||||
else DIAG(F("%c"), ch);
|
else StringFormatter::diagSerial->print((char)ch);
|
||||||
}
|
}
|
||||||
if (ch != GETFLASH(locator)) locator = waitfor;
|
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||||
if (ch == GETFLASH(locator)) {
|
if (ch == GETFLASH(locator)) {
|
||||||
locator++;
|
locator++;
|
||||||
if (!GETFLASH(locator)) {
|
if (!GETFLASH(locator)) {
|
||||||
DIAG(F("\nFound in %dms"), millis() - startTime);
|
DIAG(F("Found in %dms"), millis() - startTime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DIAG(F("\nTIMEOUT after %dms\n"), timeout);
|
DIAG(F("TIMEOUT after %dms"), timeout);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -34,23 +34,20 @@ public:
|
|||||||
const FSH *wifiESSID,
|
const FSH *wifiESSID,
|
||||||
const FSH *wifiPassword,
|
const FSH *wifiPassword,
|
||||||
const FSH *hostname,
|
const FSH *hostname,
|
||||||
const int port = 2560);
|
const int port,
|
||||||
|
const byte channel);
|
||||||
static void loop();
|
static void loop();
|
||||||
static void ATCommand(const byte *command);
|
static void ATCommand(const byte *command);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||||
const FSH *hostname, int port);
|
const FSH *hostname, int port, byte channel);
|
||||||
static Stream *wifiStream;
|
static Stream *wifiStream;
|
||||||
static DCCEXParser parser;
|
static DCCEXParser parser;
|
||||||
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||||
const FSH *hostname, int port);
|
const FSH *hostname, int port, byte channel);
|
||||||
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
|
||||||
|
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||||
static bool connected;
|
static bool connected;
|
||||||
static bool closeAfter;
|
|
||||||
static byte loopstate;
|
|
||||||
static int datalength;
|
|
||||||
static int connectionId;
|
|
||||||
static unsigned long loopTimeoutStart;
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021, Chris Harlow. All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is part of DCC-EX/CommandStation-EX
|
|
||||||
*
|
|
||||||
* This is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* It is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#ifdef ARDUINO_AVR_UNO_WIFI_REV2
|
|
||||||
// This code is ONLY compiled on a unoWifiRev2 processor which uses a different architecture
|
|
||||||
|
|
||||||
#include "WifiInterfaceRev2.h"
|
|
||||||
#include "DIAG.h"
|
|
||||||
#include "CommandDistributor.h"
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <WiFiNINA.h>
|
|
||||||
|
|
||||||
|
|
||||||
WiFiServer WifiInterface::server(2560);
|
|
||||||
bool WifiInterface::connected=false;
|
|
||||||
/**
|
|
||||||
* @brief Setup Wifi Connection
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool WifiInterface::setup(long serial_link_speed,
|
|
||||||
const FSH *wifiESSID,
|
|
||||||
const FSH *wifiPassword,
|
|
||||||
const FSH *hostname,
|
|
||||||
const int port) {
|
|
||||||
(void)serial_link_speed;
|
|
||||||
(void)port; // obsolete
|
|
||||||
(void)hostname; // To be implemented
|
|
||||||
|
|
||||||
if (WiFi.status() == WL_NO_MODULE) {
|
|
||||||
DIAG(F("Wifi- hardware failed\n"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DIAG(F("Wifi Firmware=%s expected=%S"),WiFi.firmwareVersion(),F(WIFI_FIRMWARE_LATEST_VERSION));
|
|
||||||
|
|
||||||
|
|
||||||
int status = WL_IDLE_STATUS;
|
|
||||||
int attempts = 4;
|
|
||||||
while (status != WL_CONNECTED) {
|
|
||||||
if (attempts-- <= 0) {
|
|
||||||
DIAG(F("\nFAILED - No Wifi\n"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DIAG(F("\nAttempting to connect to %s\n"),wifiESSID);
|
|
||||||
status = WiFi.begin(wifiESSID, wifiPassword);
|
|
||||||
// wait 10 seconds for connection:
|
|
||||||
delay(10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
server.begin(); // start the server on port 2560
|
|
||||||
|
|
||||||
IPAddress ip = WiFi.localIP();
|
|
||||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
|
||||||
LCD(5,F("Port:2560"));
|
|
||||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Main loop for the WifiInterfaceRev2
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void WifiInterface::loop()
|
|
||||||
{
|
|
||||||
WiFiClient client = server.available(); // listen for incoming clients
|
|
||||||
if (client)
|
|
||||||
{
|
|
||||||
// read bytes from a client
|
|
||||||
byte buffer[MAX_NINA_BUFFER];
|
|
||||||
int count = client.read(buffer, MAX_NINA_BUFFER-1);
|
|
||||||
buffer[count] = '\0'; // terminate the string properly
|
|
||||||
if (Diag::WIFI) DIAG(F("WIFI:%e\n"), buffer);
|
|
||||||
// TEMPORARY - Assume all clients are client 1, this will confuse WiThrottle!
|
|
||||||
outboundRing->mark(1);
|
|
||||||
// TEMPORARY - Assume all clients are client 1, this will confuse WiThrottle!
|
|
||||||
CommandDistributor::parse(1,buffer,outboundRing);
|
|
||||||
outboundRing->commit();
|
|
||||||
int socketOut=outboundRing->read();
|
|
||||||
if (socketOut>=0) {
|
|
||||||
int count=outboundRing->count();
|
|
||||||
if (Diag::WIFI) DIAG(F("Wifi Reply count=:%d\n"), count);
|
|
||||||
for(;count>0;count--) client.write(outboundRing->read());
|
|
||||||
client.flush(); //maybe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2021, 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 WifiInterface_h
|
|
||||||
#define WifiInterface_h
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <WiFiNINA.h>
|
|
||||||
#include "FSH.h"
|
|
||||||
#include "RingStream.h"
|
|
||||||
|
|
||||||
#define MAX_NINA_BUFFER 512
|
|
||||||
#define OUTBOUND_RING_SIZE 2048
|
|
||||||
|
|
||||||
class WifiInterface
|
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
|
||||||
static bool setup(long serial_link_speed, // ignored
|
|
||||||
const FSH *wifiESSID,
|
|
||||||
const FSH *wifiPassword,
|
|
||||||
const FSH *hostname,
|
|
||||||
const int port = 2560); // ignored
|
|
||||||
static void loop();
|
|
||||||
private:
|
|
||||||
static WiFiServer server;
|
|
||||||
static bool connected;
|
|
||||||
static RingStream * outboundRing;
|
|
||||||
};
|
|
||||||
#endif
|
|
@@ -18,13 +18,20 @@ The configuration file for DCC-EX Command Station
|
|||||||
//
|
//
|
||||||
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||||
|
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
|
||||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||||
// FIREBOX_MK1 : The Firebox MK1
|
// FIREBOX_MK1 : The Firebox MK1
|
||||||
// FIREBOX_MK1S : The Firebox MK1S
|
// FIREBOX_MK1S : The Firebox MK1S
|
||||||
|
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
|
||||||
// |
|
// |
|
||||||
// +-----------------------v
|
// +-----------------------v
|
||||||
//
|
//
|
||||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// The IP port to talk to a WIFI or Ethernet shield.
|
||||||
|
//
|
||||||
|
#define IP_PORT 2560
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -54,7 +61,7 @@ The configuration file for DCC-EX Command Station
|
|||||||
// is set (recommended), that password will be used for AP mode.
|
// is set (recommended), that password will be used for AP mode.
|
||||||
// The AP mode password must be at least 8 characters long.
|
// The AP mode password must be at least 8 characters long.
|
||||||
//
|
//
|
||||||
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
|
// Your SSID may not contain ``"'' (double quote, ASCII 0x22).
|
||||||
#define WIFI_SSID "Your network name"
|
#define WIFI_SSID "Your network name"
|
||||||
//
|
//
|
||||||
// WIFI_PASSWORD is the network password for your home network or if
|
// WIFI_PASSWORD is the network password for your home network or if
|
||||||
@@ -66,12 +73,11 @@ The configuration file for DCC-EX Command Station
|
|||||||
// WIFI_HOSTNAME: You probably don't need to change this
|
// WIFI_HOSTNAME: You probably don't need to change this
|
||||||
#define WIFI_HOSTNAME "dccex"
|
#define WIFI_HOSTNAME "dccex"
|
||||||
//
|
//
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented,
|
||||||
//
|
// WiFi will be enabled (Mega only). The default channel is set to "1" whether
|
||||||
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
|
// this line exists or not. If you need to use an alternate channel (we recommend
|
||||||
// to set this if you have an extremely slow Wifi router.
|
// using only 1,6, or 11) you may change it here.
|
||||||
//
|
#define WIFI_CHANNEL 1
|
||||||
//#define WIFI_CONNECT_TIMEOUT 14000
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -100,11 +106,10 @@ The configuration file for DCC-EX Command Station
|
|||||||
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
||||||
// #define LCD_DRIVER 0x3F,16,2
|
// #define LCD_DRIVER 0x3F,16,2
|
||||||
|
|
||||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||||
// This will not work on a UNO due to memory constraints
|
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||||
|
// Also 132x64 I2C SH1106 devices.
|
||||||
// #define OLED_DRIVER 128,32
|
// #define OLED_DRIVER 128,32
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
|
||||||
// Enable warning as memory gets depleted
|
|
||||||
#define ENABLE_FREE_MEM_WARNING false
|
|
||||||
|
15
defines.h
15
defines.h
@@ -21,20 +21,27 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// WIFI_ON: All prereqs for running with WIFI are met
|
// WIFI_ON: All prereqs for running with WIFI are met
|
||||||
//
|
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
||||||
|
|
||||||
#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO))
|
#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
||||||
#define WIFI_ON true
|
#define WIFI_ON true
|
||||||
|
#ifndef WIFI_CHANNEL
|
||||||
|
#define WIFI_CHANNEL 1
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define WIFI_ON false
|
#define WIFI_ON false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO))
|
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
||||||
#define ETHERNET_ON true
|
#define ETHERNET_ON true
|
||||||
#else
|
#else
|
||||||
#define ETHERNET_ON false
|
#define ETHERNET_ON false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if WIFI_ON && ETHERNET_ON
|
||||||
|
#error Command Station does not support WIFI and ETHERNET at the same time.
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// This defines the speed at which the Arduino will communicate with the ESP8266 module.
|
// This defines the speed at which the Arduino will communicate with the ESP8266 module.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Harald Barth
|
* © 2020, Harald Barth
|
||||||
|
* © 2021, Neil McKechnie
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC-EX
|
* This file is part of Asbelos DCC-EX
|
||||||
*
|
*
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
#include "freeMemory.h"
|
#include "freeMemory.h"
|
||||||
|
|
||||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||||
@@ -30,13 +32,80 @@ extern char *__malloc_heap_start;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
int freeMemory() {
|
static volatile int minimum_free_memory = __INT_MAX__;
|
||||||
|
|
||||||
|
#if !defined(__IMXRT1062__)
|
||||||
|
static inline int freeMemory() {
|
||||||
char top;
|
char top;
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||||
#elif defined(__AVR__)
|
#elif defined(__AVR__)
|
||||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||||
#else
|
#else
|
||||||
#error bailed out alredy above
|
#error bailed out already above
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return low memory value.
|
||||||
|
int minimumFreeMemory() {
|
||||||
|
byte sreg_save = SREG;
|
||||||
|
noInterrupts(); // Disable interrupts
|
||||||
|
int retval = minimum_free_memory;
|
||||||
|
SREG = sreg_save; // Restore interrupt state
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#if defined(ARDUINO_TEENSY40)
|
||||||
|
static const unsigned DTCM_START = 0x20000000UL;
|
||||||
|
static const unsigned OCRAM_START = 0x20200000UL;
|
||||||
|
static const unsigned OCRAM_SIZE = 512;
|
||||||
|
static const unsigned FLASH_SIZE = 1984;
|
||||||
|
#elif defined(ARDUINO_TEENSY41)
|
||||||
|
static const unsigned DTCM_START = 0x20000000UL;
|
||||||
|
static const unsigned OCRAM_START = 0x20200000UL;
|
||||||
|
static const unsigned OCRAM_SIZE = 512;
|
||||||
|
static const unsigned FLASH_SIZE = 7936;
|
||||||
|
#if TEENSYDUINO>151
|
||||||
|
extern "C" uint8_t external_psram_size;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline int freeMemory() {
|
||||||
|
extern unsigned long _ebss;
|
||||||
|
extern unsigned long _sdata;
|
||||||
|
extern unsigned long _estack;
|
||||||
|
const unsigned DTCM_START = 0x20000000UL;
|
||||||
|
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||||
|
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||||
|
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||||
|
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||||
|
return freemem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return low memory value.
|
||||||
|
int minimumFreeMemory() {
|
||||||
|
//byte sreg_save = SREG;
|
||||||
|
//noInterrupts(); // Disable interrupts
|
||||||
|
int retval = minimum_free_memory;
|
||||||
|
//SREG = sreg_save; // Restore interrupt state
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Update low ram level. Allow for extra bytes to be specified
|
||||||
|
// by estimation or inspection, that may be used by other
|
||||||
|
// called subroutines. Must be called with interrupts disabled.
|
||||||
|
//
|
||||||
|
// Although __brkval may go up and down as heap memory is allocated
|
||||||
|
// and freed, this function records only the worst case encountered.
|
||||||
|
// So even if all of the heap is freed, the reported minimum free
|
||||||
|
// memory will not increase.
|
||||||
|
//
|
||||||
|
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||||
|
int spare = freeMemory()-extraBytes;
|
||||||
|
if (spare < 0) spare = 0;
|
||||||
|
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Harald Barth
|
* © 2020, Harald Barth
|
||||||
|
* © 2021, Neil McKechnie
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX
|
* This file is part of DCC-EX
|
||||||
*
|
*
|
||||||
@@ -19,5 +20,6 @@
|
|||||||
|
|
||||||
#ifndef freeMemory_h
|
#ifndef freeMemory_h
|
||||||
#define freeMemory_h
|
#define freeMemory_h
|
||||||
int freeMemory();
|
void updateMinimumFreeMemory(unsigned char extraBytes=0);
|
||||||
|
int minimumFreeMemory();
|
||||||
#endif
|
#endif
|
||||||
|
@@ -12,5 +12,5 @@ ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
|||||||
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||||
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||||
notepad %TMP%\OBJDUMP_%a%.txt
|
%TMP%\OBJDUMP_%a%.txt
|
||||||
EXIT
|
EXIT
|
||||||
|
@@ -33,13 +33,8 @@ board = megaatmega2560
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
DIO2
|
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
SPI
|
SPI
|
||||||
marcoschwartz/LiquidCrystal_I2C
|
|
||||||
Adafruit/Adafruit_BusIO
|
|
||||||
Adafruit/Adafruit_SSD1306
|
|
||||||
Adafruit/Adafruit-GFX-Library
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_flags = --echo
|
monitor_flags = --echo
|
||||||
|
|
||||||
@@ -49,10 +44,8 @@ board = uno
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
DIO2
|
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
SPI
|
SPI
|
||||||
marcoschwartz/LiquidCrystal_I2C
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_flags = --echo
|
monitor_flags = --echo
|
||||||
|
|
||||||
@@ -62,10 +55,8 @@ board = uno_wifi_rev2
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
DIO2
|
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
SPI
|
SPI
|
||||||
marcoschwartz/LiquidCrystal_I2C
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_flags = --echo
|
monitor_flags = --echo
|
||||||
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g
|
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g
|
||||||
@@ -76,9 +67,7 @@ board = uno
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
DIO2
|
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
SPI
|
SPI
|
||||||
marcoschwartz/LiquidCrystal_I2C
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_flags = --echo
|
monitor_flags = --echo
|
||||||
|
206
release_notes.md
Normal file
206
release_notes.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||||
|
|
||||||
|
**Known Issues**
|
||||||
|
|
||||||
|
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||||
|
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
|
||||||
|
|
||||||
|
#### Summary of key features and/or bug fixes by Point Release
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||||
|
|
||||||
|
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||||
|
|
||||||
|
- Send function commands just once instead of repeating them 4 times
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||||
|
|
||||||
|
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||||
|
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||||
|
|
||||||
|
- Fix for CAB Functions greater than 127
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||||
|
|
||||||
|
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||||
|
|
||||||
|
- Reorganized files for support of 128 speed steps
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||||
|
|
||||||
|
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||||
|
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||||
|
|
||||||
|
- Rearranges serial newlines for the benefit of JMRI
|
||||||
|
- Major update for efficiencies in displays (LCD, OLED)
|
||||||
|
- Add I2C Support functions
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||||
|
|
||||||
|
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||||
|
|
||||||
|
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||||
|
- Improved overload messages with raw values (relative to offset)
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||||
|
|
||||||
|
- Prevent compiler warning about deprecated B constants
|
||||||
|
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||||
|
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||||
|
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||||
|
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||||
|
- `<D RESET>` command to reboot Arduino
|
||||||
|
- Automatic sensor offset detect
|
||||||
|
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||||
|
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||||
|
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||||
|
|
||||||
|
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||||
|
- Removed ethernet mac config and made it automatic
|
||||||
|
- Show wifi ip and port on lcd
|
||||||
|
- Auto load config.example.h with warning
|
||||||
|
- Dropped example .ino files
|
||||||
|
- Corrected .ino comments
|
||||||
|
- Add Pololu fault pin handling
|
||||||
|
- Waveform speed/simplicity improvements
|
||||||
|
- Improved pin speed in waveform
|
||||||
|
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||||
|
- Analog read speed improvements
|
||||||
|
- Drop need for DIO2 library
|
||||||
|
- Improved current check code
|
||||||
|
- Linear command
|
||||||
|
- Removed need for ArduinoTimers files
|
||||||
|
- Removed option to choose different timer
|
||||||
|
- Added EX-RAIL hooks for automation in future version
|
||||||
|
- Fixed Turnout list
|
||||||
|
- Allow command keywords in mixed case
|
||||||
|
- Dropped unused memstream
|
||||||
|
- PWM pin accuracy if requirements met
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||||
|
|
||||||
|
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||||
|
- WiFi Startup Fixes
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||||
|
|
||||||
|
- Command to write loco address and clear consist
|
||||||
|
- Command will allow for consist address
|
||||||
|
- Startup commands implemented
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||||
|
|
||||||
|
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||||
|
- Simultaneously update JMRI to handle new current meter
|
||||||
|
|
||||||
|
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||||
|
|
||||||
|
- Add back fix for jitter
|
||||||
|
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||||
|
|
||||||
|
**CommandStation-EX V3.0.0:**
|
||||||
|
|
||||||
|
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||||
|
|
||||||
|
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||||
|
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||||
|
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||||
|
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||||
|
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||||
|
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||||
|
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||||
|
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||||
|
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||||
|
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||||
|
- **Functions to F28** - All NMRA functions are now supported
|
||||||
|
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||||
|
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||||
|
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||||
|
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||||
|
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||||
|
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||||
|
- **Fix EEPROM bugs**
|
||||||
|
- **Number of locos discovery command** - `<#>` command
|
||||||
|
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||||
|
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||||
|
|
||||||
|
**Key Contributors**
|
||||||
|
|
||||||
|
**Project Lead**
|
||||||
|
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
|
||||||
|
**CommandStation-EX Developers**
|
||||||
|
|
||||||
|
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||||
|
- Harald Barth - Stockholm, Sweden (Haba)
|
||||||
|
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- M Steve Todd -
|
||||||
|
- Scott Catalano - Pennsylvania
|
||||||
|
- Gregor Baues - Île-de-France, France (grbba)
|
||||||
|
|
||||||
|
**Engine Driver and JMRI Interface**
|
||||||
|
|
||||||
|
- M Steve Todd
|
||||||
|
|
||||||
|
**exInstaller Software**
|
||||||
|
|
||||||
|
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||||
|
|
||||||
|
**Website and Documentation**
|
||||||
|
|
||||||
|
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||||
|
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||||
|
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||||
|
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||||
|
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||||
|
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||||
|
|
||||||
|
**WebThrotle-EX**
|
||||||
|
|
||||||
|
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||||
|
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||||
|
- Matt H - Somewhere in Europe
|
||||||
|
|
||||||
|
**Beta Testing / Release Management / Support**
|
||||||
|
|
||||||
|
- Larry Dribin - Release Management
|
||||||
|
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||||
|
- Keith Ledbetter
|
||||||
|
- BradVan der Elst
|
||||||
|
- Andrew Pye
|
||||||
|
- Mike Bowers
|
||||||
|
- Randy McKenzie
|
||||||
|
- Roberto Bravin
|
||||||
|
- Sim Brigden
|
||||||
|
- Alan Lautenslager
|
||||||
|
- Martin Bafver
|
||||||
|
- Mário André Silva
|
||||||
|
- Anthony Kochevar
|
||||||
|
- Gajanatha Kobbekaduwe
|
||||||
|
- Sumner Patterson
|
||||||
|
- Paul - Virginia, USA
|
||||||
|
|
||||||
|
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||||
|
|
||||||
|
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||||
|
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
16
version.h
16
version.h
@@ -3,7 +3,21 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "3.0.4"
|
#define VERSION "3.1.0"
|
||||||
|
// 3.0.16 Ignore CV1 bit 7 read rejected by decoder when identifying loco id.
|
||||||
|
// 3.0.15 only send function commands once, not 4 times
|
||||||
|
// 3.0.14 gap in ack tolerant fix, prog track power management over join fix.
|
||||||
|
// 3.0.13 Functions>127 fix
|
||||||
|
// 3.0.12 Fix HOSTNAME function for STA mode for WiFi
|
||||||
|
// 3.0.11 28 speedstep support
|
||||||
|
// 3.0.10 Teensy Support
|
||||||
|
// 3.0.9 rearranges serial newlines for the benefit of JMRI.
|
||||||
|
// 3.0.8 Includes <* *> wraps around DIAGs for the benefit of JMRI.
|
||||||
|
// 3.0.7 Includes merge from assortedBits (many changes) and ACK manager change for lazy decoders
|
||||||
|
// 3.0.6 Includes:
|
||||||
|
// Fix Bug that did not let us transmit 5 byte sized packets like PoM
|
||||||
|
// 3.0.5 Includes:
|
||||||
|
// Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||||
// 3.0.4 Includes:
|
// 3.0.4 Includes:
|
||||||
// Wifi startup bugfixes
|
// Wifi startup bugfixes
|
||||||
// 3.0.3 Includes:
|
// 3.0.3 Includes:
|
||||||
|
Reference in New Issue
Block a user