1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-25 00:56:13 +01:00

Compare commits

...

172 Commits

Author SHA1 Message Date
Harald Barth
9f38dae8ba Check bad AT firmware version 2023-08-11 00:07:02 +02:00
Harald Barth
e0e965f81c Merge branch 'master' into devel 2023-08-07 19:41:00 +02:00
Harald Barth
f2be3aeac3 Make <!> work in DC mode 2023-08-04 14:45:05 +02:00
Harald Barth
a74d85e895 Rename track mode OFF to NONE 2023-08-02 10:00:43 +02:00
Harald Barth
df2e651217 version, compile warning 2023-08-02 01:12:32 +02:00
Harald Barth
36d139268d AVR: Pin specific timer register seting for speed and mode when inrush throttling and for DC PWM 2023-08-02 01:05:31 +02:00
Harald Barth
e3ac3a8ddf Protect Uno user from choosing DC(X) 2023-08-02 01:02:46 +02:00
pmantoine
415e756020 More Nucleo variant defines 2023-07-31 16:51:25 +08:00
kempe63
f754fe2fbf GPIO PCA9555 / TCA9555 support
My 1st commit, be gentle
2023-07-29 20:34:39 +01:00
Harald Barth
399030d8ae make variable frequency a compile option 2023-07-25 12:51:23 +02:00
Harald Barth
4c7e11ddc1 version 2023-07-25 11:30:08 +02:00
Harald Barth
495bbf66bf better variable name 2023-07-25 11:23:36 +02:00
Harald Barth
2950ef010a diag 2023-07-18 01:25:38 +02:00
Harald Barth
c2eb5f23b4 restrict to relevant TRACK_MODE(s) 2023-07-17 09:42:39 +02:00
Harald Barth
94648ead28 versiontag 2023-07-17 02:31:00 +02:00
Harald Barth
ec0499e9da throttleInrush() (tested on ESP32) 2023-07-17 02:30:11 +02:00
Harald Barth
9b75026eef change from trackMode[t] to track[t]->{get,set}Mode 2023-07-17 02:26:29 +02:00
Harald Barth
6036ff9b15 ESP32: ledcSetup before ledcAttach 2023-07-17 02:22:35 +02:00
Harald Barth
6476a7aac2 version 2023-07-14 23:11:22 +02:00
Harald Barth
0edf34bfe2 inrush test ESP32 only 2023-07-14 23:10:50 +02:00
Harald Barth
aa1f25fc72 Set WIFI_FORCE_AP default as false 2023-07-09 12:04:40 +02:00
Harald Barth
b44bebc1c6 copyright, version and compile warnings fix 2023-07-08 08:58:00 +02:00
habazut
1a17cdb62f
Merge pull request #340 from nathankellenicki/devel
[Feat] Added WIFI_FORCE_AP option to config
2023-07-08 08:46:34 +02:00
Harald Barth
7ce1618a9c Merge branch 'devel-overload' into devel 2023-07-07 21:52:55 +02:00
Harald Barth
4192c1f5a3 Do not invoke graphical install on Raspbian 2023-07-06 16:58:36 +02:00
Harald Barth
c2fcdddd1f ESP32 protect from race in RMT code 2023-07-06 15:19:44 +02:00
Harald Barth
f19db3aa5c DISABLE_PROG does count as less RAM as well 2023-07-04 16:25:15 +02:00
Harald Barth
e6a40e622c download graphic installer if DISPLAY 2023-07-03 23:43:21 +02:00
Nathan Kellenicki
b3251e89d7 Fixed Arduino 2023-07-02 19:51:29 -07:00
Nathan Kellenicki
ae2bbbf668 Added WIFI_FORCE_AP to force AP mode when specifying SSID/pass 2023-07-02 19:51:29 -07:00
Harald Barth
96a46f36c2 Adjust overcurrent timeouts 2023-07-03 00:21:52 +02:00
Harald Barth
10c59028e1 Add documentation 2023-07-02 20:33:29 +02:00
Harald Barth
ab1356d070 Change first join/unjoin and set power after that 2023-07-02 13:55:56 +02:00
Harald Barth
70d4c016ef completely new overcurrent detection 2023-07-02 01:33:41 +02:00
peteGSX
efe96d1d84
Merge pull request #339 from DCC-EX:fix-re-send
RotaryEnoder, EX-Turntable fixes
2023-06-30 12:24:30 -07:00
peteGSX
5d17f247de RotaryEnoder, EX-Turntable fixes 2023-07-01 05:18:45 +10:00
Harald Barth
7c41ec7c25 version tag 2023-06-30 02:06:12 +02:00
Harald Barth
9c5e48c3d5 test more tolerant alg 2023-06-30 02:05:10 +02:00
Fred
2eb0f48994
Update .gitignore
Updated .gitignore from the devel branch and used "my@.cpp" instead of listing the individual files so we can ignore anything that starts with "my"
2023-06-29 14:24:51 -04:00
pmantoine
1bdb05a471 ESP32 now sets hostname to dccex in STA mode 2023-06-29 11:00:14 +08:00
Harald Barth
c2fa76c76a version 2.4.61 2023-06-26 20:01:56 +02:00
Harald Barth
35fd912c60 MAX_CURRENT restriction (caps motor shield value) 2023-06-26 20:00:03 +02:00
Harald Barth
dfba6c6fc1 version tag 2023-06-23 13:55:34 +02:00
Harald Barth
f3cb263aaa convert mac addr hex chars to lower case to be compatible with AT software 2023-06-23 13:54:25 +02:00
pmantoine
ec6e730559 ESP32 mDNS registration for throttle autodiscovery 2023-06-23 18:08:05 +08:00
Fred
196a27a27a
Update WifiESP32.cpp (#338)
* Update WifiESP32.cpp

Fix SSID for AP from DCC_ to DCCEX_

* Update version.h to 4.2.59
2023-06-22 19:47:20 -04:00
Harald Barth
99b6ca025a move ADCee begin as well 2023-06-22 23:30:28 +02:00
Harald Barth
db555e8820 Start motordriver as soon as possible but without waveform 2023-06-22 22:57:59 +02:00
Harald Barth
0d679ad993 version 2023-06-21 10:49:49 +02:00
Harald Barth
dd80260781 add common fault pin handling to new overload code 2023-06-21 10:44:57 +02:00
Harald Barth
08f41415dc format option to write microseconds 2023-06-21 10:43:41 +02:00
Harald Barth
2f65d4347e Merge branch 'devel-overcurrent' into devel 2023-06-20 21:17:28 +02:00
peteGSX
995c6f8ede Update version 2023-06-20 19:32:43 +10:00
peteGSX
4331ddfdf0
Merge pull request #337 from DCC-EX:rotary-encoder-send-position
Rotary-encoder-send-position
2023-06-20 02:30:40 -07:00
peteGSX
2b8b995307 Updated logic, sending move 2023-06-20 19:24:49 +10:00
peteGSX
2af01e3c42 Add ready flag 2023-06-20 12:48:13 +10:00
peteGSX
73b45ba9b8 Merge branch 'rotary-encoder-send-position' of https://github.com/DCC-EX/CommandStation-EX into rotary-encoder-send-position 2023-06-20 10:54:50 +10:00
peteGSX
247cea6dc1 Update some logic 2023-06-20 10:54:40 +10:00
peteGSX
c932325120 Add _writeAnalogue() 2023-06-20 10:54:40 +10:00
pmantoine
988011475c STM32 Serial port handling for WiFi 2023-06-20 08:34:27 +08:00
peteGSX
0be9af2270 Update some logic 2023-06-20 09:37:19 +10:00
pmantoine
c3eb6b8d8a STM32 ADCee highestPin 2023-06-19 17:47:42 +08:00
Harald Barth
08c00d275d Merge branch 'devel' into devel-overcurrent 2023-06-19 08:58:32 +02:00
Harald Barth
1888073dc2 set lastPowerChange we doing the power on retry after overload 2023-06-19 08:43:50 +02:00
Harald Barth
6dd175f63b fix power change timer micros overflow 2023-06-19 00:33:53 +02:00
peteGSX
ae54a747bb Merge branch 'rotary-encoder-send-position' of https://github.com/DCC-EX/CommandStation-EX into rotary-encoder-send-position 2023-06-19 08:26:08 +10:00
peteGSX
955ff4f96d Add _writeAnalogue() 2023-06-19 08:26:00 +10:00
peteGSX
0cf81d589e Add _writeAnalogue() 2023-06-19 08:25:20 +10:00
Harald Barth
d5dad767a4 version 4.2.55 2023-06-19 00:09:27 +02:00
Harald Barth
56fcb4e5f7 Optimize DCCTimerARV.cpp 2023-06-19 00:06:04 +02:00
Harald Barth
7783837545 Back out this as it is bigger and slower
This reverts commit efb2666060.
2023-06-18 21:08:52 +02:00
Harald Barth
0266936875 STM32: Use mask as loop variable 2023-06-18 20:22:53 +02:00
Harald Barth
befb41ce98 check ADCee::init() return value 2023-06-18 20:20:58 +02:00
Harald Barth
f83be05220 STM32: Use mask as loop variable 2023-06-18 19:26:38 +02:00
Harald Barth
cade89ba16 check ADCee::init() return value 2023-06-18 09:48:15 +02:00
Harald Barth
277825c530 versiontag 2023-06-18 09:01:32 +02:00
Harald Barth
95fe7aafe0 overload detection code cleanup 2023-06-18 08:59:37 +02:00
Harald Barth
f99deb4276 overload detection different timestamps and verbose diag 2023-06-14 22:57:28 +02:00
Harald Barth
f5d4dcb97c new overload detection 2023-06-14 00:58:02 +02:00
Harald Barth
8a69403dda devel release version 4.2.54 2023-06-03 22:01:14 +02:00
Harald Barth
e81d1cc93a Better warnings for pin number errors 2023-05-29 09:48:22 +02:00
Harald Barth
82929245ed char * / flashstring conflict 2023-05-25 14:02:28 +02:00
Harald Barth
db0e0cbf8b Send default function list in jR as well 2023-05-25 10:29:01 +02:00
Harald Barth
803b996e0b Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-05-25 09:29:47 +02:00
Harald Barth
5607ff7167 version 2023-05-25 09:29:42 +02:00
Harald Barth
58e62aaa81 Bug: Withrottle roster list returning empty string vs NULL 2023-05-25 09:27:41 +02:00
Harald Barth
5a9adea2b6 Bug: Withrottle roster list end was not detected 2023-05-25 08:05:07 +02:00
pmantoine
bf136d49e0 Fix Serial ports for Nucleo-144 boards 2023-05-25 08:34:20 +08:00
Harald Barth
50313ebbd2 cast to big enough type 2023-05-24 22:58:21 +02:00
Harald Barth
72bfc6abc7 INT16_MAX missing again 2023-05-24 22:57:43 +02:00
Harald Barth
342d9263d1 time stamp 2023-05-24 13:32:34 +02:00
Harald Barth
20d66fad4e Routes, automations and roster lists: Exclude ID 0 to be presented as available 2023-05-24 13:31:18 +02:00
Harald Barth
be4235e792 Arduino Mega2560: Use timer5 as timer4 for PWM DC 2023-05-24 13:29:20 +02:00
Harald Barth
c22d789513 INT16_MAX was missing at more places 2023-05-23 18:59:03 +02:00
Harald Barth
951a6637f0 INT16_MAX is a better end of array marker than -1 2023-05-23 10:57:45 +02:00
Harald Barth
fdbcbdf418 Do not send default roster entry on withrottle but on JR 2023-05-22 22:51:35 +02:00
Harald Barth
9478c3263d Try to find default roster entry 2023-05-22 16:39:24 +02:00
Harald Barth
16f94ecbdc Restict where what SerialX is used 2023-05-21 20:20:32 +02:00
Harald Barth
b80d7bd517 Pin handling supports pins up to 254 2023-05-21 11:54:46 +02:00
Harald Barth
8786285624 Assume that we have enough HW serials 2023-05-20 23:57:17 +02:00
Harald Barth
132b0773ef Fault pin handling made more straight forward 2023-05-20 23:15:15 +02:00
Harald Barth
99521f8a3f Support DCC-EX shield 2023-05-20 17:35:09 +02:00
pmantoine
1a3d295564 Nucleo-F446RE and other serial port updates. 2023-05-20 21:50:20 +08:00
Harald Barth
3b6789ef01 Merge branch 'devel-sabertooth' into devel 2023-05-20 14:59:07 +02:00
Harald Barth
c472f48d93 Experimental support for sabertooth motor controller on ESP32 2023-05-20 14:57:00 +02:00
pmantoine
94e9c2021b Fix config.example.h OLED_DRIVER #define 2023-05-15 11:31:44 +08:00
Harald Barth
9aad2e3206 save another 2 bytes in turnouts if eeprom is disabled 2023-05-09 14:17:30 +02:00
Harald Barth
ecc366cbd1 Merge branch '332-feature-request-add-a-no-programming-option-to-save-ram-on-uno' into devel 2023-05-09 14:11:18 +02:00
pmantoine
f4e3ca7c81 EX8874 entry for SAMD/STM32 2023-05-08 08:32:47 +08:00
Harald Barth
991bda63e0 update to production shield factors 2023-05-08 00:35:00 +02:00
Harald Barth
5164bd143c versions 2023-05-08 00:22:31 +02:00
Harald Barth
3759fc2a1a add checks for broken cab ID 2023-05-08 00:19:59 +02:00
Harald Barth
df7b890758 No EEPROM so you do not need this 2023-05-07 23:58:47 +02:00
Harald Barth
6d802f3a73 estop all locos in list, even last one 2023-05-05 16:14:44 +02:00
Harald Barth
9d953c70b8 use M1 and M2 instead of MD for motor control 2023-05-02 23:51:17 +02:00
Asbelos
6781e44fdd Fix EXRAIL speed issue 2023-05-02 22:02:52 +01:00
Asbelos
a3c9800aba Update version.h 2023-05-02 12:29:03 +01:00
Harald Barth
efdbfcb030 Add serial output for sabertooth controller 2023-05-01 20:18:32 +02:00
Asbelos
28d9843133 Broadcast changes in EXRAIlr 2023-05-01 14:25:45 +01:00
mstevetodd
fcf05206b4
Merge pull request #333 from mstevetodd/master
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:06:10 -04:00
stevet
cc3aba1feb Update WiThrottle.cpp
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:02:42 -04:00
Harald Barth
4eaad2d05b disable more PROG stuff (JOIN/UNJOIN from EXRAIL) 2023-04-23 22:45:47 +02:00
Harald Barth
72d131035e disable more PROG stuff (all hash keywords PROG etc) 2023-04-23 20:24:29 +02:00
peteGSX
2d1e695ac7 Disable <D ACK> 2023-04-20 08:26:17 +10:00
peteGSX
e780c40b34 Disable POM OPCODE 2023-04-20 08:16:32 +10:00
peteGSX
7addb13785 Disable <R> completely 2023-04-20 07:21:32 +10:00
peteGSX
b6f8889e8c Disable most programming functions 2023-04-20 07:08:11 +10:00
Fred
91d36ae909
Update ThrottleAssists.md 2023-03-03 21:59:18 -05:00
Fred
98af5c45ed
Update ThrottleAssists.md 2023-03-03 21:46:07 -05:00
Fred
d3eceb6d6c
Update ThrottleAssists.md 2023-03-03 21:41:22 -05:00
Fred
79eaaa85fa
Update ThrottleAssists.md
Fixing formatting
2023-03-03 21:35:13 -05:00
Fred
0f5b8adb6b
Update ThrottleAssists.md 2023-03-03 21:26:46 -05:00
Fred
da8faa808b
Update ThrottleAssists.md 2023-03-03 21:07:58 -05:00
Harald Barth
7311f2ce64 LCN bugfix 2023-02-12 20:38:03 +01:00
Harald Barth
7e4f9eb0e1 jT answer should contain empty string 2023-01-29 11:33:28 +01:00
Harald Barth
1f5eafbcca Bugfix for issue #299 TurnoutDescription NULL 2023-01-29 11:32:54 +01:00
peteGSX
7e16ec7088 Fix support request issue template 2022-11-05 05:17:03 +10:00
Harald Barth
912646f8ff Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX into HEAD 2022-11-04 15:41:05 +01:00
Harald Barth
dd309a3705 Ethernet init order 2022-11-04 15:39:35 +01:00
peteGSX
5376c9f410 Update project workflow for forks 2022-11-04 06:54:49 +10:00
peteGSX
b1d110ecbf Fix project workflow 2022-11-03 14:06:43 +10:00
Fred
5b7801ca6c
Update version.h 2022-10-28 14:05:35 -04:00
Fred
aca9c9c941
Update release_notes.md 2022-10-28 10:52:12 -04:00
Fred
6f94cd71ab
Update release_notes_v4.1.2.md 2022-10-28 10:50:35 -04:00
Fred
1827a11f83
Update release_notes_v4.1.1.md 2022-10-28 10:49:32 -04:00
Fred
0023ce3356
Create release_notes_v4.1.2.md 2022-10-28 10:48:40 -04:00
Fred
7b9f3ae08d
Update release_notes.md 2022-10-28 10:39:13 -04:00
Fred
5e50731a78
Update version.h
Fix version number in notes from 4.2.1 to 4.1.2
2022-10-28 10:28:54 -04:00
Harald Barth
df6c511d1d Fix for W5100 ethernet shield which does not report as the W5200 or W5500 2022-10-28 13:24:12 +02:00
peteGSX
4bfd4b1a12
Add templates and project workflow (#258)
* Add templates and project workflow

* Fixed template typos
2022-10-26 19:34:13 -04:00
Fred
4a3f3d0f34
Update release_notes_v4.1.1.md 2022-10-23 08:22:00 -04:00
Fred
f0d1909d9f
Update release_notes.md 2022-10-23 08:21:36 -04:00
Fred
daf6799ac1
Update release_notes.md 2022-10-22 18:10:42 -04:00
Kcsmith0708
b7a010f904
Verion.h 4.1.1 (#263)
Edited & Reformatted
 verify then ready for release
2022-10-22 18:01:37 -04:00
Kcsmith0708
d1518b8af0
Update Release_notes_v4.1.1.md (#264)
* Update release_notes_v4.1.1.md

Edited </RED > etc., commands and added KILLALL function to EXRAIL list

* Update release_notes_v4.1.1.md

added <t cab> back in

* Update release_notes_v4.1.1.md

fixed < t cab>  so it would display
2022-10-22 18:00:21 -04:00
Fred
39a85903ce
Update release_notes.md 2022-10-21 20:04:05 -04:00
Fred
d72474cd8f
Update release_notes_v4.1.1.md 2022-10-21 20:03:00 -04:00
Kcsmith0708
941e74beaf
Realese Document Edit & Enhancements (#262)
* Realese Document Edit & Enhancements

Edited Intro and Rearranged EXRAIL Enhancements

* Update release_notes_v4.1.1.md

edited indents

* Update release_notes_v4.1.1.md

Fomating
2022-10-21 13:45:54 -04:00
Fred
e618b91900
Update version.h 2022-10-21 11:57:55 -04:00
Fred
dcab5a0e72
Create release_notes_v4.1.1.md 2022-10-20 16:25:10 -04:00
Fred
1901d9547e
Update release_notes.md 2022-10-20 16:23:13 -04:00
Fred
7388d14bab
Update release_notes.md 2022-10-20 16:08:40 -04:00
Harald Barth
2da28ad2db version 2022-09-18 22:23:18 +02:00
Harald Barth
06bd80438e new version 2022-09-13 22:46:43 +02:00
Harald Barth
cd15eed005 EX-RAIL bugfix: Could not read long loco addrs 2022-09-13 22:43:31 +02:00
Harald Barth
23d0158804 simplify EthernetInterface::setup, make code shorter and format according to our overall style 2022-09-05 22:19:18 +02:00
habazut
2e9e614ad5
Merge pull request #256 from bcsanches/master
Keep Ethernet singleton "alive" until connection is established.
2022-09-05 20:34:11 +02:00
Bruno Crivelari Sanches
64b1de08be Detects when ethernet cable is connected and is disconnected, also correctly handles EthernetServer tead down on such situations 2022-09-05 14:23:54 -03:00
Bruno Crivelari Sanches
34c3d10767 Keep Ethernet singleton "alive" until connection is established. 2022-09-03 17:16:33 -03:00
Harald Barth
f2eb64fd21 make service start to be outside the DONT_TOUCH_WIFI_CONF area 2022-07-31 23:07:19 +02:00
Harald Barth
a80b16acba HH not supported 2022-06-21 19:46:59 +02:00
Harald Barth
b1f5e9f48c Initial version 2022-06-21 15:04:45 +02:00
41 changed files with 1198 additions and 544 deletions

View File

@ -168,7 +168,7 @@ void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// be safe for both types. // be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate); broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING #ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate); broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate);
#endif #endif
} }
@ -205,6 +205,39 @@ int16_t CommandDistributor::retClockTime() {
void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot]; DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions); broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
#ifdef SABERTOOTH
if (Serial2 && sp->loco == SABERTOOTH) {
static uint8_t rampingmode = 0;
bool direction = (sp->speedCode & 0x80) !=0; // true for forward
int32_t speed = sp->speedCode & 0x7f;
if (speed == 1) { // emergency stop
if (rampingmode != 1) {
rampingmode = 1;
Serial2.print("R1: 0\r\n");
Serial2.print("R2: 0\r\n");
}
Serial2.print("MD: 0\r\n");
} else {
if (speed != 0) {
// speed is here 2 to 127
speed = (speed - 1) * 1625 / 100;
speed = speed * (direction ? 1 : -1);
// speed is here -2047 to 2047
}
if (rampingmode != 2) {
rampingmode = 2;
Serial2.print("R1: 2047\r\n");
Serial2.print("R2: 2047\r\n");
}
Serial2.print("M1: ");
Serial2.print(speed);
Serial2.print("\r\n");
Serial2.print("M2: ");
Serial2.print(speed);
Serial2.print("\r\n");
}
}
#endif
#ifdef CD_HANDLE_RING #ifdef CD_HANDLE_RING
WiThrottle::markForBroadcast(sp->loco); WiThrottle::markForBroadcast(sp->loco);
#endif #endif
@ -228,11 +261,8 @@ void CommandDistributor::broadcastPower() {
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
} }
void CommandDistributor::broadcastText(const FSH * msg) { void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg); broadcastReply(type, F("%s"),msg);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
#endif
} }
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) { void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {

View File

@ -35,8 +35,9 @@
#endif #endif
class CommandDistributor { class CommandDistributor {
private: public:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
private:
static void broadcastToClients(clientType type); static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter; static StringBuffer * broadcastBufferWriter;
#ifdef CD_HANDLE_RING #ifdef CD_HANDLE_RING
@ -52,7 +53,7 @@ public :
static void setClockTime(int16_t time, int8_t rate, byte opt); static void setClockTime(int16_t time, int8_t rate, byte opt);
static int16_t retClockTime(); static int16_t retClockTime();
static void broadcastPower(); static void broadcastPower();
static void broadcastText(const FSH * msg); static void broadcastRaw(clientType type,char * msg);
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr); static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg); template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId); static void forget(byte clientId);

View File

@ -30,6 +30,7 @@
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting, * © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton * Fred Decker, Gregor Baues, Anthony W - Dayton
* © 2023 Nathan Kellenicki
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -78,6 +79,12 @@ void setup()
// Initialise HAL layer before reading EEprom or setting up MotorDrivers // Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin(); IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// Set up MotorDrivers early to initialize all pins
TrackManager::Setup(MOTOR_SHIELD_TYPE);
DISPLAY_START ( DISPLAY_START (
// This block is still executed for DIAGS if display not in use // This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION)); LCD(0,F("DCC-EX v%S"),F(VERSION));
@ -89,26 +96,19 @@ void setup()
// Start Ethernet if it exists // Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON #if WIFI_ON
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // WIFI_ON #endif // WIFI_ON
#else #else
// ESP32 needs wifi on always // ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL); WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // ARDUINO_ARCH_ESP32 #endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
EthernetInterface::setup(); EthernetInterface::setup();
#endif // ETHERNET_ON #endif // ETHERNET_ON
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// Responsibility 3: Start the DCC engine. // Responsibility 3: Start the DCC engine.
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) DCC::begin();
// Standard supported devices have pre-configured macros but custome hardware installations require
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
TrackManager::Setup(MOTOR_SHIELD_TYPE);
// Start RMFT aka EX-RAIL (ignored if no automnation) // Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin(); RMFT::begin();

View File

@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL; FSH* DCC::shieldName=NULL;
byte DCC::globalSpeedsteps=128; byte DCC::globalSpeedsteps=128;
void DCC::begin(const FSH * motorShieldName) { void DCC::begin() {
shieldName=(FSH *)motorShieldName;
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
// Load stuff from EEprom // Load stuff from EEprom
@ -693,7 +692,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) { if (loco==0) {
// broadcast stop/estop but dont change direction // broadcast stop/estop but dont change direction
for (int reg = 0; reg < highestUsedReg; reg++) { for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco==0) continue; if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) { if (speedTable[reg].speedCode != newspeed) {

5
DCC.h
View File

@ -51,7 +51,10 @@ const byte MAX_LOCOS = 30;
class DCC class DCC
{ {
public: public:
static void begin(const FSH * motorShieldName); static inline void setShieldName(const FSH * motorShieldName) {
shieldName=(FSH *)motorShieldName;
};
static void begin();
static void loop(); static void loop();
// Public DCC API functions // Public DCC API functions

View File

@ -152,7 +152,7 @@ byte DCCACK::getAck() {
return(0); // pending set off but not detected means no ACK. return(0); // pending set off but not detected means no ACK.
} }
#ifndef DISABLE_PROG
void DCCACK::loop() { void DCCACK::loop() {
while (ackManagerProg) { while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg); byte opcode=GETFLASH(ackManagerProg);
@ -414,7 +414,7 @@ void DCCACK::callback(int value) {
(ackManagerCallback)( value); (ackManagerCallback)( value);
} }
} }
#endif
void DCCACK::checkAck(byte sentResetsSincePacket) { void DCCACK::checkAck(byte sentResetsSincePacket) {
if (!ackPending) return; if (!ackPending) return;

View File

@ -3,7 +3,7 @@
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021 Mike S * © 2021 Mike S
* © 2021 Herb Morton * © 2021 Herb Morton
* © 2020-2022 Harald Barth * © 2020-2023 Harald Barth
* © 2020-2021 M Steve Todd * © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker * © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
@ -47,16 +47,14 @@
#define SENDFLASHLIST(stream,flashList) \ #define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \ for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \ int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==0) break; \ if (value==INT16_MAX) break; \
StringFormatter::send(stream,F(" %d"),value); \ if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
} }
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
// To discover new keyword numbers , use the <$ YOURKEYWORD> command // To discover new keyword numbers , use the <$ YOURKEYWORD> command
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_CABS = -11981; const int16_t HASH_KEYWORD_CABS = -11981;
const int16_t HASH_KEYWORD_RAM = 25982; const int16_t HASH_KEYWORD_RAM = 25982;
const int16_t HASH_KEYWORD_CMD = 9962; const int16_t HASH_KEYWORD_CMD = 9962;
@ -64,7 +62,11 @@ const int16_t HASH_KEYWORD_ACK = 3113;
const int16_t HASH_KEYWORD_ON = 2657; const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436; const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209; const int16_t HASH_KEYWORD_SLOW = -17209;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_PROGBOOST = -6353; const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#endif
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
const int16_t HASH_KEYWORD_EEPROM = -7168; const int16_t HASH_KEYWORD_EEPROM = -7168;
#endif #endif
@ -217,6 +219,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{ {
#ifdef DISABLE_PROG
(void)ringStream;
#endif
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused (void)EEPROM; // tell compiler not to warn this is unused
#endif #endif
@ -286,6 +291,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (direction < 0 || direction > 1) if (direction < 0 || direction > 1)
break; // invalid direction code break; // invalid direction code
if (cab > 10239 || cab < 0)
break; // beyond DCC range
DCC::setThrottle(cab, tspeed, direction); DCC::setThrottle(cab, tspeed, direction);
if (params == 4) // send obsolete format T response if (params == 4) // send obsolete format T response
@ -369,6 +376,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
break; break;
#ifndef DISABLE_PROG
case 'w': // WRITE CV on MAIN <w CAB CV VALUE> case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(p[0], p[1], p[2]); DCC::writeCVByteMain(p[0], p[1], p[2]);
return; return;
@ -376,9 +384,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE> case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return; return;
#endif
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9> case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
#ifndef DISABLE_PROG
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9> case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
#endif
// NOTE: this command was parsed in HEX instead of decimal // NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG params--; // drop REG
if (params<1) break; if (params<1) break;
@ -393,6 +404,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
} }
return; return;
#ifndef DISABLE_PROG
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, ringStream)) if (!stashCallback(stream, p, ringStream))
break; break;
@ -450,6 +462,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
} }
break; break;
#endif
case '1': // POWERON <1 [MAIN|PROG|JOIN]> case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{ {
@ -457,27 +470,29 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool prog=false; bool prog=false;
bool join=false; bool join=false;
if (params > 1) break; if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually if (params==0) { // All
main=true; main=true;
prog=true; prog=true;
} }
if (params==1) { if (params==1) {
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true; main=true;
prog=true; prog=true;
join=true; join=true;
} }
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true; prog=true;
} }
#endif
else break; // will reply <X> else break; // will reply <X>
} }
TrackManager::setJoin(join);
if (main) TrackManager::setMainPower(POWERMODE::ON); if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON); if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
@ -488,7 +503,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool main=false; bool main=false;
bool prog=false; bool prog=false;
if (params > 1) break; if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually if (params==0) { // All
main=true; main=true;
prog=true; prog=true;
} }
@ -496,18 +511,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true; main=true;
} }
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true; prog=true;
} }
#endif
else break; // will reply <X> else break; // will reply <X>
} }
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF); if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) { if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF); TrackManager::setProgPower(POWERMODE::OFF);
} }
TrackManager::setJoin(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
@ -638,8 +655,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (params==1) { if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList) SENDFLASHLIST(stream,RMFT2::rosterIdList)
} }
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), else {
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id)); const FSH * functionNames= RMFT2::getRosterFunctions(id);
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id),
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
}
#endif #endif
StringFormatter::send(stream, F(">\n")); StringFormatter::send(stream, F(">\n"));
return; return;
@ -893,6 +914,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory()); StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
break; break;
#ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value> case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) { if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) { if (p[1] == HASH_KEYWORD_LIMIT) {
@ -913,6 +935,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::ACK = onOff; Diag::ACK = onOff;
} }
return true; return true;
#endif
case HASH_KEYWORD_CMD: // <D CMD ON/OFF> case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff; Diag::CMD = onOff;
@ -935,11 +958,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::LCN = onOff; Diag::LCN = onOff;
return true; return true;
#endif #endif
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST: case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true; TrackManager::progTrackBoosted=true;
return true; return true;
#endif
case HASH_KEYWORD_RESET: case HASH_KEYWORD_RESET:
DCCTimer::reset(); DCCTimer::reset();
break; // and <X> if we didnt restart break; // and <X> if we didnt restart

