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

Compare commits

...

229 Commits

Author SHA1 Message Date
Kcsmith0708
42f1dc2345
Merge 183b824a5d into fb226311e5 2023-08-24 17:57:13 +00:00
Colin Murdoch
fb226311e5 Update myHal.cpp_example.txt
Added missing ::create to LiquidCrystal HAL definition
2023-08-24 14:54:33 +01:00
Harald Barth
25f8852af6 call devel for 5.1.X version number update 2023-08-24 10:09:38 +02:00
Harald Barth
9842ea8a42 Bugfix: execute 30ms off time before rejoin 2023-08-24 10:07:25 +02:00
peteGSX
fa0aa27d46 Add OPCODE list to DCCEXParser.cpp 2023-08-24 10:07:15 +02:00
kempe63
4b2c0702a4 Update version.h
Update version.h for IO_PCA9555 changes
2023-08-18 11:40:34 +01:00
kempe63
e27cceeb74 Update PCA9555.h inconsistencies to IO_MCP23017.h causing IO_PCA9555.h compile error when configure for Mux
Updated Class PCA9555 definition reflecting changes in IO_MCP23017.h to support PCA9548 mux. Checked with PCA9555 base board, compiles and run EXRAIL script with  output driver
2023-08-18 11:30:37 +01:00
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
Colin Murdoch
33306219c8 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-04-19 19:46:12 +01:00
Colin Murdoch
d857c4f2e4 Added to Copyright notice
Added my name to copyright notice
2023-04-19 19:45:40 +01:00
Asbelos
70fae16ab3 Correct response to <JA 0> 2023-04-19 11:18:47 +01:00
Harald Barth
f465020e93 Support boards with inverted fault pin 2023-04-17 23:40:48 +02:00
Harald Barth
235bcc9ff0 Merge branch 'devel' into devel-invfault 2023-04-17 23:20:01 +02:00
pmantoine
d2cc60812d Merge ESP32-fixes into DCCTimerESP 2023-04-16 15:40:27 +08:00
pmantoine
75f274e3b7 STM32 unsupported board selection error reporting 2023-04-13 15:27:22 +08:00
Colin Murdoch
ff53b90034 Update version.h
Added ONCLOCKMINS for FastClock
2023-04-12 12:11:43 +01:00
Colin Murdoch
1daa0a9ba9 Merge branch 'devel-plus-fastclockaddons' into devel 2023-04-12 12:09:27 +01:00
Colin Murdoch
294b9693c5 Additions to FastClock
Added ONCLOCKMINS to fastclock to allow hourly repeated events.
2023-04-12 12:07:08 +01:00
Harald Barth
16e44eb11a Check for max 16 analog channels 2023-04-11 12:13:47 +02:00
peteGSX
4bad334875 Update version 2023-04-11 12:01:25 +10:00
peteGSX
32491854ff
Merge pull request #330 from DCC-EX:powershell-installer
Powershell-installer
2023-04-11 11:59:28 +10:00
peteGSX
3e95372816 Rename installer exe 2023-04-11 11:48:35 +10:00
peteGSX
05b0fc3d2e Add exe version 2023-04-11 10:26:15 +10:00
peteGSX
bb7cdc5422 WiFi AP mode compiles 2023-04-11 09:42:54 +10:00
peteGSX
6199cecd42 0.0.7 Ready for testing 2023-04-11 08:55:04 +10:00
peteGSX
1aae0aed0a Working on config output 2023-04-11 05:31:44 +10:00
peteGSX
1d29be9de6 Working on 0.0.7 2023-04-10 19:52:31 +10:00
Harald Barth
bfa33a9df7 Fix STM32 set right port mode bits for analog port 2023-04-10 01:47:00 +02:00
Harald Barth
49c0a1a55a Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-04-08 23:35:48 +02:00
Harald Barth
9b7d1ae858 STM32: Use predefined function for pinnames 2023-04-08 23:33:33 +02:00
Colin Murdoch
c11d8f6359 Add TURNOUTL Macro to EXRAIL
Add TURNOUTL Macro to EXRAIL and retrospective update to version.h to record addition of EX-FastClock mods.
2023-04-08 17:22:39 +01:00
pmantoine
3868bb19ac More uint type fixes 2023-04-08 14:51:10 +08:00
pmantoine
7589917638 STM32 ADCee fix 2023-04-08 14:15:38 +08:00
peteGSX
e7d9626a72 Fix CLI directory name 2023-04-08 08:39:42 +10:00
peteGSX
fe035f4096 Move from temp to user home dir 2023-04-08 08:38:02 +10:00
peteGSX
4d236446b0 Add core index update 2023-04-08 06:57:09 +10:00
peteGSX
a100d709ce Install core library, add batch wrapper 2023-04-08 06:22:09 +10:00
Harald Barth
0d82370380 devel date 2023-04-07 20:46:20 +02:00
Harald Barth
ff6034dff2 curl only needed when downloading 2023-04-07 20:45:21 +02:00
Harald Barth
5d0de6b807 platformio wants this 2023-04-07 20:44:40 +02:00
peteGSX
2e518fcac2 Enable using existing configs 2023-04-07 19:30:15 +10:00
peteGSX
be273454bc Merge branch 'powershell-installer' of https://github.com/DCC-EX/CommandStation-EX into powershell-installer 2023-04-07 19:04:03 +10:00
peteGSX
751b46b6bb Initial test working! 2023-04-07 19:03:51 +10:00
peteGSX
91bc9df44e Working on CLI commands 2023-04-07 19:03:51 +10:00
peteGSX
72ceb63913 Start getting tag list 2023-04-07 19:03:50 +10:00
peteGSX
d2c7e7fb8d Working on logic 2023-04-07 19:03:50 +10:00
peteGSX
61db37c7ea Got tag version and URL 2023-04-07 19:03:50 +10:00
peteGSX
3a3071f35b Got tag list 2023-04-07 19:03:50 +10:00
peteGSX
ef3d36ae25 Figuring out commands 2023-04-07 19:03:50 +10:00
pmantoine
c3d2e5b222 Fix to PIO build target names for Teensy 2023-04-07 13:59:49 +10:00
peteGSX
9f212c27bf Initial test working! 2023-04-07 08:06:32 +10:00
peteGSX
de06c0ae3e Working on CLI commands 2023-04-06 15:06:22 +10:00
peteGSX
18a992bf08 Start getting tag list 2023-04-06 05:31:11 +10:00
peteGSX
de6e91a778 Working on logic 2023-04-05 15:54:48 +10:00
peteGSX
b18df1405c Got tag version and URL 2023-04-05 05:30:00 +10:00
peteGSX
305e0902f4 Got tag list 2023-04-04 19:30:59 +10:00
peteGSX
95c1b1da31 Figuring out commands 2023-04-04 15:48: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
31ecba08d8 faultPin can be inverted (from its inverted sense 2023-03-03 20:51:32 +01: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
48 changed files with 1964 additions and 659 deletions

View File

@ -2,6 +2,7 @@
* © 2022 Harald Barth * © 2022 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2020 Gregor Baues * © 2020 Gregor Baues
* © 2022 Colin Murdoch
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -167,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
} }
@ -204,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
@ -227,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