View File

@ -194,8 +194,10 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter; dataLen = bitcounter;
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
dataReady = true; dataReady = true;
dataRepeat = repeatCount+1; // repeatCount of 0 means send once dataRepeat = repeatCount+1; // repeatCount of 0 means send once
interrupts();
return 0; return 0;
} }
@ -212,6 +214,8 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() {
if (dataReady) { // if we have new data, fill while preamble is running if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1); rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
dataReady = false; dataReady = false;
if (dataRepeat == 0) // all data should go out at least once
DIAG(F("Channel %d DCC signal lost data"), channel);
} }
if (dataRepeat > 0) // if a repeat count was specified, work on that if (dataRepeat > 0) // if a repeat count was specified, work on that
dataRepeat--; dataRepeat--;

View File

@ -105,9 +105,14 @@ private:
// that an offset can be initialized. // that an offset can be initialized.
class ADCee { class ADCee {
public: public:
// init does add the pin to the list of scanned pins (if this // begin is called for any setup that must be done before
// **init** can be called. On some architectures this involves ADC
// initialisation and clock routing, sampling times etc.
static void begin();
// init adds the pin to the list of scanned pins (if this
// platform's implementation scans pins) and returns the first // platform's implementation scans pins) and returns the first
// read value. It is called before the regular scan is started. // read value (which is why it required begin to have been called first!)
// It must be called before the regular scan is started.
static int init(uint8_t pin); static int init(uint8_t pin);
// read does read the pin value from the scanned cache or directly // read does read the pin value from the scanned cache or directly
// if this is a platform that does not scan. fromISR is a hint if // if this is a platform that does not scan. fromISR is a hint if
@ -116,19 +121,15 @@ public:
static int read(uint8_t pin, bool fromISR=false); static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return // returns possible max value that the ADC can return
static int16_t ADCmax(); static int16_t ADCmax();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
private: private:
// On platforms that scan, it is called from waveform ISR // On platforms that scan, it is called from waveform ISR
// only on a regular basis. // only on a regular basis.
static void scan(); static void scan();
// bit array of used pins (max 16) // bit array of used pins (max 16)
static uint16_t usedpins; static uint16_t usedpins;
static uint8_t highestPin;
// cached analog values (malloc:ed to actual number of ADC channels) // cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals; static int *analogvals;
// ids to scan (new way)
static byte *idarr;
// friend so that we can call scan() and begin() // friend so that we can call scan() and begin()
friend class DCCWaveform; friend class DCCWaveform;
}; };