@ -2,6 +2,8 @@
* © 2022 Harald Barth * © 2022 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2020 Gregor Baues * © 2020 Gregor Baues
* © 2022 Colin Murdoch
*
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -33,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
@ -50,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);
@ -351,7 +351,7 @@ void DCCACK::callback(int value) {
switch (callbackState) { switch (callbackState) {
case AFTER_READ: case AFTER_READ:
if (ackManagerRejoin && autoPowerOff) { if (ackManagerRejoin && !autoPowerOff) {
progDriver->setPower(POWERMODE::OFF); progDriver->setPower(POWERMODE::OFF);
callbackStart=millis(); callbackStart=millis();
callbackState=WAITING_30; callbackState=WAITING_30;
@ -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,10 +3,11 @@
* © 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
* © 2022 Colin Murdoch
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -24,6 +25,79 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
/*
List of single character OPCODEs in use for reference.
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
Once a new OPCODE is decided upon, update this list.
Character, Usage
/, |EX-R| interactive commands
-, Remove from reminder table
=, |TM| configuration
!, Emergency stop
@, Reserved for future use - LCD messages to JMRI
#, Request number of supported cabs/locos; heartbeat
+, WiFi AT commands
?, Reserved for future use
0, Track power off
1, Track power on
a, DCC accessory control
A,
b, Write CV bit on main
B, Write CV bit
c, Request current command
C,
d,
D, Diagnostic commands
e, Erase EEPROM
E, Store configuration in EEPROM
f, Loco decoder function control (deprecated)
F, Loco decoder function control
g,
G,
h,
H, Turnout state broadcast
i, Reserved for future use - Turntable object broadcast
I, Reserved for future use - Turntable object command and control
j, Throttle responses
J, Throttle queries
k, Reserved for future use - Potentially Railcom
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L,
m,
M, Write DCC packet
n,
N,
o,
O, Output broadcast
p, Broadcast power state
P, Write DCC packet
q, Sensor deactivated
Q, Sensor activated
r, Broadcast address read on programming track
R, Read CVs
s, Display status
S, Sensor configuration
t, Cab/loco update command
T, Turnout configuration/control
u, Reserved for user commands
U, Reserved for user commands
v,
V, Verify CVs
w, Write CV on main
W, Write CV
x,
X, Invalid command
y,
Y, Output broadcast
z,
Z, Output configuration/control
*/
#include "StringFormatter.h" #include "StringFormatter.h"
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "DCC.h" #include "DCC.h"
@ -46,16 +120,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;
@ -63,7 +135,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
@ -216,6 +292,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
@ -285,6 +364,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
@ -368,6 +449,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;
@ -375,9 +457,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;
@ -392,6 +477,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;
@ -449,6 +535,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]>
{ {
@ -456,27 +543,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;
@ -487,7 +576,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;
} }
@ -495,18 +584,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;
@ -637,8 +728,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;
@ -892,6 +987,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) {
@ -912,6 +1008,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;
@ -934,11 +1031,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

@ -1,7 +1,7 @@
/* /*
* © 2022 Paul M. Antoine * © 2022-2023 Paul M. Antoine
* © 2021 Mike S * © 2021 Mike S
* © 2021-2022 Harald Barth * © 2021-2023 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* All rights reserved. * All rights reserved.
* *
@ -62,6 +62,9 @@ class DCCTimer {
static bool isPWMPin(byte pin); static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high); static void setPWM(byte pin, bool high);
static void clearPWM(); static void clearPWM();
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
static void DCCEXanalogWrite(uint8_t pin, int value);
// Update low ram level. Allow for extra bytes to be specified // Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other // by estimation or inspection, that may be used by other
// called subroutines. Must be called with interrupts disabled. // called subroutines. Must be called with interrupts disabled.
@ -102,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
@ -113,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

@ -150,6 +150,45 @@ int DCCTimer::freeMemory() {
void DCCTimer::reset() { void DCCTimer::reset() {
ESP.restart(); ESP.restart();
} }
#include "esp32-hal.h"
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
static int cnt_channel = LEDC_CHANNELS;
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] != 0) {
ledcSetup(pin_to_channel[pin], frequency, 8);
}
}
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
int ADCee::init(uint8_t pin) { int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG); pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_width(ADC_WIDTH_BIT_12);

View File

@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL; int * ADCee::analogvals = NULL;
int ADCee::init(uint8_t pin) { int ADCee::init(uint8_t pin) {
uint id = pin - A0; uint8_t id = pin - A0;
int value = 0; int value = 0;
if (id > NUM_ADC_INPUTS) if (id > NUM_ADC_INPUTS)
@ -210,7 +210,7 @@ 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 uint id = 0; // id and mask are the same thing but it is faster to static uint8_t 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 uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false; static bool waiting = false;

View File

@ -1,8 +1,8 @@
/* /*
* © 2023 Neil McKechnie * © 2023 Neil McKechnie
* © 2022 Paul M. Antoine * © 2022-23 Paul M. Antoine
* © 2021 Mike S * © 2021 Mike S
* © 2021 Harald Barth * © 2021, 2023 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2021 Chris Harlow * © 2021 Chris Harlow
* © 2021 David Cutting * © 2021 David Cutting
@ -30,24 +30,35 @@
#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.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // On the F446RE, Serial3 and Serial5 are easy to use:
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
// 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.
#else #else
#warning Serial1 not defined #error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
#endif #endif
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
@ -224,10 +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)
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
#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;
@ -237,10 +254,13 @@ int16_t ADCee::ADCmax() {
} }
int ADCee::init(uint8_t pin) { int ADCee::init(uint8_t pin) {
uint id = pin - A0;
int value = 0; int value = 0;
PinName stmpin = digitalPin[analogInputPin[id]]; PinName stmpin = analogInputToPinName(pin);
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 if (stmpin == NC) // do not continue if this is not an analog pin at all
return -1024; // some silly value as error
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!) uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
GPIO_TypeDef * gpioBase; GPIO_TypeDef * gpioBase;
@ -258,12 +278,20 @@ int ADCee::init(uint8_t pin) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC; gpioBase = GPIOC;
break; break;
default:
return -1023; // some silly value as error
} }
// Set pin mux mode to analog input // Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
// Set the sampling rate for that analog input // Set the sampling rate for that analog input
// This is F411x specific! Different on for example F334
// STM32F11xC/E Reference manual
// 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18)
// 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9)
if (adcchan > 18)
return -1022; // silly value as error
if (adcchan < 10) if (adcchan < 10)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else else
@ -275,14 +303,21 @@ int ADCee::init(uint8_t pin) {
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
value = ADC1->DR; // Read value from register value = ADC1->DR; // Read value from register
if (analogvals == NULL) uint8_t id = pin - PNUM_ANALOG_BASE;
{ if (id > 15) { // today we have not enough bits in the mask to support more
return -1021;
}
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t)); analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
} }
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;
} }
@ -291,7 +326,7 @@ int ADCee::init(uint8_t pin) {
* 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) {
uint8_t id = pin - A0; uint8_t id = pin - PNUM_ANALOG_BASE;
// Was this pin initialised yet? // Was this pin initialised yet?
if ((usedpins & (1<<id) ) == 0) if ((usedpins & (1<<id) ) == 0)
return -1023; return -1023;
@ -306,7 +341,7 @@ 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 uint id = 0; // id and mask are the same thing but it is faster to static uint8_t 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 uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false; static bool waiting = false;
@ -317,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;
} }
@ -335,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

@ -1,61 +0,0 @@
/*
* © 2022 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_ARCH_ESP32
#include <Arduino.h>
#include "ESP32-fixes.h"
#include "esp32-hal.h"
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
static int cnt_channel = LEDC_CHANNELS;
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] != 0) {
ledcSetup(pin_to_channel[pin], frequency, 8);
}
}
}
void DCCEXanalogWrite(uint8_t pin, int value) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
#endif

View File

@ -1,26 +0,0 @@
/*
* © 2022 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef ARDUINO_ARCH_ESP32
#pragma once
#include <Arduino.h>
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
void DCCEXanalogWrite(uint8_t pin, int value);
#endif

Binary file not shown.

View File

@ -2,6 +2,7 @@
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021-2023 Harald Barth * © 2021-2023 Harald Barth
* © 2020-2023 Chris Harlow * © 2020-2023 Chris Harlow
* © 2022 Colin Murdoch
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -265,16 +266,17 @@ 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==INT16_MAX) break;
if (rid==id) return 'R'; if (rid==id) return 'R';
if (rid==0) break;
} }
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==INT16_MAX) break;
if (rid==id) return 'A'; if (rid==id) return 'A';
if (rid==0) break;
} }
return 'X'; return 'X';
} }
// This filter intercepts <> commands to do the following: // This filter intercepts <> commands to do the following:
// - Implement RMFT specific commands/diagnostics // - Implement RMFT specific commands/diagnostics
// - Reject/modify JMRI commands that would interfere with RMFT processing // - Reject/modify JMRI commands that would interfere with RMFT processing
@ -608,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;
@ -881,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();
@ -918,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:
{ {
@ -1129,7 +1134,10 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time // Hunt for an ONTIME for this time
if (Diag::CMD) if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime); DIAG(F("Looking for clock event at : %d"), clocktime);
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime); if (change) {
handleEvent(F("CLOCK"),onClockLookup,clocktime);
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
}
} }
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
@ -1237,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

@ -1,6 +1,7 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow * © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth * © 2023 Harald Barth
* All rights reserved. * All rights reserved.
* *
@ -44,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,
@ -76,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

@ -1,5 +1,6 @@
/* /*
* © 2020-2022 Chris Harlow. All rights reserved. * © 2020-2022 Chris Harlow. All rights reserved.
* © 2022 Colin Murdoch
* © 2023 Harald Barth * © 2023 Harald Barth
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -91,6 +92,7 @@
#undef ONCLOSE #undef ONCLOSE
#undef ONTIME #undef ONTIME
#undef ONCLOCKTIME #undef ONCLOCKTIME
#undef ONCLOCKMINS
#undef ONGREEN #undef ONGREEN
#undef ONRED #undef ONRED
#undef ONTHROW #undef ONTHROW
@ -99,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
@ -134,11 +138,13 @@
#undef STOP #undef STOP
#undef THROW #undef THROW
#undef TURNOUT #undef TURNOUT
#undef TURNOUTL
#undef UNJOIN #undef UNJOIN
#undef UNLATCH #undef UNLATCH
#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
@ -208,6 +214,7 @@
#define ONAMBER(signal_id) #define ONAMBER(signal_id)
#define ONTIME(value) #define ONTIME(value)
#define ONCLOCKTIME(hours,mins) #define ONCLOCKTIME(hours,mins)
#define ONCLOCKMINS(mins)
#define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear) #define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id) #define ONCLOSE(turnout_id)
@ -219,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
@ -254,11 +263,13 @@
#define STOP #define STOP
#define THROW(id) #define THROW(id)
#define TURNOUT(id,addr,subaddr,description...) #define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
#define UNJOIN #define UNJOIN
#define UNLATCH(sensor_id) #define UNLATCH(sensor_id)
#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

@ -1,6 +1,7 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow * © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth * © 2023 Harald Barth
* All rights reserved. * All rights reserved.
* *
@ -80,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
@ -152,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;
@ -187,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"
; ;
@ -198,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"
@ -220,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
@ -316,6 +319,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTIME(value) OPCODE_ONTIME,V(value), #define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), #define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
@ -324,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),
@ -361,10 +367,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define STOP OPCODE_SPEED,V(0), #define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id), #define THROW(id) OPCODE_THROW,V(id),
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr), #define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
#define UNJOIN OPCODE_UNJOIN,0,0, #define UNJOIN OPCODE_UNJOIN,0,0,
#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-202303252126Z" #define GITHUB_SHA "devel-202308240808Z"

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, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, 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

@ -1,8 +1,8 @@
/* /*
* © 2022 Paul M Antoine * © 2022-2023 Paul M Antoine
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2022 Harald Barth * © 2020-2023 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* All rights reserved. * All rights reserved.
* *
@ -27,19 +27,16 @@
#include "DCCTimer.h" #include "DCCTimer.h"
#include "DIAG.h" #include "DIAG.h"
#if defined(ARDUINO_ARCH_ESP32) unsigned long MotorDriver::globalOverloadStart = 0;
#include "ESP32-fixes.h"
#endif
bool MotorDriver::commonFaultPin=false;
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, byte 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;
@ -95,32 +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;
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);
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
@ -135,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() {
@ -157,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) {
@ -197,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
@ -210,12 +230,17 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR; (void)fromISR;
if (currentPin==UNUSED_PIN) return 0; if (currentPin==UNUSED_PIN) return 0;
int current; int current;
current = ADCee::read(currentPin, fromISR)-senseOffset; current = ADCee::read(currentPin, fromISR);
// here one can diag raw value
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
current = current-senseOffset; // adjust with offset
if (current<0) current=0-current; if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) // current >= 0 here, we use negative current as fault pin flag
if ((faultPin != UNUSED_PIN) && powerPin) {
if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
return (current == 0 ? -1 : -current); return (current == 0 ? -1 : -current);
}
return current; return current;
} }
#ifdef ANALOG_READ_INTERRUPT #ifdef ANALOG_READ_INTERRUPT
@ -253,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,
@ -261,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;
@ -278,12 +331,14 @@ 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 ] ;
} }
} }
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup #endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
} }
#endif #endif
if (tSpeed <= 1) brake = 255; if (tSpeed <= 1) brake = 255;
@ -292,7 +347,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
if (invertBrake) if (invertBrake)
brake=255-brake; brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
DCCEXanalogWrite(brakePin,brake); DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else #else
analogWrite(brakePin,brake); analogWrite(brakePin,brake);
#endif #endif
@ -321,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;
@ -350,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, byte faultPin); 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,23 +194,53 @@ 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;
bool dualSignal; // true to use signalPin2 bool dualSignal; // true to use signalPin2
bool invertBrake; // brake pin passed as negative means pin is inverted bool invertBrake; // brake pin passed as negative means pin is inverted
bool invertPower; // power pin passed as negative means pin is inverted bool invertPower; // power pin passed as negative means pin is inverted
bool invertFault; // fault pin passed as negative means pin is inverted
// Raw to milliamp conversion factors avoiding float data types. // Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale // Milliamps=rawADCreading * sensefactorInternal / senseScale
@ -215,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;
@ -226,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,6 +87,12 @@
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"),\
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.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.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
@ -88,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

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

@ -0,0 +1,13 @@
@ECHO OFF
FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a
IF NOT %PS_POLICY=="Bypass" (
powershell Set-ExecutionPolicy -Scope CurrentUser Bypass
)
powershell %~dp0%installer.ps1
IF NOT %PS_POLICY=="Bypass" (
powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY%
)

540
installer.ps1 Normal file
View File

@ -0,0 +1,540 @@
<#
# © 2023 Peter Cole
#
# This file is part of EX-CommandStation
#
# 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/>.
#>
<############################################
For script errors set ExecutionPolicy:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass
############################################>
<############################################
Optional command line parameters:
$buildDirectory - specify an existing directory rather than generating a new unique one
$configDirectory - specify a directory containing existing files as per $configFiles
############################################>
Param(
[Parameter()]
[String]$buildDirectory,
[Parameter()]
[String]$configDirectory
)
<############################################
Define global parameters here such as known URLs etc.
############################################>
$installerVersion = "v0.0.8"
$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h")
$wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32")
$userDirectory = $env:USERPROFILE + "\"
$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags"
$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/"
if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") {
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip"
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_64bit.zip"
} else {
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip"
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip"
}
$arduinoCLIDirectory = $userDirectory + "arduino-cli"
$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe"
<############################################
List of supported devices with FQBN in case clones used that aren't detected
############################################>
$supportedDevices = @(
@{
name = "Arduino Mega or Mega 2560"
fqbn = "arduino:avr:mega"
},
@{
name = "Arduino Nano"
fqbn = "arduino:avr:nano"
},
@{
name = "Arduino Uno"
fqbn = "arduino:avr:uno"
},
@{
name = "ESP32 Dev Module"
fqbn = "esp32:esp32:esp32"
}
)
<############################################
List of supported displays
############################################>
$displayList = @(
@{
option = "LCD 16 columns x 2 rows"
configLine = "#define LCD_DRIVER 0x27,16,2"
},
@{
option = "LCD 16 columns x 4 rows"
configLine = "#define LCD_DRIVER 0x27,16,4"
},
@{
option = "OLED 128 x 32"
configLine = "#define OLED_DRIVER 128,32"
},
@{
option = "OLED 128 x 64"
configLine = "#define OLED_DRIVER 128,64"
}
)
<############################################
Basics of config.h
############################################>
$configLines = @(
"/*",
"This config.h file was generated by the DCC-EX PowerShell installer $installerVersion",
"*/",
"",
"// Define standard motor shield",
"#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD",
""
)
<############################################
Set default action for progress indicators, warnings, and errors
############################################>
$global:ProgressPreference = "SilentlyContinue"
$global:WarningPreference = "SilentlyContinue"
$global:ErrorActionPreference = "SilentlyContinue"
<############################################
If $buildDirectory not provided, generate a new time/date stamp based directory to use
############################################>
if (!$PSBoundParameters.ContainsKey('buildDirectory')) {
$buildDate = Get-Date -Format 'yyyyMMdd-HHmmss'
$buildDirectory = $userDirectory + "EX-CommandStation-Installer\" + $buildDate
}
$commandStationDirectory = $buildDirectory + "\CommandStation-EX"
<############################################
Write out intro message and prompt to continue
############################################>
@"
Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion)
Current installer options:
- EX-CommandStation will be built in $commandStationDirectory
- Arduino CLI will downloaded and extracted to $arduinoCLIDirectory
Before continuing, please ensure:
- Your computer is connected to the internet
- The device you wish to install EX-CommandStation on is connected to a USB port
This installer will obtain the Arduino CLI (if not already present), and then download and install your chosen version of EX-CommandStation
"@
<############################################
Prompt user to confirm all is ready to proceed
############################################>
$confirmation = Read-Host "Enter 'Y' or 'y' then press <Enter> to confirm you are ready to proceed, any other key to exit"
if ($confirmation -ne "Y" -and $confirmation -ne "y") {
Exit
}
<############################################
See if we have the Arduino CLI already, otherwise download and extract it
############################################>
if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) {
if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) {
try {
New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null
}
catch {
Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory"
Exit
}
}
Write-Output "`r`nDownloading and extracting Arduino CLI"
try {
Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip
}
catch {
Write-Output "Failed to download Arduino CLI"
Exit
}
try {
Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force
}
catch {
Write-Output "Failed to extract Arduino CLI"
}
} else {
Write-Output "`r`nArduino CLI already downloaded, ensuring it is up to date and you have a board connected"
}
<############################################
Make sure Arduino CLI core index updated and list of boards populated
############################################>
# Need to do an initial board list to download everything first
try {
& $arduinoCLI core update-index | Out-Null
}
catch {
Write-Output "Failed to update Arduino CLI core index"
Exit
}
# Need to do an initial board list to download everything first
try {
& $arduinoCLI board list | Out-Null
}
catch {
Write-Output "Failed to update Arduino CLI board list"
Exit
}
<############################################
Identify available board(s)
############################################>
try {
$boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json
}
catch {
Write-Output "Failed to obtain list of boards"
Exit
}
<############################################
Get user to select board
############################################>
if ($boardList.count -eq 0) {
Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it"
Exit
} else {
@"
Devices attached to COM ports:
------------------------------
"@
$boardSelect = 1
foreach ($board in $boardList) {
if ($board.matching_boards.name) {
$boardName = $board.matching_boards.name
} else {
$boardName = "Unknown device"
}
$port = $board.port.address
Write-Output "$boardSelect - $boardName on port $port"
$boardSelect++
}
Write-Output "$boardSelect - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "`r`nSelect the device to use from the list above"
} until (
(($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1)))
)
if ($userSelection -eq ($boardList.count + 1)) {
Write-Output "Exiting installer"
Exit
} else {
$selectedBoard = $userSelection - 1
}
}
<############################################
If the board is unknown, need to choose which one
############################################>
if ($null -eq $boardList[$selectedBoard].matching_boards.name) {
Write-Output "The device selected is unknown, these boards are supported:`r`n"
$deviceSelect = 1
foreach ($device in $supportedDevices) {
Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)"
$deviceSelect++
}
Write-Output "$deviceSelect - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "Select the board type from the list above"
} until (
(($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1)))
)
if ($userSelection -eq ($supportedDevices.count + 1)) {
Write-Output "Exiting installer"
Exit
} else {
$deviceName = $supportedDevices[$userSelection - 1].name
$deviceFQBN = $supportedDevices[$userSelection - 1].fqbn
$devicePort = $boardList[$selectedBoard].port.address
}
} else {
$deviceName = $boardList[$selectedBoard].matching_boards.name
$deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn
$devicePort = $boardList[$selectedBoard].port.address
}
<############################################
Get the list of tags
############################################>
try {
$gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags
}
catch {
Write-Output "Failed to obtain list of available EX-CommandStation versions"
Exit
}
<############################################
Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want
############################################>
$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)"
$tagList = @{}
foreach ($tag in $gitHubTags) {
$tagHash = @{}
$tagHash["Ref"] = $tag.ref
$version = $tag.ref.split("/")[2]
$null = $version -match $versionMatch
$tagHash["Major"] = [int]$Matches[1]
$tagHash["Minor"] = [int]$Matches[2]
$tagHash["Patch"] = [int]$Matches[3]
$tagHash["Type"] = $Matches[4]
$tagList.Add($version, $tagHash)
}
<############################################
Get latest two Prod and Devel for user to select
############################################>
$userList = @{}
$prodCount = 1
$devCount = 1
$select = 1
foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) {
if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) {
$userList[$select] = $tag
$select++
$prodCount++
} elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) {
$userList[$select] = $tag
$select++
$devCount++
}
}
<############################################
Display options for user to select and get the selection
############################################>
@"
Available EX-CommandStation versions:
-------------------------------------
"@
foreach ($selection in $userList.Keys | Sort-Object $selection) {
Write-Output "$selection - $($userList[$selection])"
}
Write-Output "5 - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)"
} until (
(($userSelection -ge 1) -and ($userSelection -le 5))
)
if ($userSelection -eq 5) {
Write-Output "Exiting installer"
Exit
} else {
$downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip"
}
<############################################
Create build directory if it doesn't exist, or fail
############################################>
if (!(Test-Path -PathType Container -Path $buildDirectory)) {
try {
New-Item -ItemType Directory -Path $buildDirectory | Out-Null
}
catch {
Write-Output "Could not create build directory $buildDirectory"
Exit
}
}
<############################################
Download the chosen version to the build directory
############################################>
$downladFile = $buildDirectory + "\CommandStation-EX.zip"
Write-Output "Downloading and extracting $($userList[$userSelection])"
try {
Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile
}
catch {
Write-Output "Error downloading EX-CommandStation zip file"
Exit
}
<############################################
If folder exists, bail out and tell user
############################################>
if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") {
Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX"
Exit
}
<############################################
Extract and rename to CommandStation-EX to allow building
############################################>
try {
Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force
}
catch {
Write-Output "Failed to extract EX-CommandStation zip file"
Exit
}
$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "")
try {
Rename-Item -Path $folderName -NewName $commandStationDirectory
}
catch {
Write-Output "Could not rename folder"
Exit
}
<############################################
If config directory provided, copy files here
############################################>
if ($PSBoundParameters.ContainsKey('configDirectory')) {
if (Test-Path -PathType Container -Path $configDirectory) {
foreach ($file in $configFiles) {
if (Test-Path -PathType Leaf -Path "$configDirectory\$file") {
Copy-Item -Path "$configDirectory\$file" -Destination "$commandStationDirectory\$file"
}
}
} else {
Write-Output "User provided configuration directory $configDirectory does not exist, skipping"
}
} else {
<############################################
If no config directory provided, prompt for display option
############################################>
Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n"
Write-Output "1 - I have no display, skip this step"
$displaySelect = 2
foreach ($display in $displayList) {
Write-Output "$displaySelect - $($displayList[$displaySelect - 2].option)"
$displaySelect++
}
Write-Output "$($displayList.Count + 2) - Exit"
do {
[int]$displayChoice = Read-Host "`r`nSelect a display option"
} until (
($displayChoice -ge 1 -and $displayChoice -le ($displayList.Count + 2))
)
if ($displayChoice -eq ($displayList.Count + 2)) {
Exit
} elseif ($displayChoice -ge 2) {
$configLines+= "// Display configuration"
$configLines+= "$($displayList[$displayChoice - 2].configLine)"
$configLines+= "#define SCROLLMODE 1 // Alternate between pages"
}
<############################################
If device supports WiFi, prompt to configure
############################################>
if ($wifiBoards.Contains($deviceFQBN)) {
Write-Output "`r`nYour chosen board supports WiFi`r`n"
Write-Output "1 - I don't want WiFi, skip this step
2 - Configure my device as an access point I will connect to directly
3 - Configure my device to connect to my home WiFi network
4 - Exit"
do {
[int]$wifiChoice = Read-Host "`r`nSelect a WiFi option"
} until (
($wifiChoice -ge 1 -and $wifiChoice -le 4)
)
if ($wifiChoice -eq 4) {
Exit
} elseif ($wifiChoice -ne 1) {
$configLines+= ""
$configLines+= "// WiFi configuration"
$configLines+= "#define ENABLE_WIFI true"
$configLines+= "#define IP_PORT 2560"
$configLines+= "#define WIFI_HOSTNAME ""dccex"""
$configLines+= "#define WIFI_CHANNEL 1"
if ($wifiChoice -eq 2) {
$configLines+= "#define WIFI_SSID ""Your network name"""
$configLines+= "#define WIFI_PASSWORD ""Your network passwd"""
}
if ($wifiChoice -eq 3) {
$wifiSSID = Read-Host "Please enter the SSID of your home network here"
$wifiPassword = Read-Host "Please enter your home network WiFi password here"
$configLines+= "#define WIFI_SSID ""$($wifiSSID)"""
$configLines+= "#define WIFI_PASSWORD ""$($wifiPassword)"""
}
}
}
<############################################
Write out config.h to a file here only if config directory not provided
############################################>
$configH = $commandStationDirectory + "\config.h"
try {
$configLines | Out-File -FilePath $configH -Encoding ascii
}
catch {
Write-Output "Error writing config file to $configH"
Exit
}
}
<############################################
Install core libraries for the platform
############################################>
$platformArray = $deviceFQBN.split(":")
$platform = $platformArray[0] + ":" + $platformArray[1]
try {
& $arduinoCLI core install $platform
}
catch {
Write-Output "Error install core libraries"
Exit
}
<############################################
Upload the sketch to the selected board
############################################>
#$arduinoCLI upload -b fqbn -p port $commandStationDirectory
Write-Output "Compiling and uploading to $deviceName on $devicePort"
try {
$output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json
}
catch {
Write-Output "Failed to compile"
Exit
}
if ($output.success -eq "True") {
Write-Output "`r`nCongratulations! DCC-EX EX-CommandStation $($userList[$userSelection]) has been installed on your $deviceName`r`n"
} else {
Write-Output "`r`nThere was an error installing $($userList[$userSelection]) on your $($deviceName), please take note of the errors provided:`r`n"
if ($null -ne $output.compiler_err) {
Write-Output "Compiler error: $($output.compiler_err)`r`n"
}
if ($null -ne $output.builder_result) {
Write-Output "Builder result: $($output.builder_result)`r`n"
}
}
Write-Output "`r`nPress any key to exit the installer"
[void][System.Console]::ReadKey($true)

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
@ -69,10 +88,10 @@ else
# need to do this config better # need to do this config better
cp -p config.example.h config.h cp -p config.example.h config.h
fi fi
need curl
if test -x "$ACLI" ; then if test -x "$ACLI" ; then
: all well : all well
else else
need curl
curl "$ACLIINSTALL" > acliinstall.sh curl "$ACLIINSTALL" > acliinstall.sh
chmod +x acliinstall.sh chmod +x acliinstall.sh
./acliinstall.sh ./acliinstall.sh

View File

@ -51,7 +51,7 @@ void halSetup() {
// Create a 20x4 LCD display device as display number 2 // Create a 20x4 LCD display device as display number 2
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")'). // (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
// HALDisplay<LiquidCrystal>(2, 0x27, 20, 4); // HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
//======================================================================= //=======================================================================

View File

@ -20,11 +20,11 @@ default_envs =
ESP32 ESP32
Nucleo-F411RE Nucleo-F411RE
Nucleo-F446RE Nucleo-F446RE
Teensy3.2 Teensy3_2
Teensy3.5 Teensy3_5
Teensy3.6 Teensy3_6
Teensy4.0 Teensy4_0
Teensy4.1 Teensy4_1
src_dir = . src_dir = .
include_dir = . include_dir = .
@ -53,7 +53,7 @@ monitor_speed = 115200
monitor_echo = yes monitor_echo = yes
build_flags = -std=c++17 build_flags = -std=c++17
[env:Arduino M0] [env:Arduino-M0]
platform = atmelsam platform = atmelsam
board = mzeroUSB board = mzeroUSB
framework = arduino framework = arduino
@ -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,11 +190,11 @@ 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
[env:Teensy3.2] [env:Teensy3_2]
platform = teensy platform = teensy
board = teensy31 board = teensy31
framework = arduino framework = arduino
@ -200,7 +202,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
[env:Teensy3.5] [env:Teensy3_5]
platform = teensy platform = teensy
board = teensy35 board = teensy35
framework = arduino framework = arduino
@ -208,7 +210,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
[env:Teensy3.6] [env:Teensy3_6]
platform = teensy platform = teensy
board = teensy36 board = teensy36
framework = arduino framework = arduino
@ -216,7 +218,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
[env:Teensy4.0] [env:Teensy4_0]
platform = teensy platform = teensy
board = teensy40 board = teensy40
framework = arduino framework = arduino
@ -224,7 +226,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
[env:Teensy4.1] [env:Teensy4_1]
platform = teensy platform = teensy
board = teensy41 board = teensy41
framework = arduino framework = arduino

View File

@ -3,8 +3,51 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define VERSION "5.1.1"
#define VERSION "4.2.41" // 5.1.1 - Check bad AT firmware version
// - Update IO_PCA9555.h reflecting IO_MCP23017.h changes to support PCA9548 mux
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
// 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.46 - Support boards with inverted fault pin
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events
// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe
// 4.2.43 - Fix STM32 set right port mode bits for analog
// 4.2.42 - Added EXRAIL TURNOUTL Macro definition
// 4.2.41 - Move HAl startup to ASAP in setup() // 4.2.41 - Move HAl startup to ASAP in setup()
// - Fix DNOU8 output pin setup to all LOW // - Fix DNOU8 output pin setup to all LOW
// 4.2.40 - Automatically detect conflicting default I2C devices and disable // 4.2.40 - Automatically detect conflicting default I2C devices and disable
@ -72,6 +115,10 @@
// 4.2.11 Exrail IFLOCO feature added // 4.2.11 Exrail IFLOCO feature added
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
// IO_EXIOExpander.h input speed optimisation // IO_EXIOExpander.h input speed optimisation
// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock
// <JC> Serial command added for EX-FastClock
// <jC> Broadcast added for EX-FastClock
// IO_EXFastClock.h added for I2C FastClock connection
// 4.2.9 duinoNodes support // 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) // 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements // Withrottle connect/disconnect improvements