View File

@ -1,6 +1,6 @@
/* /*
* © 2021 Mike S * © 2021 Mike S
* © 2021-2022 Harald Barth * © 2021-2023 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2021 Chris Harlow * © 2021 Chris Harlow
* © 2021 David Cutting * © 2021 David Cutting
@ -29,6 +29,9 @@
#include <avr/boot.h> #include <avr/boot.h>
#include <avr/wdt.h> #include <avr/wdt.h>
#include "DCCTimer.h" #include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
INTERRUPT_CALLBACK interruptHandler=0; INTERRUPT_CALLBACK interruptHandler=0;
// Arduino nano, uno, mega etc // Arduino nano, uno, mega etc
@ -128,8 +131,8 @@ void DCCTimer::reset() {
#define NUM_ADC_INPUTS 8 #define NUM_ADC_INPUTS 8
#endif #endif
uint16_t ADCee::usedpins = 0; uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL; int * ADCee::analogvals = NULL;
byte *ADCee::idarr = NULL;
static bool ADCusesHighPort = false; static bool ADCusesHighPort = false;
/* /*
@ -139,28 +142,17 @@ static bool ADCusesHighPort = false;
*/ */
int ADCee::init(uint8_t pin) { int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0; uint8_t id = pin - A0;
byte n;
if (id >= NUM_ADC_INPUTS) if (id >= NUM_ADC_INPUTS)
return -1023; return -1023;
if (id > 7) if (id > 7)
ADCusesHighPort = true; ADCusesHighPort = true;
pinMode(pin, INPUT); pinMode(pin, INPUT);
int value = analogRead(pin); int value = analogRead(pin);
if (analogvals == NULL) { if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int)); analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker analogvals[id] = value;
analogvals[n] = -32768; // 16 bit int min value usedpins |= (1<<id);
idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value if (id > highestPin) highestPin = id;
for (n=0 ; n <= NUM_ADC_INPUTS; n++)
idarr[n] = 255; // set 255 as end of array marker
}
analogvals[id] = value; // store before enable by idarr[n]
for (n=0 ; n <= NUM_ADC_INPUTS; n++) {
if (idarr[n] == 255) {
idarr[n] = id;
break;
}
}
return value; return value;
} }
int16_t ADCee::ADCmax() { int16_t ADCee::ADCmax() {
@ -170,14 +162,14 @@ int16_t ADCee::ADCmax() {
* Read function ADCee::read(pin) to get value instead of analogRead(pin) * Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/ */
int ADCee::read(uint8_t pin, bool fromISR) { int ADCee::read(uint8_t pin, bool fromISR) {
(void)fromISR; // AVR does ignore this arg
uint8_t id = pin - A0; uint8_t id = pin - A0;
int a; if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL) // we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case // because usedpins would still be 0 in that case
noInterrupts(); if (!fromISR) noInterrupts();
a = analogvals[id]; int a = analogvals[id];
interrupts(); if (!fromISR) interrupts();
return a; return a;
} }
/* /*
@ -186,7 +178,8 @@ int ADCee::read(uint8_t pin, bool fromISR) {
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("-O3") #pragma GCC optimize ("-O3")
void ADCee::scan() { void ADCee::scan() {
static byte num = 0; // index into id array static byte id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false; static bool waiting = false;
if (waiting) { if (waiting) {
@ -198,26 +191,49 @@ void ADCee::scan() {
low = ADCL; //must read low before high low = ADCL; //must read low before high
high = ADCH; high = ADCH;
bitSet(ADCSRA, ADIF); bitSet(ADCSRA, ADIF);
analogvals[idarr[num]] = (high << 8) | low; analogvals[id] = (high << 8) | low;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
waiting = false; waiting = false;
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
} }
if (!waiting) { if (!waiting) {
// cycle around in-use analogue pins if (usedpins == 0) // otherwise we would loop forever
num++; return;
if (idarr[num] == 255) // look for a valid track to sample or until we are around
num = 0; while (true) {
if (mask & usedpins) {
// start new ADC aquire on id // start new ADC aquire on id
#if defined(ADCSRB) && defined(MUX5) #if defined(ADCSRB) && defined(MUX5)
if (ADCusesHighPort) { // if we ever have started to use high pins) if (ADCusesHighPort) { // if we ever have started to use high pins)
if (idarr[num] > 7) // if we use a high ADC pin if (id > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit bitSet(ADCSRB, MUX5); // set MUX5 bit
else else
bitClear(ADCSRB, MUX5); bitClear(ADCSRB, MUX5);
} }
#endif #endif
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
bitSet(ADCSRA, ADSC); // start conversion bitSet(ADCSRA,ADSC); // start conversion
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true; waiting = true;
return;
}
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
} }
} }
#pragma GCC pop_options #pragma GCC pop_options

View File

@ -180,8 +180,8 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
return; return;
} }
pin_to_channel[pin] = --cnt_channel; pin_to_channel[pin] = --cnt_channel;
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8); ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
} else { } else {
ledcAttachPin(pin, pin_to_channel[pin]); ledcAttachPin(pin, pin_to_channel[pin]);
} }

View File

@ -30,29 +30,33 @@
#ifdef ARDUINO_ARCH_STM32 #ifdef ARDUINO_ARCH_STM32
#include "DCCTimer.h" #include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F411RE) #if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 defined by default // Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console // Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(ARDUINO_NUCLEO_F446RE) #elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default // Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins
// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console // Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// NB: USART3 and USART6 are available but as yet undefined // On the F446RE, Serial3 and Serial5 are easy to use:
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
// Nucleo-144 boards don't have Serial1 defined by default // Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console // Serial3 is defined to use USART3 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// NB:
// On all of the above, USART3, and USART6 are available but as yet undefined
// On F446ZE and F429ZI, UART4, UART5 are also available but as yet undefined
// On F429ZI, UART7 and UART8 are also available but as yet undefined
#else #else
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined #error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
#endif #endif
@ -231,13 +235,16 @@ void DCCTimer::reset() {
while(true) {}; while(true) {};
} }
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! // TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) #if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing! #warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
#endif #endif
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
#define NUM_ADC_INPUTS 16
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
uint16_t ADCee::usedpins = 0; uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL; int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL; uint32_t * analogchans = NULL;
bool adc1configured = false; bool adc1configured = false;
@ -308,6 +315,9 @@ int ADCee::init(uint8_t pin) {
analogvals[id] = value; // Store sampled value analogvals[id] = value; // Store sampled value
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
usedpins |= (1 << id); // This pin is now ready usedpins |= (1 << id); // This pin is now ready
if (id > highestPin) highestPin = id; // Store our highest pin in use
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
return value; return value;
} }
@ -342,11 +352,13 @@ void ADCee::scan() {
// found value // found value
analogvals[id] = ADC1->DR; analogvals[id] = ADC1->DR;
// advance at least one track // advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0); #ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
waiting = false; waiting = false;
id++; id++;
mask = mask << 1; mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) { if (id > highestPin) { // the 1 has been shifted out
id = 0; id = 0;
mask = 1; mask = 1;
} }
@ -360,13 +372,15 @@ void ADCee::scan() {
// start new ADC aquire on id // start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
// for scope debug TrackManager::track[1]->setBrake(1); #ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true; waiting = true;
return; return;
} }
id++; id++;
mask = mask << 1; mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) { if (id > highestPin) {
id = 0; id = 0;
mask = 1; mask = 1;
} }

View File

@ -247,6 +247,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
pendingPacket[byteCount] = checksum; pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1; pendingLength = byteCount + 1;
pendingRepeats = repeats; pendingRepeats = repeats;
// DIAG repeated commands (accesories)
// if (pendingRepeats > 0)
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
// The resets will be zero not only now but as well repeats packets into the future // The resets will be zero not only now but as well repeats packets into the future
clearResets(repeats+1); clearResets(repeats+1);
{ {

View File

@ -266,12 +266,12 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
char RMFT2::getRouteType(int16_t id) { char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i+=2) { for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i); int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==0) break; if (rid==INT16_MAX) break;
if (rid==id) return 'R'; if (rid==id) return 'R';
} }
for (int16_t i=0;;i+=2) { for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i); int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==0) break; if (rid==INT16_MAX) break;
if (rid==id) return 'A'; if (rid==id) return 'A';
} }
return 'X'; return 'X';
@ -610,6 +610,7 @@ void RMFT2::loop2() {
break; break;
case OPCODE_SPEED: case OPCODE_SPEED:
forward=DCC::getThrottleDirection(loco)^invert;
driveLoco(operand); driveLoco(operand);
break; break;
@ -883,18 +884,13 @@ void RMFT2::loop2() {
while(loopTask) loopTask->kill(F("KILLALL")); while(loopTask) loopTask->kill(F("KILLALL"));
return; return;
#ifndef DISABLE_PROG
case OPCODE_JOIN: case OPCODE_JOIN:
TrackManager::setPower(POWERMODE::ON); TrackManager::setPower(POWERMODE::ON);
TrackManager::setJoin(true); TrackManager::setJoin(true);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
break; break;
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_UNJOIN: case OPCODE_UNJOIN:
TrackManager::setJoin(false); TrackManager::setJoin(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
@ -920,6 +916,13 @@ void RMFT2::loop2() {
forward=true; forward=true;
invert=false; invert=false;
break; break;
#endif
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_START: case OPCODE_START:
{ {
@ -1242,7 +1245,10 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL); DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
break; break;
case thrunge_broadcast: case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString()); CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString());
break;
case thrunge_withrottle:
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
break; break;
case thrunge_lcd: case thrunge_lcd:
LCD(id,F("%s"),buffer->getString()); LCD(id,F("%s"),buffer->getString());

View File

@ -45,7 +45,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE, OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR, OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM, #ifndef DISABLE_PROG
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
#endif
OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET, OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
@ -77,7 +80,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
// Ensure thrunge_lcd is put last as there may be more than one display, // Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd. // sequentially numbered from thrunge_lcd.
enum thrunger: byte { enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, thrunge_print, thrunge_broadcast, thrunge_withrottle,
thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6, thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcn, thrunge_lcn,

View File

@ -101,7 +101,9 @@
#undef PAUSE #undef PAUSE
#undef PIN_TURNOUT #undef PIN_TURNOUT
#undef PRINT #undef PRINT
#ifndef DISABLE_PROG
#undef POM #undef POM
#endif
#undef POWEROFF #undef POWEROFF
#undef POWERON #undef POWERON
#undef READ_LOCO #undef READ_LOCO
@ -142,6 +144,7 @@
#undef VIRTUAL_SIGNAL #undef VIRTUAL_SIGNAL
#undef VIRTUAL_TURNOUT #undef VIRTUAL_TURNOUT
#undef WAITFOR #undef WAITFOR
#undef WITHROTTLE
#undef XFOFF #undef XFOFF
#undef XFON #undef XFON
@ -223,7 +226,9 @@
#define PIN_TURNOUT(id,pin,description...) #define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg) #define PRINT(msg)
#define PARSE(msg) #define PARSE(msg)
#ifndef DISABLE_PROG
#define POM(cv,value) #define POM(cv,value)
#endif
#define POWEROFF #define POWEROFF
#define POWERON #define POWERON
#define READ_LOCO #define READ_LOCO
@ -264,6 +269,7 @@
#define VIRTUAL_SIGNAL(id) #define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...) #define VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin) #define WAITFOR(pin)
#define WITHROTTLE(msg)
#define XFOFF(cab,func) #define XFOFF(cab,func)
#define XFON(cab,func) #define XFON(cab,func)
#endif #endif

View File

@ -81,14 +81,14 @@ void exrailHalSetup() {
#define ROUTE(id, description) id, #define ROUTE(id, description) id,
const int16_t HIGHFLASH RMFT2::routeIdList[]= { const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h" #include "myAutomation.h"
0}; INT16_MAX};
// Pass 2a create throttle automation list // Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef AUTOMATION #undef AUTOMATION
#define AUTOMATION(id, description) id, #define AUTOMATION(id, description) id,
const int16_t HIGHFLASH RMFT2::automationIdList[]= { const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h" #include "myAutomation.h"
0}; INT16_MAX};
// Pass 3 Create route descriptions: // Pass 3 Create route descriptions:
#undef ROUTE #undef ROUTE
@ -153,6 +153,8 @@ const int StringMacroTracker1=__COUNTER__;
lcdid=id;\ lcdid=id;\
break;\ break;\
} }
#undef WITHROTTLE
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
void RMFT2::printMessage(uint16_t id) { void RMFT2::printMessage(uint16_t id) {
thrunger tmode; thrunger tmode;
@ -188,7 +190,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
// Pass 6: Roster IDs (count) // Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROSTER #undef ROSTER
#define ROSTER(cabid,name,funcmap...) +1 #define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1)
const byte RMFT2::rosterNameCount=0 const byte RMFT2::rosterNameCount=0
#include "myAutomation.h" #include "myAutomation.h"
; ;
@ -199,7 +201,7 @@ const byte RMFT2::rosterNameCount=0
#define ROSTER(cabid,name,funcmap...) cabid, #define ROSTER(cabid,name,funcmap...) cabid,
const int16_t HIGHFLASH RMFT2::rosterIdList[]={ const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h" #include "myAutomation.h"
0}; INT16_MAX};
// Pass 7: Roster names getter // Pass 7: Roster names getter
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
@ -221,7 +223,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#include "myAutomation.h" #include "myAutomation.h"
default: break; default: break;
} }
return F(""); return NULL;
} }
// Pass 8 Signal definitions // Pass 8 Signal definitions
@ -326,7 +328,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0, #define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#ifndef DISABLE_PROG
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#endif
#define POWEROFF OPCODE_POWEROFF,0,0, #define POWEROFF OPCODE_POWEROFF,0,0,
#define POWERON OPCODE_POWERON,0,0, #define POWERON OPCODE_POWERON,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
@ -368,6 +372,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
#define VIRTUAL_SIGNAL(id) #define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0), #define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#define WITHROTTLE(msg) PRINT(msg)
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func), #define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),

View File

@ -1 +1 @@
#define GITHUB_SHA "devel-202304172140Z" #define GITHUB_SHA "devel-202308102205Z"

View File

@ -50,12 +50,12 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
// Initialisation of EXTurntable // Initialisation of EXTurntable
void EXTurntable::_begin() { void EXTurntable::_begin() {
I2CManager.begin(); I2CManager.begin();
I2CManager.setClock(1000000);
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
} else { } else {
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
} }
} }

112
IO_PCA9555.h Normal file
View File

@ -0,0 +1,112 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef io_pca9555_h
#define io_pca9555_h
#include "IO_GPIOBase.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments).
*/
class PCA9555 : public GPIOBase<uint16_t> {
public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
}
// Constructor
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
{
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_INPUT_P0;
}
private:
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
}
void _writePullups() override {
// Do nothing, pull-ups are always in place for input ports
// This function is here for HAL GPIOBase API compatibilitiy
}
void _writePortModes() override {
// Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others.
// PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge
uint16_t temp = ~(_portMode & _portInUse);
I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8);
}
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0);
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
/* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h
* after a Read operation to the PCA9555 device or before reading from
* another device"
* Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data
* Issue not seen during testing, uncomment if needed
*/
//I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0);
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
else
_portInputState = 0xffff;
}
void _setupDevice() override {
// HAL API calls
_writePortModes();
_writePullups();
_writeGpioPort();
}
uint8_t inputBuffer[2];
uint8_t outputBuffer[1];
enum {
REG_INPUT_P0 = 0x00,
REG_INPUT_P1 = 0x01,
REG_OUTPUT_P0 = 0x02,
REG_OUTPUT_P1 = 0x03,
REG_POL_INV_P0 = 0x04,
REG_POL_INV_P1 = 0x05,
REG_CONF_P0 = 0x06,
REG_CONF_P1 = 0x07,
};
};
#endif

View File

@ -1,4 +1,5 @@
/* /*
* © 2023, Peter Cole. All rights reserved.
* © 2022, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved.
* *
* This file is part of EX-CommandStation * This file is part of EX-CommandStation
@ -28,9 +29,23 @@
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received * IFRE(vpin, position) - test to see if specified rotary encoder position has been received
* *
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
* *
* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other
* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished
* using the EXRAIL SERVO(vpin, position, profile) command, where:
* - vpin = the third defined Vpin (any other is ignored)
* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255
* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance
*
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
*
* #include "IO_RotaryEncoder.h"
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
*
* Refer to the documentation for further information including the valid activities and examples. * Refer to the documentation for further information including the valid activities and examples.
*/ */
@ -44,58 +59,88 @@
class RotaryEncoder : public IODevice { class RotaryEncoder : public IODevice {
public: public:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
} }
private: private:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
_firstVpin = firstVpin;
_nPins = nPins;
if (_nPins > 3) {
_nPins = 3;
DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins);
}
_I2CAddress = i2cAddress;
addDevice(this);
}
// Initiate the device // Initiate the device
void _begin() { void _begin() {
uint8_t _status;
// Attempt to initilalise device
I2CManager.begin(); I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
byte _getVersion[1] = {RE_VER}; // Send RE_RDY, must receive RE_RDY to be online
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); _sendBuffer[0] = RE_RDY;
_status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1);
if (_status == I2C_STATUS_OK) {
if (_rcvBuffer[0] == RE_RDY) {
_sendBuffer[0] = RE_VER;
if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) {
_majorVer = _versionBuffer[0]; _majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1]; _minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2]; _patchVer = _versionBuffer[2];
_buffer[0] = RE_OP; }
I2CManager.write(_I2CAddress, _buffer, 1); } else {
DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]);
_deviceState = DEVSTATE_FAILED;
return;
}
} else {
DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
} else { } else {
DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
} }
} }
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
I2CManager.read(_I2CAddress, _buffer, 1); if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed
_position = _buffer[0]; if (_i2crb.isBusy()) return; // Return if I2C operation still in progress
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE) if (currentMicros - _lastPositionRead > _positionRefresh) {
_lastPositionRead = currentMicros;
_sendBuffer[0] = RE_READ;
I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder
_position = _rcvBuffer[0];
// If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) { if (_position != _previousPosition) {
_previousPosition = _position; _previousPosition = _position;
RMFT2::changeEvent(_firstVpin,1); RMFT2::changeEvent(_firstVpin, 1);
} else { } else {
RMFT2::changeEvent(_firstVpin,0); RMFT2::changeEvent(_firstVpin, 0);
}
#endif
} }
#endif
delayUntil(currentMicros + 100000);
} }
// Device specific read function // Return the position sent by the rotary encoder software
int _readAnalogue(VPIN vpin) override { int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0; if (_deviceState == DEVSTATE_FAILED) return 0;
return _position; return _position;
} }
// Send the feedback value to the rotary encoder software
void _write(VPIN vpin, int value) override { void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) { if (vpin == _firstVpin + 1) {
if (value != 0) value = 0x01; if (value != 0) value = 0x01;
@ -104,6 +149,19 @@ private:
} }
} }
// Send a position update to the rotary encoder software
// To be valid, must be 0 to 255, and different to the current position
// If the current position is the same, it was initiated by the rotary encoder
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
if (vpin == _firstVpin + 2) {
if (position >= 0 && position <= 255 && position != _position) {
byte newPosition = position & 0xFF;
byte _positionBuffer[2] = {RE_MOVE, newPosition};
I2CManager.write(_I2CAddress, _positionBuffer, 2);
}
}
}
void _display() override { void _display() override {
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
@ -112,14 +170,21 @@ private:
int8_t _position; int8_t _position;
int8_t _previousPosition = 0; int8_t _previousPosition = 0;
uint8_t _versionBuffer[3]; uint8_t _versionBuffer[3];
uint8_t _buffer[1]; uint8_t _sendBuffer[1];
uint8_t _rcvBuffer[1];
uint8_t _majorVer = 0; uint8_t _majorVer = 0;
uint8_t _minorVer = 0; uint8_t _minorVer = 0;
uint8_t _patchVer = 0; uint8_t _patchVer = 0;
I2CRB _i2crb;
unsigned long _lastPositionRead = 0;
const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms
enum { enum {
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_RDY = 0xA0, // Flag to check if encoder is ready for operation
RE_OP = 0xA1, // Flag for normal operation RE_VER = 0xA1, // Flag to retrieve rotary encoder software version
RE_READ = 0xA2, // Flag to read the current position of the encoder
RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end
RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder
}; };
}; };

View File

@ -27,15 +27,16 @@
#include "DCCTimer.h" #include "DCCTimer.h"
#include "DIAG.h" #include "DIAG.h"
bool MotorDriver::commonFaultPin=false; unsigned long MotorDriver::globalOverloadStart = 0;
volatile portreg_t shadowPORTA; volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB; volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC; volatile portreg_t shadowPORTC;
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, int8_t fault_pin) { byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
powerPin=power_pin; const FSH * warnString = F("** WARNING **");
invertPower=power_pin < 0; invertPower=power_pin < 0;
if (invertPower) { if (invertPower) {
powerPin = 0-power_pin; powerPin = 0-power_pin;
@ -91,35 +92,54 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
} }
else dualSignal=false; else dualSignal=false;
brakePin=brake_pin;
if (brake_pin!=UNUSED_PIN){ if (brake_pin!=UNUSED_PIN){
invertBrake=brake_pin < 0; invertBrake=brake_pin < 0;
brakePin=invertBrake ? 0-brake_pin : brake_pin; if (invertBrake)
brake_pin = 0-brake_pin;
if (brake_pin > MAX_PIN)
DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN);
brakePin=(byte)brake_pin;
getFastPin(F("BRAKE"),brakePin,fastBrakePin); getFastPin(F("BRAKE"),brakePin,fastBrakePin);
// if brake is used for railcom cutout we need to do PORTX register trick here as well // if brake is used for railcom cutout we need to do PORTX register trick here as well
pinMode(brakePin, OUTPUT); pinMode(brakePin, OUTPUT);
setBrake(true); // start with brake on in case we hace DC stuff going on setBrake(true); // start with brake on in case we hace DC stuff going on
} else {
brakePin=UNUSED_PIN;
} }
else brakePin=UNUSED_PIN;
currentPin=current_pin; currentPin=current_pin;
if (currentPin!=UNUSED_PIN) ADCee::init(currentPin); if (currentPin!=UNUSED_PIN) {
int ret = ADCee::init(currentPin);
if (ret < -1010) { // XXX give value a name later
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
currentPin = UNUSED_PIN;
}
}
senseOffset=0; // value can not be obtained until waveform is activated senseOffset=0; // value can not be obtained until waveform is activated
faultPin=fault_pin; if (fault_pin != UNUSED_PIN) {
if (faultPin != UNUSED_PIN) {
invertFault=fault_pin < 0; invertFault=fault_pin < 0;
faultPin=invertFault ? 0-fault_pin : fault_pin; if (invertFault)
fault_pin = 0-fault_pin;
if (fault_pin > MAX_PIN)
DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN);
faultPin=(byte)fault_pin;
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault); DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
pinMode(faultPin, INPUT); pinMode(faultPin, INPUT);
} else {
faultPin=UNUSED_PIN;
} }
// This conversion performed at compile time so the remainder of the code never needs // This conversion performed at compile time so the remainder of the code never needs
// float calculations or libraray code. // float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale; senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps; tripMilliamps=trip_milliamps;
rawCurrentTripValue=mA2raw(trip_milliamps); #ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) { if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never // This would mean that the values obtained from the ADC never
@ -134,20 +154,16 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
} }
if (currentPin==UNUSED_PIN) if (currentPin==UNUSED_PIN)
DIAG(F("** WARNING ** No current or short detection")); DIAG(F("%S No current or short detection"), warnString);
else { else {
DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue); DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
// self testing diagnostic for the non-float converters... may be removed when happy // self testing diagnostic for the non-float converters... may be removed when happy
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
// senseFactorInternal, raw2mA(1000),mA2raw(1000)); // senseFactorInternal, raw2mA(1000),mA2raw(1000));
} }
// prepare values for current detection
sampleDelay = 0;
lastSampleTaken = millis();
progTripValue = mA2raw(TRIP_CURRENT_PROG); progTripValue = mA2raw(TRIP_CURRENT_PROG);
} }
bool MotorDriver::isPWMCapable() { bool MotorDriver::isPWMCapable() {
@ -156,7 +172,12 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) { void MotorDriver::setPower(POWERMODE mode) {
bool on=mode==POWERMODE::ON; if (powerMode == mode) return;
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
lastPowerChange[(int)mode] = micros();
if (mode == POWERMODE::OVERLOAD)
globalOverloadStart = lastPowerChange[(int)mode];
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
if (on) { if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF // when switching a track On, we need to check the crrentOffset with the pin OFF
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) { if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
@ -196,8 +217,8 @@ bool MotorDriver::canMeasureCurrent() {
return currentPin!=UNUSED_PIN; return currentPin!=UNUSED_PIN;
} }
/* /*
* Return the current reading as pin reading 0 to 1023. If the fault * Return the current reading as pin reading 0 to max resolution (1024 or 4096).
* pin is activated return a negative current to show active fault pin. * If the fault pin is activated return a negative current to show active fault pin.
* As there is no -0, cheat a little and return -1 in that case. * As there is no -0, cheat a little and return -1 in that case.
* *
* senseOffset handles the case where a shield returns values above or below * senseOffset handles the case where a shield returns values above or below
@ -214,14 +235,12 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current); // if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
current = current-senseOffset; // adjust with offset current = current-senseOffset; // adjust with offset
if (current<0) current=0-current; if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) { // current >= 0 here, we use negative current as fault pin flag
if (invertFault && isLOW(fastFaultPin)) if ((faultPin != UNUSED_PIN) && powerPin) {
return (current == 0 ? -1 : -current); if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
if (!invertFault && !isLOW(fastFaultPin))
return (current == 0 ? -1 : -current); return (current == 0 ? -1 : -current);
} }
return current; return current;
} }
#ifdef ANALOG_READ_INTERRUPT #ifdef ANALOG_READ_INTERRUPT
@ -259,6 +278,7 @@ void MotorDriver::startCurrentFromHW() {
#endif //ANALOG_READ_INTERRUPT #endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#ifdef VARIABLE_TONES
uint16_t taurustones[28] = { 165, 175, 196, 220, uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330, 247, 262, 294, 330,
349, 392, 440, 494, 349, 392, 440, 494,
@ -267,16 +287,43 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
330, 284, 262, 247, 330, 284, 262, 247,
220, 196, 175, 165 }; 220, 196, 175, 165 };
#endif #endif
#endif
void MotorDriver::setDCSignal(byte speedcode) { void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN) if (brakePin == UNUSED_PIN)
return; return;
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO) #if defined(ARDUINO_AVR_UNO)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz // Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif #endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz case 9:
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs case 10:
// Timer2 (is differnet)
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
#endif #endif
default:
break;
}
// spedcoode is a dcc speed & direction // spedcoode is a dcc speed & direction
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80; byte tDir=speedcode & 0x80;
@ -284,11 +331,13 @@ void MotorDriver::setDCSignal(byte speedcode) {
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
{ {
int f = 131; int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) { if (tSpeed > 2) {
if (tSpeed <= 58) { if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ; f = taurustones[ (tSpeed-2)/2 ] ;
} }
} }
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
} }
#endif #endif
@ -327,7 +376,60 @@ void MotorDriver::setDCSignal(byte speedcode) {
interrupts(); interrupts();
} }
} }
void MotorDriver::throttleInrush(bool on) {
if (brakePin == UNUSED_PIN)
return;
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
return;
byte duty = on ? 208 : 0;
if (invertBrake)
duty = 255-duty;
#if defined(ARDUINO_ARCH_ESP32)
if(on) {
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
} else {
ledcDetachPin(brakePin);
}
#else
if(on){
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is different)
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
#endif
default:
break;
}
}
analogWrite(brakePin,duty);
#endif
}
unsigned int MotorDriver::raw2mA( int raw) { unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale); //DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale; return (int32_t)raw * senseFactorInternal / senseScale;
@ -356,64 +458,170 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),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);
} }
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { ///////////////////////////////////////////////////////////////////////////////////////////
if (millis() - lastSampleTaken < sampleDelay) return; // checkPowerOverload(useProgLimit, trackno)
lastSampleTaken = millis(); // bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); // byte trackno: trackmanager knows it's number (could be skipped?)
//
// Short ciruit handling strategy:
//
// There are the following power states: ON ALERT OVERLOAD OFF
// OFF state is only changed to/from manually. Power is on
// during ON and ALERT. Power is off during OVERLOAD and OFF.
// The overload mechanism changes between the other states like
//
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
// or
// ON -1-> ALERT -4-> ON
//
// Times are in class MotorDriver (MotorDriver.h).
//
// 1. ON to ALERT:
// Transition on fault pin condition or current overload
//
// 2. ALERT to OVERLOAD:
// Transition happens if different timeouts have elapsed.
// If only the fault pin is active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
// If only overcurrent is detected, timeout is
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
// If fault pin and overcurrent are active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
// Transition to OVERLOAD turns off power to the affected
// output (unless fault pins are shared)
// If the transition conditions are not fullfilled,
// transition according to 4 is tested.
//
// 3. OVERLOAD to ALERT
// Transiton happens when timeout has elapsed, timeout
// is named power_sample_overload_wait. It is started
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
// to OVERLOAD and then increased by a factor of 2
// at further entries to the OVERLOAD condition. This
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
// power_sample_overload_wait is reset by a poweroff or
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
// After timeout power is turned on again and state
// goes back to ALERT.
//
// 4. ALERT to ON
// Transition happens by watching the current and fault pin
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
// values have been good during that time, transition is
// made back to ON. Note that even if state is back to ON,
// the power_sample_overload_wait time is first reset
// later (see above).
//
// The time keeping is handled by timestamps lastPowerChange[]
// which are set by each power change and by lastBadSample which
// keeps track if conditions during ALERT have been good enough
// to go back to ON. The time differences are calculated by
// microsSinceLastPowerChange().
//
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
// Trackname for diag messages later
switch (powerMode) { switch (powerMode) {
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT; case POWERMODE::OFF: {
lastPowerMode = POWERMODE::OFF;
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
break; break;
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (commonFaultPin) {
if (lastCurrent < tripValue) {
setPower(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 case POWERMODE::ON: {
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); lastPowerMode = POWERMODE::ON;
bool cF = checkFault();
bool cC = checkCurrent(useProgLimit);
if(cF || cC ) {
if (cC) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
cF ? "FAULT" : "",
mA);
} else { } else {
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A'); DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
} }
setPower(POWERMODE::ALERT);
break;
} }
// all well
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
} }
if (lastCurrent < tripValue) { break;
sampleDelay = POWER_SAMPLE_ON_WAIT; }
if(power_good_counter<100)
power_good_counter++; case POWERMODE::ALERT: {
else // set local flags that handle how much is output to diag (do not output duplicates)
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
} else { bool powerModeChange = (powerMode != lastPowerMode);
unsigned long now = micros();
if (powerModeChange)
lastBadSample = now;
lastPowerMode = POWERMODE::ALERT;
// check how long we have been in this state
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
if(checkFault()) {
throttleInrush(true);
lastBadSample = now;
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
if ( mslpc < timeout) {
if (powerModeChange)
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
break;
}
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD); setPower(POWERMODE::OVERLOAD);
break;
}
if (checkCurrent(useProgLimit)) {
lastBadSample = now;
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
if (powerModeChange) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
}
break;
}
unsigned int mA=raw2mA(lastCurrent); unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue); unsigned int maxmA=raw2mA(tripValue);
power_good_counter=0; DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
sampleDelay = power_sample_overload_wait; trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay); throttleInrush(false);
if (power_sample_overload_wait >= 10000) setPower(POWERMODE::OVERLOAD);
power_sample_overload_wait = 10000; break;
else }
power_sample_overload_wait *= 2; // all well
unsigned long goodtime = micros() - lastBadSample;
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
if (true || notFromOverload) { // we did a RESTORE message XXX
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
}
throttleInrush(false);
setPower(POWERMODE::ON);
} }
break; break;
case POWERMODE::OVERLOAD: }
// Try setting it back on after the OVERLOAD_WAIT
setPower(POWERMODE::ON); case POWERMODE::OVERLOAD: {
sampleDelay = POWER_SAMPLE_ON_WAIT; lastPowerMode = POWERMODE::OVERLOAD;
// Debug code.... unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay); if (mslpc > power_sample_overload_wait) {
// adjust next wait time
power_sample_overload_wait *= 2;
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
// power on test
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);
}
break; break;
}
default: default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. break;
} }
} }

View File

@ -27,6 +27,10 @@
#include "IODevice.h" #include "IODevice.h"
#include "DCCTimer.h" #include "DCCTimer.h"
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) #define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
@ -74,8 +78,9 @@
// Virtualised Motor shield 1-track hardware Interface // Virtualised Motor shield 1-track hardware Interface
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
#define UNUSED_PIN 127 // inside int8_t #define UNUSED_PIN 255 // inside uint8_t
#endif #endif
#define MAX_PIN 254
class pinpair { class pinpair {
public: public:
@ -106,13 +111,13 @@ extern volatile portreg_t shadowPORTA;
extern volatile portreg_t shadowPORTB; extern volatile portreg_t shadowPORTB;
extern volatile portreg_t shadowPORTC; extern volatile portreg_t shadowPORTC;
enum class POWERMODE : byte { OFF, ON, OVERLOAD }; enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
class MotorDriver { class MotorDriver {
public: public:
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, int8_t fault_pin); byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
void setPower( POWERMODE mode); void setPower( POWERMODE mode);
POWERMODE getPower() { return powerMode;} POWERMODE getPower() { return powerMode;}
// as the port registers can be shadowed to get syncronized DCC signals // as the port registers can be shadowed to get syncronized DCC signals
@ -144,6 +149,7 @@ class MotorDriver {
}; };
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte); void setDCSignal(byte speedByte);
void throttleInrush(bool on);
inline void detachDCSignal() { inline void detachDCSignal() {
#if defined(__arm__) #if defined(__arm__)
pinMode(brakePin, OUTPUT); pinMode(brakePin, OUTPUT);
@ -174,7 +180,10 @@ class MotorDriver {
bool isPWMCapable(); bool isPWMCapable();
bool canMeasureCurrent(); bool canMeasureCurrent();
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte setCommonFaultPin() {
return commonFaultPin = true;
}
inline byte getFaultPin() { inline byte getFaultPin() {
return faultPin; return faultPin;
} }
@ -185,17 +194,46 @@ class MotorDriver {
inline void setTrackLetter(char c) { inline void setTrackLetter(char c) {
trackLetter = c; trackLetter = c;
}; };
// this returns how much time has passed since the last power change. If it
// was really long ago (approx > 52min) advance counter approx 35 min so that
// we are at 18 minutes again. Times for 32 bit unsigned long.
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange[(int)mode];
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
return diff;
};
#ifdef ANALOG_READ_INTERRUPT #ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW(); bool sampleCurrentFromHW();
void startCurrentFromHW(); void startCurrentFromHW();
#endif #endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
};
inline TRACK_MODE getMode() {
return trackMode;
};
private: private:
char trackLetter = '?'; char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track bool isProgTrack = false; // tells us if this is a prog track
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) { inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result); getFastPin(type, pin, 0, result);
} };
// side effect sets lastCurrent and tripValue
inline bool checkCurrent(bool useProgLimit) {
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
lastCurrent = getCurrentRaw();
if (lastCurrent < 0)
lastCurrent = -lastCurrent;
return lastCurrent >= tripValue;
};
// side effect sets lastCurrent
inline bool checkFault() {
lastCurrent = getCurrentRaw();
return lastCurrent < 0;
};
VPIN powerPin; VPIN powerPin;
byte signalPin, signalPin2, currentPin, faultPin, brakePin; byte signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
@ -216,10 +254,14 @@ class MotorDriver {
int rawCurrentTripValue; int rawCurrentTripValue;
// current sampling // current sampling
POWERMODE powerMode; POWERMODE powerMode;
unsigned long lastSampleTaken; POWERMODE lastPowerMode;
unsigned int sampleDelay; unsigned long lastPowerChange[4]; // timestamp in microseconds
unsigned long lastBadSample; // timestamp in microseconds
// used to sync restore time when common Fault pin detected
static unsigned long globalOverloadStart; // timestamp in microseconds
int progTripValue; int progTripValue;
int lastCurrent; int lastCurrent; //temp value
int tripValue; //temp value
#ifdef ANALOG_READ_INTERRUPT #ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp; volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent; volatile uint16_t sampleCurrent;
@ -227,16 +269,28 @@ class MotorDriver {
int maxmA; int maxmA;
int tripmA; int tripmA;
// Wait times for power management. Unit: milliseconds // Times for overload management. Unit: microseconds.
static const int POWER_SAMPLE_ON_WAIT = 100; // Base for wait time until power is turned on again
static const int POWER_SAMPLE_OFF_WAIT = 1000; static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20; // Time after we consider all faults old and forgotten
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
// Time after which we consider a ALERT over
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
// How long to ignore fault pin if current is under limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
// How long to ignore fault pin if current is higher than limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
// How long to wait between overcurrent and turning off
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
// Upper limit for retry period
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
// Trip current for programming track, 250mA. Change only if you really // Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either. // need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250; static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0; unsigned int power_good_counter = 0;
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
}; };
#endif #endif

View File

@ -1,7 +1,7 @@
/* /*
* © 2022 Paul M. Antoine * © 2022-2023 Paul M. Antoine
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2022 Harald Barth * © 2020-2023 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved. * (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved. * (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved. * (c) 2020 Harald Barth. All rights reserved.
@ -36,7 +36,7 @@
// custom defines in config.h. // custom defines in config.h.
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h #ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
#define UNUSED_PIN 127 // inside int8_t #define UNUSED_PIN 255 // inside uint8_t
#endif #endif
// The MotorDriver definition is: // The MotorDriver definition is:
@ -60,7 +60,8 @@
// Arduino STANDARD Motor Shield, used on different architectures: // Arduino STANDARD Motor Shield, used on different architectures:
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) #if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 // Standard Motor Shield definition for 3v3 processors (other than the ESP32)
// Setup for SAMD21 Sparkfun DEV board MUST use Arduino Motor Shield R3 (MUST be R3
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using // for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need // 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
// to tweak both these // to tweak both these
@ -70,6 +71,12 @@
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
#define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5)
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the // STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the // 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
@ -80,9 +87,11 @@
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \ new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN) new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
// EX 8874 based shield connected to a 3.3V system (like ESP32) and 12bit (4096) ADC
// numbers are GPIO numbers. comments are UNO form factor shield pin numbers
#define EX8874_SHIELD F("EX8874"),\ #define EX8874_SHIELD F("EX8874"),\
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.17, 5000, -36 /*-A4*/), \ new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.17, 5000, -39 /*-A5*/) new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/)
#else #else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification. // STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
@ -92,6 +101,12 @@
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \ #define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN) new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC
#define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, A5)
#endif #endif
// Pololu Motor Shield // Pololu Motor Shield
@ -120,10 +135,6 @@
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
#define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, -A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, -A5)
// 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), \

View File

@ -87,6 +87,9 @@ void SerialManager::init() {
delay(1000); delay(1000);
} }
#endif #endif
#ifdef SABERTOOTH
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#endif
} }
void SerialManager::broadcast(char * stringBuffer) { void SerialManager::broadcast(char * stringBuffer) {

View File

@ -117,6 +117,24 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'o': stream->print(va_arg(args, int), OCT); break; case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break; case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break; case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
case 'M':
{ // this prints a unsigned long microseconds time in readable format
unsigned long time = va_arg(args, long);
if (time >= 2000) {
time = time / 1000;
if (time >= 2000) {
printPadded(stream, time/1000, formatWidth, formatLeft);
stream->print(F("sec"));
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("msec"));
}
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("usec"));
}
}
break;
//case 'f': stream->print(va_arg(args, double), 2); break; //case 'f': stream->print(va_arg(args, double), 2); break;
//format width prefix //format width prefix
case '-': case '-':

View File

@ -31,19 +31,20 @@
#define APPLY_BY_MODE(findmode,function) \ #define APPLY_BY_MODE(findmode,function) \
FOR_EACH_TRACK(t) \ FOR_EACH_TRACK(t) \
if (trackMode[t]==findmode) \ if (track[t]->getMode()==findmode) \
track[t]->function; track[t]->function;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_PROG = -29718;
#endif
const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_OFF = 22479; const int16_t HASH_KEYWORD_OFF = 22479;
const int16_t HASH_KEYWORD_NONE = -26550;
const int16_t HASH_KEYWORD_DC = 2183; const int16_t HASH_KEYWORD_DC = 2183;
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
MotorDriver * TrackManager::track[MAX_TRACKS]; MotorDriver * TrackManager::track[MAX_TRACKS];
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS];
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
@ -73,7 +74,7 @@ void TrackManager::sampleCurrent() {
waiting = false; waiting = false;
tr++; tr++;
if (tr > lastTrack) tr = 0; if (tr > lastTrack) tr = 0;
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) { if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) {
return; // We could continue but for prog track we return; // We could continue but for prog track we
// rather do it in next interrupt beacuse // rather do it in next interrupt beacuse
// that gives us well defined sampling point. // that gives us well defined sampling point.
@ -84,7 +85,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) { if (!waiting) {
// look for a valid track to sample or until we are around // look for a valid track to sample or until we are around
while (true) { while (true) {
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW(); track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1); // for scope debug track[1]->setBrake(1);
waiting = true; waiting = true;
@ -116,19 +117,31 @@ void TrackManager::Setup(const FSH * shieldname,
// Default the first 2 tracks (which may be null) and perform HA waveform check. // Default the first 2 tracks (which may be null) and perform HA waveform check.
setTrackMode(0,TRACK_MODE_MAIN); setTrackMode(0,TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
setTrackMode(1,TRACK_MODE_PROG); setTrackMode(1,TRACK_MODE_PROG);
#else
setTrackMode(1,TRACK_MODE_MAIN);
#endif
// TODO Fault pin config for odd motor boards (example pololu) // Fault pin config for odd motor boards (example pololu)
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) FOR_EACH_TRACK(t) {
// && (mainDriver->getFaultPin() != UNUSED_PIN)); for (byte s=t+1;s<=lastTrack;s++) {
DCC::begin(shieldname); if (track[t]->getFaultPin() != UNUSED_PIN &&
track[t]->getFaultPin() == track[s]->getFaultPin()) {
track[t]->setCommonFaultPin();
track[s]->setCommonFaultPin();
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
}
}
}
DCC::setShieldName(shieldname);
} }
void TrackManager::addTrack(byte t, MotorDriver* driver) { void TrackManager::addTrack(byte t, MotorDriver* driver) {
trackMode[t]=TRACK_MODE_OFF;
track[t]=driver; track[t]=driver;
if (driver) { if (driver) {
track[t]->setPower(POWERMODE::OFF); track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
track[t]->setTrackLetter('A'+t); track[t]->setTrackLetter('A'+t);
lastTrack=t; lastTrack=t;
} }
@ -169,22 +182,27 @@ void TrackManager::setPROGSignal( bool on) {
// with interrupts turned off around the critical section // with interrupts turned off around the critical section
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab) continue; if (trackDCAddr[t]!=cab && cab != 0) continue;
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
} }
} }
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
//DIAG(F("Track=%c"),trackToSet+'A'); //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
// DC tracks require a motorDriver that can set brake! // DC tracks require a motorDriver that can set brake!
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
&& !track[trackToSet]->brakeCanPWM()) { #if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC"));
return false;
#endif
if (!track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC")); DIAG(F("Brake pin can't PWM: No DC"));
return false; return false;
} }
}
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
// remove pin from MUX matrix and turn it off // remove pin from MUX matrix and turn it off
@ -198,12 +216,16 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
} }
#endif #endif
#ifndef DISABLE_PROG
if (mode==TRACK_MODE_PROG) { if (mode==TRACK_MODE_PROG) {
#else
if (false) {
#endif
// only allow 1 track to be prog // only allow 1 track to be prog
FOR_EACH_TRACK(t) FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
track[t]->setPower(POWERMODE::OFF); track[t]->setPower(POWERMODE::OFF);
trackMode[t]=TRACK_MODE_OFF; track[t]->setMode(TRACK_MODE_NONE);
track[t]->makeProgTrack(false); // revoke prog track special handling track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t); streamTrackState(NULL,t);
} }
@ -211,7 +233,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} else { } else {
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
} }
trackMode[trackToSet]=mode; track[trackToSet]->setMode(mode);
trackDCAddr[trackToSet]=dcAddr; trackDCAddr[trackToSet]=dcAddr;
streamTrackState(NULL,trackToSet); streamTrackState(NULL,trackToSet);
@ -238,7 +260,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on // DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM // so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX. // capable tracks is now DC or DCX.
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) { if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
if (track[t]->isPWMCapable()) { if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below break; // in this mode, so abort and prevent globally below
@ -246,7 +268,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A'); //DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
} }
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) { } else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM); //DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM; canDo &= track[t]->trackPWM;
@ -284,7 +306,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
void TrackManager::applyDCSpeed(byte t) { void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]); uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
if (trackMode[t]==TRACK_MODE_DCX) if (track[t]->getMode()==TRACK_MODE_DCX)
speedByte = speedByte ^ 128; // reverse direction bit speedByte = speedByte ^ 128; // reverse direction bit
track[t]->setDCSignal(speedByte); track[t]->setDCSignal(speedByte);
} }
@ -306,11 +328,13 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN> if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
return setTrackMode(p[0],TRACK_MODE_MAIN); return setTrackMode(p[0],TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG> if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
return setTrackMode(p[0],TRACK_MODE_PROG); return setTrackMode(p[0],TRACK_MODE_PROG);
#endif
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
return setTrackMode(p[0],TRACK_MODE_OFF); return setTrackMode(p[0],TRACK_MODE_NONE);
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT); return setTrackMode(p[0],TRACK_MODE_EXT);
@ -328,15 +352,17 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast // null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return; if (track[t]==NULL) return;
auto format=F(""); auto format=F("");
switch(trackMode[t]) { switch(track[t]->getMode()) {
case TRACK_MODE_MAIN: case TRACK_MODE_MAIN:
format=F("<= %c MAIN>\n"); format=F("<= %c MAIN>\n");
break; break;
#ifndef DISABLE_PROG
case TRACK_MODE_PROG: case TRACK_MODE_PROG:
format=F("<= %c PROG>\n"); format=F("<= %c PROG>\n");
break; break;
case TRACK_MODE_OFF: #endif
format=F("<= %c OFF>\n"); case TRACK_MODE_NONE:
format=F("<= %c NONE>\n");
break; break;
case TRACK_MODE_EXT: case TRACK_MODE_EXT:
format=F("<= %c EXT>\n"); format=F("<= %c EXT>\n");
@ -358,19 +384,21 @@ byte TrackManager::nextCycleTrack=MAX_TRACKS;
void TrackManager::loop() { void TrackManager::loop() {
DCCWaveform::loop(); DCCWaveform::loop();
#ifndef DISABLE_PROG
DCCACK::loop(); DCCACK::loop();
#endif
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted; bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
nextCycleTrack++; nextCycleTrack++;
if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return; if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack]; MotorDriver * motorDriver=track[nextCycleTrack];
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG; bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack); motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
} }
MotorDriver * TrackManager::getProgDriver() { MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t) FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG) return track[t]; if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
return NULL; return NULL;
} }
@ -378,7 +406,7 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() { std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v; std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t) FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]); if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
return v; return v;
} }
#endif #endif
@ -388,7 +416,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
MotorDriver * driver=track[t]; MotorDriver * driver=track[t];
if (!driver) continue; if (!driver) continue;
switch (trackMode[t]) { switch (track[t]->getMode()) {
case TRACK_MODE_MAIN: case TRACK_MODE_MAIN:
if (setProg) break; if (setProg) break;
// toggle brake before turning power on - resets overcurrent error // toggle brake before turning power on - resets overcurrent error
@ -416,7 +444,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
driver->setBrake(false); driver->setBrake(false);
driver->setPower(mode); driver->setPower(mode);
break; break;
case TRACK_MODE_OFF: case TRACK_MODE_NONE:
break; break;
} }
} }
@ -424,7 +452,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
POWERMODE TrackManager::getProgPower() { POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t) FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG) if (track[t]->getMode()==TRACK_MODE_PROG)
return track[t]->getPower(); return track[t]->getPower();
return POWERMODE::OFF; return POWERMODE::OFF;
} }
@ -469,7 +497,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (joined) { if (joined) {
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
if (trackMode[t]==TRACK_MODE_PROG) { if (track[t]->getMode()==TRACK_MODE_PROG) {
tempProgTrack = t; tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN); setTrackMode(t, TRACK_MODE_MAIN);
break; break;

View File

@ -27,10 +27,6 @@
#include "MotorDriver.h" #include "MotorDriver.h"
// Virtualised Motor shield multi-track hardware Interface // Virtualised Motor shield multi-track hardware Interface
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1; const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
@ -86,6 +82,13 @@ class TrackManager {
static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited static bool progTrackBoosted; // true when prog track is not current limited
#ifdef DEBUG_ADC
public:
#else
private:
#endif
static MotorDriver* track[MAX_TRACKS];
private: private:
static void addTrack(byte t, MotorDriver* driver); static void addTrack(byte t, MotorDriver* driver);
static byte lastTrack; static byte lastTrack;
@ -93,8 +96,6 @@ class TrackManager {
static POWERMODE mainPowerGuess; static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t); static void applyDCSpeed(byte t);
static MotorDriver* track[MAX_TRACKS];
static TRACK_MODE trackMode[MAX_TRACKS];
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join static byte tempProgTrack; // holds the prog track number during join

View File

@ -250,6 +250,7 @@
} }
} }
tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed); tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed);
DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData));
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant); IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
return tt; return tt;
#else #else

View File

@ -69,10 +69,12 @@ protected:
uint16_t id; uint16_t id;
} _turnoutData; // 3 bytes } _turnoutData; // 3 bytes
#ifndef DISABLE_EEPROM
// Address in eeprom of first byte of the _turnoutData struct (containing the closed flag). // Address in eeprom of first byte of the _turnoutData struct (containing the closed flag).
// Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and // Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and
// for all LCN turnouts. // for all LCN turnouts.
uint16_t _eepromAddress = 0; uint16_t _eepromAddress = 0;
#endif
// Pointer to next turnout on linked list. // Pointer to next turnout on linked list.
Turnout *_nextTurnout = 0; Turnout *_nextTurnout = 0;

View File

@ -235,6 +235,10 @@ int WiThrottle::getLocoId(byte * cmd) {
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 *
if (locoid > 10239 || locoid < -1) {
StringFormatter::send(stream, F("No valid DCC loco %d\n"), locoid);
return;
}
byte * aval=cmd; byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++; while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;> if (*aval) aval+=2; // skip ;>
@ -527,8 +531,11 @@ void WiThrottle::sendRoster(Print* stream) {
rosterSent=true; rosterSent=true;
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) { for (int16_t r=0;;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2); int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
if (cabid == INT16_MAX)
break;
if (cabid > 0)
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"), StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L'); RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
} }
@ -544,14 +551,14 @@ void WiThrottle::sendRoutes(Print* stream) {
// first pass automations // first pass automations
for (int ix=0;;ix+=2) { for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix); int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break; if (id==INT16_MAX) break;
const FSH * desc=RMFT2::getRouteDescription(id); const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc); StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
} }
// second pass routes. // second pass routes.
for (int ix=0;;ix+=2) { for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix); int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break; if (id==INT16_MAX) break;
const FSH * desc=RMFT2::getRouteDescription(id); const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc); StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
} }
@ -567,9 +574,13 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); const FSH * functionNames= RMFT2::getRosterFunctions(locoid);
if (!functionNames) { if (functionNames == NULL) {
// no roster, use non-exrail presets as above // no roster entry for locoid, try to find default entry
functionNames= RMFT2::getRosterFunctions(0);
}
if (functionNames == NULL) {
// no default roster entry either, use non-exrail presets as above
} }
else if (GETFLASH(functionNames)=='\0') { else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given // "" = Roster but no functions given
@ -584,7 +595,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
fkeys=0; fkeys=0;
bool firstchar=true; bool firstchar=true;
for (int fx=0;;fx++) { for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx); char c=GETFLASH((char *)functionNames+fx);
if (c=='\0') { if (c=='\0') {
fkeys++; fkeys++;
break; break;

View File

@ -1,5 +1,7 @@
/* /*
© 2021, Harald Barth. © 2023 Paul M. Antoine
© 2021 Harald Barth
© 2023 Nathan Kellenicki
This file is part of CommandStation-EX This file is part of CommandStation-EX
@ -20,6 +22,7 @@
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#include <vector> #include <vector>
#include "defines.h" #include "defines.h"
#include "ESPmDNS.h"
#include <WiFi.h> #include <WiFi.h>
#include "esp_wifi.h" #include "esp_wifi.h"
#include "WifiESP32.h" #include "WifiESP32.h"
@ -105,11 +108,18 @@ void wifiLoop(void *){
} }
#endif #endif
char asciitolower(char in) {
if (in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
bool WifiESP::setup(const char *SSid, bool WifiESP::setup(const char *SSid,
const char *password, const char *password,
const char *hostname, const char *hostname,
int port, int port,
const byte channel) { const byte channel,
const bool forceAP) {
bool havePassword = true; bool havePassword = true;
bool haveSSID = true; bool haveSSID = true;
bool wifiUp = false; bool wifiUp = false;
@ -137,7 +147,8 @@ bool WifiESP::setup(const char *SSid,
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0) if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
havePassword = false; havePassword = false;
if (haveSSID && havePassword) { if (haveSSID && havePassword && !forceAP) {
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
#ifdef SERIAL_BT_COMMANDS #ifdef SERIAL_BT_COMMANDS
WiFi.setSleep(true); WiFi.setSleep(true);
@ -174,16 +185,20 @@ bool WifiESP::setup(const char *SSid,
} }
} }
} }
if (!haveSSID) { if (!haveSSID || forceAP) {
// prepare all strings // prepare all strings
String strSSID("DCC_"); String strSSID(forceAP ? SSid : "DCCEX_");
String strPass("PASS_"); String strPass(forceAP ? password : "PASS_");
if (!forceAP) {
String strMac = WiFi.macAddress(); String strMac = WiFi.macAddress();
strMac.remove(0,9); strMac.remove(0,9);
strMac.replace(":",""); strMac.replace(":","");
strMac.replace(":",""); strMac.replace(":","");
// convert mac addr hex chars to lower case to be compatible with AT software
std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower);
strSSID.concat(strMac); strSSID.concat(strMac);
strPass.concat(strMac); strPass.concat(strMac);
}
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
#ifdef SERIAL_BT_COMMANDS #ifdef SERIAL_BT_COMMANDS
@ -209,6 +224,15 @@ bool WifiESP::setup(const char *SSid,
// no idea to go on // no idea to go on
return false; return false;
} }
// Now Wifi is up, register the mDNS service
if(!MDNS.begin(hostname)) {
DIAG(F("Wifi setup failed to start mDNS"));
}
if(!MDNS.addService("withrottle", "tcp", 2560)) {
DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
}
server = new WiFiServer(port); // start listening on tcp port server = new WiFiServer(port); // start listening on tcp port
server->begin(); server->begin();
// server started here // server started here

View File

@ -1,5 +1,6 @@
/* /*
* © 2021, Harald Barth. * © 2021 Harald Barth
* © 2023 Nathan Kellenicki
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -31,7 +32,8 @@ public:
const char *wifiPassword, const char *wifiPassword,
const char *hostname, const char *hostname,
const int port, const int port,
const byte channel); const byte channel,
const bool forceAP);
static void loop(); static void loop();
private: private:
}; };

View File

@ -2,6 +2,7 @@
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2022 Harald Barth * © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow * © 2020-2022 Chris Harlow
* © 2023 Nathan Kellenicki
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -52,10 +53,32 @@ Stream * WifiInterface::wifiStream;
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
#define NUM_SERIAL 3 #define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial3
#endif
#if defined(ARDUINO_ARCH_STM32)
// Handle serial ports availability on STM32 for variants!
// #undef NUM_SERIAL
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial6
#elif defined(ARDUINO_NUCLEO_F446RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial3
#define SERIAL3 Serial5
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else
#warning This variant of Nucleo not yet explicitly supported
#endif
#endif #endif
#ifndef NUM_SERIAL #ifndef NUM_SERIAL
#define NUM_SERIAL 1 #define NUM_SERIAL 1
#define SERIAL1 Serial1
#endif #endif
bool WifiInterface::setup(long serial_link_speed, bool WifiInterface::setup(long serial_link_speed,
@ -63,7 +86,8 @@ bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiPassword, const FSH *wifiPassword,
const FSH *hostname, const FSH *hostname,
const int port, const int port,
const byte channel) { const byte channel,
const bool forceAP) {
wifiSerialState wifiUp = WIFI_NOAT; wifiSerialState wifiUp = WIFI_NOAT;
@ -75,27 +99,34 @@ bool WifiInterface::setup(long serial_link_speed,
(void) hostname; (void) hostname;
(void) port; (void) port;
(void) channel; (void) channel;
(void) forceAP;
#endif #endif
// See if the WiFi is attached to the first serial port
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS) #if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
Serial1.begin(serial_link_speed); SERIAL1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel); wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
#endif #endif
// Other serials are tried, depending on hardware. // Other serials are tried, depending on hardware.
// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!)
#if defined(ARDUINO_AVR_MEGA2560)
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS) #if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
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, channel); wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
} }
#endif #endif
#endif
// We guess here that in all architctures that have a Serial3
// we can use it for our purpose.
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS) #if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
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, channel); wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
} }
#endif #endif
@ -113,7 +144,7 @@ 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, byte channel) { const FSH* hostname, int port, byte channel, bool forceAP) {
wifiSerialState wifiState; wifiSerialState wifiState;
static uint8_t ntry = 0; static uint8_t ntry = 0;
ntry++; ntry++;
@ -122,7 +153,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
DIAG(F("++ Wifi Setup Try %d ++"), ntry); DIAG(F("++ Wifi Setup Try %d ++"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel); wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
if (wifiState == WIFI_NOAT) { if (wifiState == WIFI_NOAT) {
LCD(4, F("WiFi no AT chip")); LCD(4, F("WiFi no AT chip"));
@ -146,7 +177,7 @@ 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, byte channel) { const FSH* hostname, int port, byte channel, bool forceAP) {
bool ipOK = false; bool ipOK = false;
bool oldCmd = false; bool oldCmd = false;
@ -169,7 +200,21 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// 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, true, false); // Makes this visible on the console if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0";
for (int i=0; i<8;i++) {
while(!wifiStream->available());
version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]);
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
forceAP = true;
}
}
}
checkForOK(2000, true, false);
#ifdef DONT_TOUCH_WIFI_CONF #ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config")); DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
@ -199,7 +244,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
if (!checkForOK(1000, F("0.0.0.0"), true,false)) if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true; ipOK = true;
} }
} else { } else if (!forceAP) {
// 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
@ -259,6 +304,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
i=0; i=0;
do { do {
if (!forceAP) {
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\",%d,4\r\n"), StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
@ -268,6 +314,10 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel); macTail, password, channel);
} }
} else {
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
oldCmd ? "" : "_CUR", SSid, password, channel);
}
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, 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("Warning: Setting AP SSID and password failed")); // but issue warning DIAG(F("Warning: Setting AP SSID and password failed")); // but issue warning

View File

@ -1,6 +1,7 @@
/* /*
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2020, Harald Barth. * © 2020, Harald Barth.
* © 2023 Nathan Kellenicki
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -36,17 +37,18 @@ public:
const FSH *wifiPassword, const FSH *wifiPassword,
const FSH *hostname, const FSH *hostname,
const int port, const int port,
const byte channel); const byte channel,
const bool forceAP);
static void loop(); static void loop();
static void ATCommand(HardwareSerial * stream,const byte *command); static void ATCommand(HardwareSerial * stream,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, byte channel); const FSH *hostname, int port, byte channel, bool forceAP);
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, byte channel); const FSH *hostname, int port, byte channel, bool forceAP);
static bool checkForOK(const unsigned int timeout, 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 checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
static bool connected; static bool connected;

View File

@ -1,9 +1,10 @@
/* /*
* © 2022 Paul M. Antoine * © 2022 Paul M. Antoine
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2021 Harald Barth * © 2020-2023 Harald Barth
* © 2020-2021 Fred Decker * © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -27,6 +28,16 @@ The configuration file for DCC-EX Command Station
**********************************************************************/ **********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// If you want to add your own motor driver definition(s), add them here
// For example MY_SHIELD with display name "MINE":
// (remove comment start and end marker if you want to edit and use that)
/*
#define MY_SHIELD F("MINE"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5)
*/
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software // NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage // check the quick install guides!!! Some of these boards require a voltage
@ -34,19 +45,34 @@ The configuration file for DCC-EX Command Station
// the correct resistor could damage the sense pin on your Arduino or destroy // the correct resistor could damage the sense pin on your Arduino or destroy
// the device. // the device.
// //
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: // DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h
// //
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel // 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 // IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
// | // |
// +-----------------------v // +-----------------------v
// //
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD #define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
//
/////////////////////////////////////////////////////////////////////////////////////
//
// If you want to restrict the maximum current LOWER than what your
// motor shield can provide, you can do that here. For example if you
// have a motor shield that can provide 5A and your power supply can
// only provide 2.5A then you should restict the maximum current to
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
// it would be 2250, adjust the number according to your PS. If your
// PS has a higher rating than your motor shield you do not need this.
// You can use this as well if you are cautious and your trains do not
// need full current.
// #define MAX_CURRENT 2250
//
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// //
// The IP port to talk to a WIFI or Ethernet shield. // The IP port to talk to a WIFI or Ethernet shield.
@ -98,6 +124,11 @@ The configuration file for DCC-EX Command Station
// this line exists or not. If you need to use an alternate channel (we recommend // this line exists or not. If you need to use an alternate channel (we recommend
// using only 1,6, or 11) you may change it here. // using only 1,6, or 11) you may change it here.
#define WIFI_CHANNEL 1 #define WIFI_CHANNEL 1
//
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
// true. Otherwise it is assumed that you'd like to connect to an existing network
// with that SSID.
#define WIFI_FORCE_AP false
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// //
@ -128,7 +159,7 @@ The configuration file for DCC-EX Command Station
//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied) //OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied)
// 128x32 or 128x64 I2C SSD1306-based devices are supported. // 128x32 or 128x64 I2C SSD1306-based devices are supported.
// Use 132,64 for a SH1106-based I2C device with a 128x64 display. // Use 132,64 for a SH1106-based I2C device with a 128x64 display.
// #define OLED_DRIVER 128,32,0x3c // #define OLED_DRIVER 0x3c,128,32
// Define scroll mode as 0, 1 or 2 // Define scroll mode as 0, 1 or 2
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), // * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
@ -141,7 +172,7 @@ The configuration file for DCC-EX Command Station
// //
// If you do not need the EEPROM at all, you can disable all the code that saves // If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO // data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM // and want to use the EXRAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work. // to do that. Of course, then none of the EEPROM related commands work.
// //
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled, // EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
@ -149,6 +180,17 @@ The configuration file for DCC-EX Command Station
// //
// #define DISABLE_EEPROM // #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE PROG
//
// If you do not need programming capability, you can disable all programming related
// commands. You might want to do that if you are using an Arduino UNO and still want
// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both.
//
// Note this disables all programming functionality, including EXRAIL.
//
// #define DISABLE_PROG
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
// is 127 and the first long address is 128. There are manufacturers which have // is 127 and the first long address is 128. There are manufacturers which have
@ -224,5 +266,15 @@ The configuration file for DCC-EX Command Station
// //
//#define SERIAL_BT_COMMANDS //#define SERIAL_BT_COMMANDS
// SABERTOOTH
//
// This is a very special option and only useful if you happen to have a
// sabertooth motor controller from dimension engineering configured to
// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX)
// and GPIO16 (RX, currently unused).
// The number defined is the DCC address for which speed controls are sent
// to the sabertooth controller _as_well_. Default: Undefined.
//
//#define SABERTOOTH 1
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,169 +0,0 @@
/**********************************************************************
Config.h
COPYRIGHT (c) 2013-2016 Gregg E. Berman
COPYRIGHT (c) 2020 Fred Decker
The configuration file for DCC++ EX Command Station
**********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
// generating resitor on the current sense pin of the device. Failure to select
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
//
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S
// |
// +-----------------------v
//
// #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"),
// new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN),
// new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
//
#define IP_PORT 2560
/////////////////////////////////////////////////////////////////////////////////////
//
// NOTE: Only supported on Arduino Mega
// Set to false if you not even want it on the Arduino Mega
//
//#define ENABLE_WIFI true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE WiFi Parameters (only in effect if WIFI is on)
//
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
// the <+> commands and this sketch will not change anything over
// AT commands and the other WIFI_* defines below do not have any effect.
//#define DONT_TOUCH_WIFI_CONF
//
// WIFI_SSID is the network name IF you want to use your existing home network.
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
//
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
// to connect to the previously configured network and if that fails
// fall back to Access Point mode. The SSID of the AP will be
// automatically set to DCCEX_*.
//
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_SSID "Your network name"
//
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "deadcafe"
//
// WIFI_HOSTNAME: You probably don't need to change this
#define WIFI_HOSTNAME "dccex"
//
/////////////////////////////////////////////////////////////////////////////////////
//
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
// to set this if you have an extremely slow Wifi router.
//
#define WIFI_CONNECT_TIMEOUT 14000
/////////////////////////////////////////////////////////////////////////////////////
//
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
// is not for Wifi. You will then need the Arduino Ethernet library as well
//
//#define ENABLE_ETHERNET true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
//
//#define IP_ADDRESS { 192, 168, 1, 31 }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
//
// Uncomment to use with Ethernet Shields
//
// Ethernet Shields do not have have a MAC address in hardware. There may be one on
// a sticker on the Shield that you should use. Otherwise choose one of the ones below
// Be certain that no other device on your network has this same MAC address!
//
// 52:b8:8a:8e:ce:21
// e3:e9:73:e1:db:0d
// 54:2b:13:52:ac:0c
// NOTE: This is not used with ESP8266 WiFi modules.
//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
//
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
//
// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780
// controller and a PCF8574 based I2C 'backpack',
// OR an I2C Oled screen based on SSD1306 (128x64 or 128x32) controller,
// OR an I2C Oled screen based on SH1106 (132x64) controller.
// To enable, uncomment one of the lines below
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
//#define LCD_DRIVER {SubBus_4,0x27},20,4
//OR define OLED_DRIVER width,height in pixels (address auto detected)
#if defined(ARDUINO_ARCH_STM32)
#define OLED_DRIVER 0x3c, 128, 64
#else
#define OLED_DRIVER {SubBus_0,0x3c}, 128, 32
#endif
#define SCROLLMODE 1
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work.
//
#define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
//
// According to norm RCN-213 a DCC packet with a 1 is closed/straight
// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous
// versions of DCC++EX, a turnout throw command was implemented in the packet as
// '1' and a close command as '0'. The #define below makes the states
// match with the norm. But we don't want to cause havoc on existent layouts,
// so we define this only for new installations. If you don't want this,
// don't add it to your config.h.
//#define DCC_TURNOUTS_RCN_213
// The following #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
// (throw turnout).
//#define DCC_ACCESSORY_RCN_213
/////////////////////////////////////////////////////////////////////////////////////

View File

@ -148,7 +148,6 @@
#define I2C_USE_WIRE #define I2C_USE_WIRE
#endif #endif
/* TODO when ready /* TODO when ready
#elif defined(ARDUINO_ARCH_RP2040) #elif defined(ARDUINO_ARCH_RP2040)
#define ARDUINO_TYPE "RP2040" #define ARDUINO_TYPE "RP2040"
@ -183,6 +182,15 @@
#define WIFI_ON false #define WIFI_ON false
#endif #endif
#ifndef WIFI_FORCE_AP
#define WIFI_FORCE_AP false
#else
#if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false
#else
#error WIFI_FORCE_AP needs to be true or false
#endif
#endif
#if ENABLE_ETHERNET #if ENABLE_ETHERNET
#if defined(HAS_ENOUGH_MEMORY) #if defined(HAS_ENOUGH_MEMORY)
#define ETHERNET_ON true #define ETHERNET_ON true
@ -206,7 +214,7 @@
#define WIFI_SERIAL_LINK_SPEED 115200 #define WIFI_SERIAL_LINK_SPEED 115200
#if __has_include ( "myAutomation.h") #if __has_include ( "myAutomation.h")
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) #if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
#define EXRAIL_ACTIVE #define EXRAIL_ACTIVE
#else #else
#define EXRAIL_WARNING #define EXRAIL_WARNING

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# #
# © 2022 Harald Barth # © 2022,2023 Harald Barth
# #
# This file is part of CommandStation-EX # This file is part of CommandStation-EX
# #
@ -29,14 +29,33 @@ ACLI="./bin/arduino-cli"
function need () { function need () {
type -p $1 > /dev/null && return type -p $1 > /dev/null && return
dpkg -l $1 2>&1 | egrep ^ii >/dev/null && return
sudo apt-get install $1 sudo apt-get install $1
type -p $1 > /dev/null && return type -p $1 > /dev/null && return
echo "Could not install $1, abort" echo "Could not install $1, abort"
exit 255 exit 255
} }
need git need git
if cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then
# we are on a raspi where we do not support graphical
unset DISPLAY
fi
if [ x$DISPLAY != x ] ; then
# we have DISPLAY, do the graphic thing
need python3-tk
need python3.8-venv
mkdir -p ~/ex-installer/venv
python3 -m venv ~/ex-installer/venv
cd ~/ex-installer/venv || exit 255
source ./bin/activate
git clone https://github.com/DCC-EX/EX-Installer
cd EX-Installer || exit 255
pip3 install -r requirements.txt
exec python3 -m ex_installer
fi
if test -d `basename "$DCCEXGITURL"` ; then if test -d `basename "$DCCEXGITURL"` ; then
: assume we are almost there : assume we are almost there
cd `basename "$DCCEXGITURL"` || exit 255 cd `basename "$DCCEXGITURL"` || exit 255

View File

@ -173,6 +173,8 @@ board = esp32dev
framework = arduino framework = arduino
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
build_flags = -std=c++17 build_flags = -std=c++17
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F411RE] [env:Nucleo-F411RE]
platform = ststm32 platform = ststm32
@ -188,7 +190,7 @@ platform = ststm32
board = nucleo_f446re board = nucleo_f446re
framework = arduino framework = arduino
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes monitor_echo = yes

View File

@ -3,8 +3,43 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define VERSION "5.0.1"
#define VERSION "4.2.47" // 5.0.1 - Check bad AT firmware version
// 5.0.0 - Make 4.2.69 the 5.0.0 release
// 4.2.69 - Bugfix: Make <!> work in DC mode
// 4.2.68 - Rename track mode OFF to NONE
// 4.2.67 - AVR: Pin specific timer register seting
// - Protect Uno user from choosing DC(X)
// - More Nucleo variant defines
// - GPIO PCA9555 / TCA9555 support
// 4.2.66 - Throttle inrush current by applying PWM to brake pin when
// fault pin goes active
// 4.2.65 - new config WIFI_FORCE_AP option
// 4.2.63 - completely new overcurrent detection
// - ESP32 protect from race in RMT code
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
// - Update IO_EXTurntable.h to remove forced I2C clock speed
// - Show device offline if EX-Turntable not connected
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
// 4.2.58 - Start motordriver as soon as possible but without waveform
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)
// - Optimize analog read STM32
// 4.2.56 - Update IO_RotaryEncoder.h:
// - Improved I2C communication, non-blocking reads
// - Enable sending positions to the encoder from EXRAIL via SERVO()
// 4.2.55 - Optimize analog read for AVR
// 4.2.54 - EX8874 shield in config.example.h
// - Fix: Better warnings for pin number errors
// - Fix: Default roster list possible in Withrottle and <jR>
// - Fix: Pin handling supports pins up to 254
// 4.2.53 - Fix: Fault pin handling made more straight forward
// 4.2.52 - Experimental support for sabertooth motor controller on ESP32
// 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash
// 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check
// 4.2.49 - Exrail SPEED take notice of external direction change
// 4.2.48 - BROADCAST/WITHROTTLE Exrail macros
// 4.2.47 - Correct response to <JA 0> // 4.2.47 - Correct response to <JA 0>
// 4.2.46 - Support boards with inverted fault pin // 4.2.46 - Support boards with inverted fault pin
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events