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

Compare commits

..

937 Commits

Author SHA1 Message Date
Harald Barth
2db2b0ecc6 Committing a SHA 2023-08-07 20:27:22 +02:00
Harald Barth
fd58a749ef Committing a SHA 2023-08-07 20:25:14 +02:00
Harald Barth
3bddf4dfd1 Make 4.2.69 the 5.0.0 release 2023-08-07 19:45:45 +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
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
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
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
Asbelos
273f55b143 4.2.41 Hal setup and DNOU8 fix 2023-04-05 23:19:43 +01:00
peteGSX
86cb8f4666
Merge pull request #327 from DCC-EX:auto-disable-default-i2c
Added disable logic
2023-04-06 07:09:32 +10:00
peteGSX
9571088e1b Added disable logic 2023-04-06 07:03:59 +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
Neil McKechnie
1b4faa92cd Update IO_DFPlayer.h
Reinstate STOP command in place of PAUSE, as PAUSE was being reported differently to STOP in the status response.
2023-03-31 17:58:30 +01:00
Neil McKechnie
6fbaca7930 Update IO_DFPlayer.h
Ensure device goes off-line when not responding.
2023-03-31 16:50:18 +01:00
Neil McKechnie
6b535654f8 DFplayer driver now polls device to detect failures and errors.
Add cyclic (1-second) poll of DFplayer device to detect if it goes unresponsive.
2023-03-31 16:40:40 +01:00
peteGSX
2c943d250e
Merge pull request #326 from DCC-EX:287-to-do-clean-up-rotary-encoder-device-driver-compile-time-warning
Cleaned up warning
2023-03-30 07:01:40 +10:00
peteGSX
89664eff9d Cleaned up warning 2023-03-30 06:54:18 +10:00
f8b054cf6a
[ESP32] Use GPIO 35/A2 and 34/A3 for current sensing (#325)
* [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing while used in combination with the standard Motor Shield
* Update version.h changelog
2023-03-27 10:44:47 -04:00
Neil McKechnie
d2a8aebd0f Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-27 13:08:19 +01:00
Neil McKechnie
86c3020672 Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:08:14 +01:00
Neil McKechnie
60ea7f081a Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:03:19 +01:00
Neil McKechnie
f348857ddb Add FLAGS device for EX-RAIL state communications. Improve VPIN display in messages.
FLAGS HAL device added to IODevice.h, which allows use of SET/RESET/<Z>/<T> to set and reset a VPIN state, and to allow <S>/IF/IFNOT/AT/WAITFOR/etc. to monitor the VPIN state.
Also, correct handling of VPINs above 32767 in DIAG calls within IODevice.cpp and IODevice.h.
2023-03-27 12:39:11 +01:00
Harald Barth
bdd4bc9999 version 2023-03-25 22:26:57 +01:00
Harald Barth
8a425fe0ef do not broadcast a turnout state that has not changed 2 2023-03-25 19:28:37 +01:00
Harald Barth
1ec378281b do not broadcast a turnout state that has not changed 2023-03-25 12:14:58 +01:00
Asbelos
51a480dff3 doc typo only 2023-03-24 00:24:03 +00:00
Asbelos
f0ee8aeb85 z Commands 2023-03-23 19:52:49 +00:00
peteGSX
94d0aa92d9
Merge pull request #324 from DCC-EX:fix-analogue-input-bug
Fix-analogue-input-bug
2023-03-21 07:09:52 +10:00
peteGSX
82c447e8a4 Merge branch 'fix-analogue-input-bug' of https://github.com/DCC-EX/CommandStation-EX into fix-analogue-input-bug 2023-03-21 07:04:19 +10:00
peteGSX
a3d03ac68c Fix validated, update version 2023-03-21 07:04:08 +10:00
peteGSX
b183439a5b Using correct size for memcpy 2023-03-21 07:03:23 +10:00
Harald Barth
d51281f1f2 github tag 2023-03-20 21:24:42 +01:00
Harald Barth
168368864c Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-20 21:23:02 +01:00
Harald Barth
a75ca00e3c exchange pin number to track letter part 2 2023-03-20 21:22:48 +01:00
peteGSX
7ab5f556d9 Using correct size for memcpy 2023-03-21 05:30:33 +10:00
peteGSX
337bd969a1
Merge pull request #323 from DCC-EX:ex-io-analogue-input-bug
Fixed non-working analogue inputs
2023-03-20 19:11:59 +10:00
Harald Barth
21c99c8694 improve Wifi connect messages 2023-03-20 10:11:14 +01:00
peteGSX
4087cd6e29 Fixed non-working analogue inputs 2023-03-20 19:05:53 +10:00
Harald Barth
2fb485847f exchange pin number to track letter 2023-03-20 10:03:02 +01:00
Neil McKechnie
43c7baf8f5 Fix display scrolling on LCD and OLED
Eliminate spurious blanking of screen in mode 1, duplicated lines of text in mode 2, and non-display of more than the first screen-full of lines in mode 0.
2023-03-19 22:06:02 +00:00
Neil McKechnie
2e1a2d38e3 Update IO_EXIOExpander.h
Reinstate byte-wise processing of analogue input values.
2023-03-19 01:20:20 +00:00
Asbelos
fe18341994 Update myAutomation.example.h
better example with power on
2023-03-18 18:53:48 +00:00
Asbelos
329dc41452 Remove implicit AUTOSTART 2023-03-18 18:52:01 +00:00
Neil McKechnie
c4b4e11a67 Update IO_EXIOExpander.h
Avoid repeated error messages for a single fault.
2023-03-18 15:30:14 +00:00
Neil McKechnie
e55dc51bdb EX-IOExpander updates 2023-03-18 15:05:21 +00:00
Neil McKechnie
5dd2770442 Update IO_HCSR04.h
Modify mode of measurement so that the driver doesn't loop for long periods waiting for the incoming pulse to complete.   Original loop behaviour can be reinstated by adding LOOP option in create call (see comment header in file).
2023-03-17 21:15:26 +00:00
Neil McKechnie
d67b07fe46 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-15 18:10:30 +00:00
Neil McKechnie
3073061596 Update IO_DFPlayer.h
Bugfix, volume not working correctly.
2023-03-15 18:10:27 +00:00
Harald Barth
278347756a Bugfix Scroll LCD without empty lines and consistent 2023-03-15 16:41:02 +01:00
Neil McKechnie
3d35e78533 Update version.h 2023-03-15 09:39:30 +00:00
Neil McKechnie
75b5806eb7 Shorten I2C error message 2023-03-15 09:39:20 +00:00
Neil McKechnie
4a1210fa64 Remove HA mode from STM32
In some pin configurations for DC track mode, the use of analogWrite will conflict with other timer uses including HA mode.
 Consequently, the HA mode support has been temporarily removed pending a suitable solution for this.  Original use of Timer11 has been reinstated.
2023-03-15 09:31:54 +00:00
Neil McKechnie
72c76391a5 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-15 09:21:24 +00:00
Neil McKechnie
325d4bce73 Update IO_DFPlayer.h
Rework delay between command: instead of sending null characters, which doesn't work on some chip sets, send commands from the _loop() function with a 100ms delay between consecutive commands.
2023-03-15 09:20:42 +00:00
Harald Barth
27ba551986 Bugfix LCD showed random characters in SCROLLMODE 2 2023-03-14 20:50:24 +01:00
peteGSX
ce12f3b6c5
Merge pull request #320 from DCC-EX:ex-io-driver-updates
Ex-io-driver-updates
2023-03-14 19:10:23 +10:00
peteGSX
48cd567bda Update version after testing 2023-03-14 19:04:08 +10:00
peteGSX
25676aab6b Update comments 2023-03-14 07:32:08 +10:00
peteGSX
42bddb587e Update .gitignore 2023-03-14 07:25:30 +10:00
peteGSX
a51cefbdeb
Delete settings.json 2023-03-14 07:23:55 +10:00
peteGSX
5fc925bfc6
Delete extensions.json 2023-03-14 07:23:42 +10:00
peteGSX
ca2e5e6ce3 Undo vscode change 2023-03-14 07:21:55 +10:00
peteGSX
9728e15e0a Merge branch 'ex-io-driver-updates' of https://github.com/DCC-EX/CommandStation-EX into ex-io-driver-updates 2023-03-14 07:20:46 +10:00
peteGSX
c83741d2b4 Add read refresh delays 2023-03-14 07:20:27 +10:00
peteGSX
df3eb11eb9 Add read refresh delays 2023-03-14 07:19:20 +10:00
Asbelos
d89dd0d1fa command ref work in progress 2023-03-13 00:53:42 +00:00
peteGSX
95d0120204 Implement status checks 2023-03-13 08:38:28 +10:00
peteGSX
0cc07ed1df Starting on driver feedback 2023-03-13 05:29:22 +10:00
Asbelos
5e60fb4e27 SAMD21 odd byte boundary 2023-03-11 22:46:11 +00:00
Harald Barth
881463ada9 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-10 17:52:16 +01:00
Harald Barth
45cf610028 Bugfix Ethernet shield: Static IP now possible 2023-03-10 17:49:51 +01:00
Neil McKechnie
471b8ac8e1 Update I2CManager.cpp
Rearrange I2C short-circuit check to before I2C is initialised.
2023-03-09 16:28:07 +00:00
Neil McKechnie
3ae1859ec7 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-09 16:01:10 +00:00
Neil McKechnie
679e5885c4 Update IO_HALDisplay.h
Enable I2C address overlap check on HAL display.
2023-03-09 15:59:52 +00:00
Neil McKechnie
59c6c1e5af Update examples and comments. 2023-03-09 15:59:25 +00:00
Harald Barth
f94a5f971e Bugfix signalpin2 was not set up in shadow port 2023-03-07 16:20:00 +01:00
Asbelos
1d29436008 Compiler pedantics 2023-03-06 13:47:59 +00:00
Asbelos
bec8aea5a5 TM Broadcasts
TM changes will trigger TM state broadcasts
2023-03-06 11:57:14 +00:00
peteGSX
4f23dbc984
Merge pull request #315 from DCC-EX:47-to-do-update-device-driver-to-use-non-blocking-i2c
Non-blocking implemented
2023-03-04 19:02:11 +10:00
peteGSX
46070e2999 Non-blocking implemented 2023-03-04 18:55:13 +10:00
Harald Barth
31ecba08d8 faultPin can be inverted (from its inverted sense 2023-03-03 20:51:32 +01:00
Neil McKechnie
22c0bff697 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-02 23:01:47 +00:00
Neil McKechnie
19bbb186e7 Create IO_TouchKeypad.h
Driver for 16-pad capacitative key pad device (TTP229-B based).
2023-03-02 23:01:44 +00:00
Asbelos
a17a55d904 Implement <JG> <JI> 2023-03-02 13:03:05 +00:00
Asbelos
b969563d35 Squashed commit of the following:
commit 4d8efcdd05
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Mar 1 16:32:05 2023 +0000

    Reinstate obsolete <s>

commit 003313998b
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Mar 1 16:07:11 2023 +0000

    Change <I><G> to <JI><JG>

commit c72bf51959
Author: Asbelos <asbelos@btinternet.com>
Date:   Sat Feb 25 17:38:39 2023 +0000

    G and I commands
2023-03-02 12:56:30 +00:00
Asbelos
f0c1ea958c 4.2.19 sensorOffset bugfix 2023-03-02 10:45:39 +00:00
Neil McKechnie
ca4592dc3e Reinstate original #ifdef DIAG_IO tests in servo modules 2023-03-01 23:09:27 +00:00
Neil McKechnie
0663cc6138 Update IO_EXIOExpander.h
_I2CAddress or _i2cAddress - the checkOverlap function uses the former, and the driver uses the latter.  I incorrectly used the wrong one!
2023-02-28 23:49:51 +00:00
Neil McKechnie
5fb10fa6d0 Delete IO_ExternalEEPROM.h 2023-02-28 20:09:37 +00:00
Harald Barth
1d47dce473 update tag 2023-02-28 15:22:57 +01:00
Harald Barth
8a126906f3 Only need do anything if cab existed 2023-02-28 15:20:29 +01:00
Harald Barth
b01e4388ce Small typo in tone scale 2023-02-28 15:17:04 +01:00
Colin Murdoch
4adcdc1b0b Update config.example.h
Removed redundant define for FastClock no longer required.
2023-02-28 12:12:08 +00:00
Neil McKechnie
95b640686a Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-02-25 11:43:39 +00:00
Neil McKechnie
f56f5d9ebe Update I2CManager_Wire.h
Add alternative multiplexer support for I2C Wire subsystem.
2023-02-25 11:43:36 +00:00
Neil McKechnie
8c8a913678 Update IO_HALDisplay.h
Check for memory allocation errors.
2023-02-25 11:43:05 +00:00
Neil McKechnie
711ad6f030 Update SSD1306Ascii.cpp
Add remaining extended graphics characters (still no international characters though.
2023-02-25 11:42:43 +00:00
Neil McKechnie
c2983efebb Update to comments 2023-02-25 11:42:12 +00:00
Asbelos
02ee8ad080 Removed duplicate DIAG 2023-02-24 16:56:21 +00:00
Neil McKechnie
780c6ea162 Revert "Update DisplayInterface.cpp"
This reverts commit a23f5839b7.
2023-02-24 09:39:00 +00:00
Neil McKechnie
8582e51483 Change example EXRAIL DFplayer calls from SERVO to ANOUT. 2023-02-23 20:57:25 +00:00
Neil McKechnie
cdf3927aad Update I2CManager.h
Make getMuxCount() publicly visible.
2023-02-23 20:52:40 +00:00
Neil McKechnie
ddfc67d2e3 Remove files that were incorrectly included in the merge. 2023-02-23 20:52:13 +00:00
Neil McKechnie
a23f5839b7 Update DisplayInterface.cpp
Remove dummy display driver object - it's unnecessary now as a null pointer is benign in this context.
2023-02-23 20:24:42 +00:00
Neil McKechnie
eddd6382d9 Update IO_EXFastclock.h
Update to support extended I2C addresses.
2023-02-23 20:23:08 +00:00
Neil McKechnie
45e3e3d185 Update IO_RotaryEncoder.h
Update to support extended I2C addresses.
2023-02-23 20:17:15 +00:00
Neil McKechnie
5e9ae4a0ac Remove redundant commented out code. 2023-02-23 20:16:29 +00:00
Neil McKechnie
39d953ed29
Merge pull request #312 from DCC-EX/devel-nmck
Merge Devel-nmck:
I2C Multiplexer support through Extended Addresses added for Wire, 4209 and AVR I2C drivers.
I2C retries when an operation fails.
I2C timeout handling and recovery completed.
I2C SAMD Driver Read code completed.
PCF8575 I2C GPIO driver added.
EX-RAIL ANOUT function for triggering analogue HAL drivers (e.g. analogue outputs, DFPlayer, PWM).
EX-RAIL SCREEN function for writing to screens other than the primary one.
Installable HALDisplay Driver, with support for multiple displays.
Layered HAL Drivers PCA9685pwm and Servo added for native PWM on PCA9685 module and for animations of servo movement via PCA9685pwm. This is intended to support EXIOExpander and also replace the existing PCA9685 driver.
Add <D HAL RESET> to reinitialise failed drivers (it calls the _begin method of all HAL drivers to reinitialise them).
Add UserAddin facility to allow a user-written C++ function to be declared in myHal.cpp, to be called at a user-specified frequency.
Add ability to configure clock speed of PCA9685 drivers (to allow flicker-free LED control).
Improve stability of VL53L0X driver when XSHUT pin connected.
Enable DCC high accuracy mode for STM32 on standard motor shield (pins D12/D13).
Incorporate improvements to ADC scanning performance (courtesy of HABA).
2023-02-23 11:20:17 +00:00
Neil McKechnie
b7483d99e9 Update version.h 2023-02-23 11:13:05 +00:00
Neil McKechnie
ce3885b125 Update DisplayInterface.h
Remove compiler warning
2023-02-23 10:36:42 +00:00
Neil McKechnie
c52b4a60a5 Update IO_HALDisplay.h
Update comments.
2023-02-22 21:34:52 +00:00
Neil McKechnie
ef85d5eaba Update version.h
4.2.18
2023-02-22 21:28:16 +00:00
Neil McKechnie
f281938606 Merge branch 'devel' into devel-nmck 2023-02-22 21:24:08 +00:00
Neil McKechnie
a405d36523 Rename class OLEDDisplay to HALDisplay. 2023-02-22 21:11:37 +00:00
Neil McKechnie
8e8ae90030 Update I2CManager.cpp
Add Real-time clock as a device address category (address 0x68).
2023-02-22 21:09:40 +00:00
Neil McKechnie
c3675367ed Update IO_VL53L0X.h
Configure XSHUT control so that the pull-up on the module raises the pin to +2.8V rather than trying to drive it to +5V.
2023-02-22 21:08:09 +00:00
Neil McKechnie
4deb323802 Update LiquidCrystal_I2C.cpp
Ensure that pipelined I/O requests complete before the next one is set up.
2023-02-22 21:06:39 +00:00
Neil McKechnie
4eb277f19e Refactor Display handler to (hopefully) improve clarity. 2023-02-22 21:06:03 +00:00
Neil McKechnie
c2e8557c4c defines.h - cosmetic change to indenting.
Lay out long #if statement with indents to make it a bit easier to read.
2023-02-21 11:12:31 +00:00
Neil McKechnie
8e90bb6996 Enable extended addresses and extended OLED characters on all but Nano, Uno and Mega4809..
Define I2C_EXTENDED_ADDRESS on most platforms, and define NO_EXTENDED_CHARACTERS on Nano, Uno and Mega4809.
2023-02-21 11:07:19 +00:00
Neil McKechnie
034bb6b675 Update DCCTimerSTM32.cpp
Enable hardware pulse generation on STM32 for pins 12 and 13 (standard motor shield pins), using timers TIM2 and TIM3.  Any other pins will currently be controlled directly by the interrupt routine (in effect, by digitalWrite calls).
2023-02-21 11:03:14 +00:00
Neil McKechnie
43b1e8db21 Update LCD driver to make it more reliable
Delay times at startup extended to make start-up configuration more reliable.
2023-02-21 11:00:27 +00:00
Neil McKechnie
a36dccfad0 Add UserAddin class to facilitate user-written cyclic functions.
UserAddin allows a function to be 'plugged in' to the IODevice (HAL) framework and executed cyclically, using just one line of code in the myHal.cpp.  This will facilitate functions for displaying CS state on OLEDs and LCDs, among other things.
2023-02-21 10:55:37 +00:00
Neil McKechnie
33229b4847 Update SSD1306Ascii.cpp
Bugfix: Move calculation of m_charsPerColumn and m_charsPerRow into constructors, to avoid incorrect random values being reported.
2023-02-19 19:14:15 +00:00
Neil McKechnie
173676287c Allow extended I2C addresses to be specified in non-extended configuration
If an extended I2C address is specified (including mux and/or subbus) then these parameters are ignored, but a warning output to the diagnostic console.
2023-02-18 09:32:38 +00:00
Neil McKechnie
9797a0fd2d Add high accuracy timing for STM32, on pins D3 and D6. 2023-02-17 16:04:21 +00:00
Neil McKechnie
10cd580061 Update I2CManager_NonBlocking.h
Missing initialisation of read buffer pointer!
2023-02-16 22:27:23 +00:00
Neil McKechnie
d6c8595f8a Update Display.h
Get rid of compiler warning.
2023-02-16 16:50:42 +00:00
Neil McKechnie
6b863ea483 Update I2CManager_NonBlocking.h
Cosmetic changes
2023-02-16 16:41:33 +00:00
Neil McKechnie
8ed3bbd845 Support for multiple displays
Refactor display handling so DisplayInterface class passes the relevant commands to the relevant display objects.
2023-02-16 16:41:13 +00:00
Neil McKechnie
d0445f157c Update IODevice.h
Finish changes relating to PWM frequency (doh!)
2023-02-15 22:34:37 +00:00
Neil McKechnie
0118aa037d Merge branch 'devel-nmck' of https://github.com/DCC-EX/CommandStation-EX into devel-nmck 2023-02-15 22:29:28 +00:00
Neil McKechnie
21c82b37b0 Allow frequency of PWM to be set for PCA9685 drivers.
It's a parameter on the create() call, e.g.
PCA9685::create(vpin, npins, address, frequency);
2023-02-15 22:29:21 +00:00
pmantoine
67bd886a98 USB Serial fixes for EX-RAIL & debug 2023-02-15 08:51:21 +08:00
Neil McKechnie
3292c93192 Update I2CManager.cpp
Report address 0x3d as (probably) OLED Display.
If an I2C probe for a device times out, assume that there's a bus problem on the MUX sub-bus, and skip the rest of the devices on that bus.  This reduces the impact of the long timeout on some Wire implementations.
2023-02-14 21:56:11 +00:00
Harald Barth
2ada89f918 LCN bugfix 2023-02-12 20:35:57 +01:00
Neil McKechnie
f1f1be8ad9 Oops- forgot to push this one. Name changes again. 2023-02-12 09:18:02 +00:00
peteGSX
0b0aba7aef
Merge pull request #305 from DCC-EX:41-feature-request-enable-servo-animations-in-ex-ioexpander
Fix myHal example for EX-IOExpander
2023-02-12 19:14:59 +10:00
peteGSX
9c95eb6905 Servo animation moved to EX-IO 2023-02-12 19:06:46 +10:00
Neil McKechnie
ec73ac69d9 A few more name changes to more generic names 2023-02-12 09:01:16 +00:00
peteGSX
47cda83210 Disabled servo animations 2023-02-12 10:36:26 +10:00
Neil McKechnie
35f3cca9b3 Rename LCDDisplay class to Display; renameEXRAIL LCD2 macro to SCREEN 2023-02-11 23:37:09 +00:00
Neil McKechnie
65f7a4917f Separate out lcd (write to default display) and lcd2 (any display). 2023-02-11 17:04:18 +00:00
Neil McKechnie
8be9d9e0b0 Support for multiple displays (OLED etc).
New EXRAIL command LCD2(display,row,"text").  Display 0 is the usual one, other displays can be configured through HAL.
2023-02-11 15:47:50 +00:00
Neil McKechnie
a9971968c0 Update I2CManager_NonBlocking.h
Reduce platform-specific part of ATOMIC_BLOCK definition to one inline function _getInterruptState()
2023-02-10 22:58:58 +00:00
Neil McKechnie
e498915b28 Update I2CManager_AVR.h
Avoid loop in I2C_sendStart function.
2023-02-10 19:57:42 +00:00
Neil McKechnie
c315895cd9 Update I2CManager_NonBlocking.h
Rework ATOMIC_BLOCK to further simplify and clarify.
2023-02-10 19:52:24 +00:00
Neil McKechnie
1cfe5a1e46 Update I2CManager_STM32.h
Fix some merge errors.
2023-02-10 18:22:35 +00:00
Neil McKechnie
ad4cedfccf Update I2CManager_NonBlocking.h
Rationalise ATOMIC_BLOCK macro definition and remove reliance on atomic.h.
2023-02-10 18:21:50 +00:00
Neil McKechnie
98697427a3 Update I2CManager_SAMD.h
Fix compile errors following other changes
2023-02-10 18:21:06 +00:00
Neil McKechnie
f5b5809ba5 Merge branch 'devel-nmck' of https://github.com/DCC-EX/CommandStation-EX into devel-nmck 2023-02-10 15:57:57 +00:00
Neil McKechnie
0b307a67e4 I2CManager: Update native drivers for MUX support from the common code. 2023-02-10 15:47:44 +00:00
Neil McKechnie
553a94bf67 I2CManager: Refactor common driver code.
Put mux handling into I2CManager_NonBlocking.h to keep the native (controller-specific) drivers more simple.
Remove almost all but one of the static definitions, in preparation for supporting multiple I2C buses.
2023-02-10 15:46:50 +00:00
Neil McKechnie
18b148ed1f IO_EXFastClock - fix compile error due to closing brace outside of #if block. 2023-02-10 15:35:17 +00:00
Neil McKechnie
1ffb3a9836 Update IO_OLEDDisplay.h
Round up number of characters per line, so that the last few pixels on the line are erased when writing blanks.
2023-02-10 15:34:13 +00:00
Neil McKechnie
f358880f30 IO_VL53L0X: Some bug fixes.
Modify state model, and improve recovery after <D HAL RESET>.
2023-02-10 15:32:41 +00:00
Neil McKechnie
5f9705d1b7 Improve IODevice::reset function
Ensure that the _loop() function is able to run after a device is reset.
2023-02-10 15:30:35 +00:00
Neil McKechnie
7e2487ffbb Avoid compiler error when no HAL installed. 2023-02-10 15:29:09 +00:00
pmantoine
fd07402aec STM32 better I2C still work in progress 2023-02-09 15:12:16 +08:00
peteGSX
d8d785877e Fix myHal example for EX-IOExpander 2023-02-09 13:38:06 +10:00
peteGSX
3b82a94d83
Merge pull request #304 from DCC-EX:ex-io-28-feature-request-enable-pwm-support
Ex-io-28-feature-request-enable-pwm-support
2023-02-09 13:20:46 +10:00
peteGSX
acadf241e6 Update version 2023-02-09 13:15:04 +10:00
peteGSX
8cc5f7ddf4 Merge branch 'ex-io-28-feature-request-enable-pwm-support' of https://github.com/DCC-EX/CommandStation-EX into ex-io-28-feature-request-enable-pwm-support 2023-02-09 13:08:34 +10:00
peteGSX
f1c17c3606 Add more state checking 2023-02-09 13:03:00 +10:00
peteGSX
d36ac7dcfd Revert IODevice.h change 2023-02-09 12:58:48 +10:00
peteGSX
6b67760db1 Fix dynamic RAM allocation 2023-02-09 12:58:48 +10:00
peteGSX
6874ddca9b Servo functional 2023-02-09 12:58:48 +10:00
peteGSX
06827a42b7 Remove excess drivers 2023-02-09 12:58:48 +10:00
peteGSX
f59fe6e83b Some success 2023-02-09 12:58:48 +10:00
peteGSX
c768bdc361 Start adding servo to EX-IO 2023-02-09 12:58:48 +10:00
peteGSX
ad97260055 Add extra error checking 2023-02-09 12:58:48 +10:00
peteGSX
938b4cfbd6 Update version 2023-02-09 12:58:48 +10:00
peteGSX
2a3d48dc00 Fix digital read bug 2023-02-09 12:58:48 +10:00
peteGSX
5efb0c5013 Basic PWM working 2023-02-09 12:58:48 +10:00
peteGSX
e53ed7b46d Brief start on PWM 2023-02-09 12:58:48 +10:00
peteGSX
4d31cd64a5 Add new drivers 2023-02-09 12:58:48 +10:00
peteGSX
6031a0fb7f Fix mess after rebase and conflicts 2023-02-09 12:58:48 +10:00
peteGSX
d375723a13 Cleaned up PWM start 2023-02-09 12:57:30 +10:00
peteGSX
fa38583772 Brief PWM start 2023-02-09 12:57:30 +10:00
peteGSX
984ef6fead Refactored, analogue tested 2023-02-09 12:57:29 +10:00
peteGSX
cf2817d7c4 Brief PWM start 2023-02-09 12:54:05 +10:00
peteGSX
0c2f8428df Refactored, analogue tested 2023-02-09 12:54:05 +10:00
peteGSX
53215b496e Refactored, analogue tested 2023-02-09 12:54:05 +10:00
peteGSX
d41b5e0938 Brief PWM start 2023-02-09 12:54:05 +10:00
peteGSX
d8cbdb24e1 Refactored, analogue tested 2023-02-09 12:54:05 +10:00
peteGSX
93ac1b6d61 Revert IODevice.h change 2023-02-09 12:45:34 +10:00
Neil McKechnie
8083bd1b3b Merge branch 'devel-nmck' of https://github.com/DCC-EX/CommandStation-EX into devel-nmck 2023-02-09 00:18:17 +00:00
Neil McKechnie
9e0e110b5d Update defines.h - inappropriate define NO_INTERRUPTS replaced with I2C_USE_WIRE. 2023-02-09 00:17:31 +00:00
Neil McKechnie
7de46a0c17 Add <D HAL RESET> command to attempt to reset failed devices. 2023-02-09 00:16:39 +00:00
Neil McKechnie
9dd9990979 Improve formatting of I2CAddress data type in diagnostics. 2023-02-09 00:16:06 +00:00
Neil McKechnie
dd0ee8b50a Additional support for Extended I2C Addresses 2023-02-09 00:13:23 +00:00
peteGSX
ad4a0a9592
Merge pull request #303 from DCC-EX:exio-test-servo-included
Exio-test-servo-included
2023-02-09 09:43:03 +10:00
peteGSX
deb49f2943 Fix dynamic RAM allocation 2023-02-09 09:31:09 +10:00
peteGSX
5cb216dd79 Servo functional 2023-02-09 08:41:50 +10:00
peteGSX
afc94a75bb Remove excess drivers 2023-02-09 07:39:58 +10:00
peteGSX
2848ba616b Some success 2023-02-09 07:38:00 +10:00
peteGSX
3d480ee9ef Start adding servo to EX-IO 2023-02-09 05:32:27 +10:00
pmantoine
d7f92d7b88 STM32 native I2C driver updates 2023-02-08 13:06:11 +08:00
pmantoine
3fbcd6f300 STM32 native I2C driver initial edits 2023-02-08 10:04:18 +08:00
Neil McKechnie
a0f0b860eb Update version.h 2023-02-07 23:21:23 +00:00
Neil McKechnie
efb2666060 DCCTimer_AVR - incorporate Haba's optimisations to ADC scanning 2023-02-07 23:18:59 +00:00
Neil McKechnie
73a7d3e0ca Update I2CManager_AVR.h
Bug fix in native driver MUX code.
2023-02-07 23:16:37 +00:00
Neil McKechnie
47cb43d1e9 Update IO_OLEDDisplay.h
Make display updates non-blocking (after initialisation completes).
2023-02-07 23:16:00 +00:00
Neil McKechnie
e2e9b7bae7 Update version.h 2023-02-07 22:04:09 +00:00
Harald Barth
57292c2250 installer.sh script bug fix and enhancements 2023-02-07 20:44:03 +01:00
Neil McKechnie
31ce2e3fef EXTurntable - change I2C address type to I2CAddress. 2023-02-07 18:36:48 +00:00
Neil McKechnie
d8881deb6a Merge branch 'devel' into devel-nmck 2023-02-07 18:05:18 +00:00
Neil McKechnie
5439dd3158 Suppress compiler warnings. 2023-02-07 17:59:40 +00:00
Neil McKechnie
aad0d28d1f Servo driver - split PCA9685 into a filter + PWM driver.
Two new drivers: PCA9685pwm drives the PCA9685 I2C device directly, and Servo driver which acts as a 'shim' over the top to control animations.  This is aimed at supporting devices like the EXIOExpander by allowing the Servo driver to talk to the EXIOExpander driver instead, to animate servos on another controller.
2023-02-07 17:24:05 +00:00
Neil McKechnie
19070d33ba Add Arduino M0, and display time on serial monitor. 2023-02-07 17:13:36 +00:00
Neil McKechnie
7b79680de2 First stab at HAL-installable OLED Display Driver. 2023-02-07 17:09:17 +00:00
Neil McKechnie
0c88c74706 Revert "Refactor SSD1306 initialisation to support different initialisation sequences."
This reverts commit 278a9c52a6.
2023-02-07 17:01:45 +00:00
Neil McKechnie
cb287f23a4 I2CManager: Add support for I2C Multiplexers to Wire and AVR.
AVR Native (non-blocking) driver now supports up to 8 I2C Multiplexers, as does any controller that uses Wire for I2C.
Other native drivers will be updated in due course.
2023-02-07 16:38:30 +00:00
Neil McKechnie
261ccf2f3b HAL - change variable type for PCA9685 data. 2023-02-07 15:09:21 +00:00
Neil McKechnie
278a9c52a6 Refactor SSD1306 initialisation to support different initialisation sequences.
To support a HAL Display driver, the SSD1306 driver can be created (new) and then the I2C address assigned explicitly in the begin() call.  The original approach of looking for the I2C device address has also been retained in a different constructor.
2023-02-07 15:07:29 +00:00
Neil McKechnie
9435869ee3 Prepare HAL device drivers to support Extended I2C Addresses
Cast I2CAddress variables in DIAG calls to (int).
2023-02-07 15:04:03 +00:00
Neil McKechnie
d5a394d4e6 Prepare HAL device drivers to support Extended I2C Addresses
Update I2C addresses of HAL devices to type I2CAddress (to support extended address functions).
Cast I2CAddress variables in DIAG calls to (int).
Remove uses of max() function (not available on some platforms.
2023-02-07 14:55:14 +00:00
peteGSX
c870940dde Add extra error checking 2023-02-07 07:32:16 +10:00
peteGSX
754639c7e3 Update version 2023-02-06 19:39:25 +10:00
peteGSX
a478ad7112
Merge pull request #302 from DCC-EX:separate-server-from-pca9685
Separate-server-from-pca9685
2023-02-06 19:34:00 +10:00
Neil McKechnie
13bd6ef9eb HAL: Add support for Extended Addresses and I2C Multiplexer
Change I2C addresses from uint8_t to I2CAddress, in preparation for MUX support.  Currently, by default, I2CAddress is typedef'd to uint8_t.
MUX support implemented for AVR and Wire versions.
2023-02-04 23:57:22 +00:00
Neil McKechnie
45657881eb Merge branch 'devel-nmck' of https://github.com/DCC-EX/CommandStation-EX into devel-nmck 2023-02-04 23:01:51 +00:00
Neil McKechnie
a590245e93 I2CManager_SAMD.h: Remove unneeded declaration. 2023-02-04 21:01:02 +00:00
Neil McKechnie
6f5680fce0 DFPlayer: Avoid jumps in volume when switching song and reducing volume at the same time. 2023-02-04 20:59:19 +00:00
Neil McKechnie
2f46a8e083 Add EX-RAIL 'ANOUT' function for general analogue outputs. 2023-02-04 20:56:12 +00:00
Neil McKechnie
bd62939713 Fix handling of hex numbers to avoid extending MSB (sign bit)
Also, added format %X (unsigned long) to complement %x (unsigned int).
2023-02-04 20:55:14 +00:00
peteGSX
abe79b854e Fix digital read bug 2023-02-04 09:19:32 +10:00
Neil McKechnie
27ddc7b30b IO_ExampleSerial - refactor and update comments
To more directly reflect the bulk of HAL drivers, the .h/.cpp split has been removed and the class is fully defined in the .h file.
2023-02-03 12:56:19 +00:00
Neil McKechnie
81559998ec Update IODevice base class to better support filter drivers
Filter drivers provide extra functionality above a hardware driver.  For example, a hardware driver for a PWM module may just set the PWM ratio, but a separate filter driver could animate motors or servos over time, calling the PWM driver to output the pulses.  This would allow the animations to be easily implemented on a different type of PWM module.
2023-02-03 12:55:25 +00:00
Neil McKechnie
847ced2f49 Update IO_VL53L0X.h
Improve comments;  drive XSHUT pin through pullup resistor, not directly.
2023-02-03 12:46:38 +00:00
Neil McKechnie
49713badb2 Update I2CManager_NonBlocking.h
Add code to try and recover from stuck bus following a timeout.
2023-02-02 12:21:35 +00:00
pmantoine
be88344407 PCF8575 16-bit port device support added 2023-02-02 07:12:47 +08:00
peteGSX
ec83a345dc Basic PWM working 2023-02-01 19:46:08 +10:00
peteGSX
4e32c707b9 Brief start on PWM 2023-02-01 14:53:46 +10:00
peteGSX
73e1dfc192 Remove duplicate comment 2023-02-01 08:13:23 +10:00
peteGSX
1ae74d9487 Merge branch 'separate-server-from-pca9685' of https://github.com/DCC-EX/CommandStation-EX into separate-server-from-pca9685 2023-02-01 07:51:45 +10:00
peteGSX
a7366b42c1 Add new drivers 2023-02-01 07:51:38 +10:00
peteGSX
84431d1841 Fix mess after rebase and conflicts 2023-02-01 07:49:31 +10:00
peteGSX
e12c5292fa Merge branch 'ex-io-28-feature-request-enable-pwm-support' of https://github.com/DCC-EX/CommandStation-EX into ex-io-28-feature-request-enable-pwm-support 2023-02-01 07:29:17 +10:00
peteGSX
e76197faa9 Brief PWM start 2023-02-01 07:26:03 +10:00
peteGSX
bdc8aec9a6 Refactored, analogue tested 2023-02-01 07:19:52 +10:00
peteGSX
052256e2ed Refactored, analogue tested 2023-02-01 07:18:04 +10:00
Neil McKechnie
ba9b363058 I2CManager_NonBlocking - Defer I2C speed changes for all drivers
Following on from the change to I2CManager_SAMD.h, the capability of deferring a request to change the speed of the I2C has been removed from the SAMD driver and put into the common NonBlocking code, so that all native drivers benefit from it.
2023-01-31 18:39:15 +00:00
Neil McKechnie
bdffd36820 IODevice.h - change visibility of findDevice to protected.
To support nested drivers efficiently (i.e. to allow the higher driver to call another driver directly, without searching for a VPIN every time), the visibility of the IODevice::findDevice() function has been changed from private to protected.
2023-01-31 15:24:38 +00:00
Neil McKechnie
4d350040ba I2CManager_SAMD.h - avoid bus hangs on speed changes
The speed change is deferred until the next transmission is about to start to avoid issues with the I2C module being disabled and enabled during a transmission.
2023-01-31 12:28:51 +00:00
peteGSX
1073e142e6 Add new drivers 2023-01-31 19:32:12 +10:00
peteGSX
a18c06d021 Cleaned up PWM start 2023-01-31 19:29:39 +10:00
Neil McKechnie
291a331f3e Fix read operations on I2CManager for SAMD 2023-01-31 00:36:57 +00:00
peteGSX
77b20e6a16 Merge branch 'ex-io-28-feature-request-enable-pwm-support' of https://github.com/DCC-EX/CommandStation-EX into ex-io-28-feature-request-enable-pwm-support 2023-01-30 05:00:40 +10:00
peteGSX
1d27eb67e4 Brief PWM start 2023-01-30 05:00:31 +10:00
peteGSX
7f19a92d2a Refactored, analogue tested 2023-01-30 05:00:31 +10:00
peteGSX
28caa9e8d3 Brief PWM start 2023-01-29 19:26:33 +10:00
Harald Barth
95945eab4c version bump 2023-01-29 08:50:19 +01:00
Harald Barth
638682f05c STM32F4xx fast ADC read implementation (merge branch 'stm32_adcee_pma' into devel) 2023-01-29 08:47:05 +01:00
peteGSX
ffb08523da Merge branch 'ex-io-28-feature-request-enable-pwm-support' of https://github.com/DCC-EX/CommandStation-EX into ex-io-28-feature-request-enable-pwm-support 2023-01-29 17:18:33 +10:00
peteGSX
d8a1bcaf34 Refactored, analogue tested 2023-01-29 17:18:23 +10:00
Harald Barth
212bf8d80e Broadcast power for <s> again 2023-01-29 08:13:52 +01:00
peteGSX
a17c02444d Refactored, analogue tested 2023-01-29 10:06:01 +10:00
Harald Barth
290d878063 version 2023-01-28 19:09:16 +01:00
Harald Barth
2a7588b1b5 jT answer should contain empty string 2023-01-28 19:07:59 +01:00
pmantoine
be33bafa66 Fixed logic of ADC ready 2023-01-28 14:39:00 +08:00
pmantoine
6cc66e26c1 Initial STM32F4xx fast ADC read implementation 2023-01-28 13:58:55 +08:00
Harald Barth
c91d66549c Remove warnings 2023-01-27 19:42:55 +01:00
Harald Barth
9e5d780c14 Bugfix for issue #299 TurnoutDescription NULL 2023-01-27 18:42:26 +01:00
Harald Barth
2c0886bc2f version and copyright info 2023-01-27 17:03:39 +01:00
Harald Barth
762742b4af Add the macro def 2023-01-27 13:05:36 +01:00
Harald Barth
88b572a148 Add EXRAIL IFLOCO function 2023-01-26 16:55:58 +01:00
peteGSX
fcf16c1367 Update version 2023-01-26 18:53:25 +10:00
Colin Murdoch
c69b8d85c8 Merge branch 'devel-plus-fastclock' into devel 2023-01-24 12:30:48 +00:00
Colin Murdoch
006c85e6ae Delete platformio.ini.original
Delete file not required
2023-01-24 12:21:28 +00:00
Neil McKechnie
d0ce59b19f I2CManager_Mega4809.h - allow other I2C clock speeds. 2023-01-24 09:35:10 +00:00
Neil McKechnie
a3d4255fee Revert "I2CManager_Mega4809.h - allow other I2C clock speeds."
This reverts commit 682c47f7dd.
2023-01-24 09:33:17 +00:00
Neil McKechnie
e8e00f69d6 Non-blocking I2C - reset byte counters on timeout. 2023-01-23 22:31:33 +00:00
Neil McKechnie
10c8915d33 Ensure correct functions are called for strcpy_P, strncmp_P, strlen_P etc. on non-AVR targets.. 2023-01-23 22:28:43 +00:00
peteGSX
4f233de726
Merge pull request #297 from DCC-EX:31-exio-to-do-optimise-read-speed
31-exio-to-do-optimise-read-speed
2023-01-24 08:25:13 +10:00
Neil McKechnie
682c47f7dd I2CManager_Mega4809.h - allow other I2C clock speeds. 2023-01-23 22:23:05 +00:00
peteGSX
4acf46db54 EX-IO reads optimised for speed 2023-01-24 08:17:43 +10:00
peteGSX
20b3e9064c Analogue inputs functioning 2023-01-23 21:35:22 +10:00
peteGSX
459904e5dd More analogue inputs 2023-01-23 20:12:28 +10:00
peteGSX
878549d538 Working on analogue inputs 2023-01-23 16:26:07 +10:00
peteGSX
7f4e3d9cea Digital inputs optimised 2023-01-23 11:49:23 +10:00
peteGSX
f2aeb4069f
Merge pull request #296 from DCC-EX:294-bug-report-ex-rail-signalsignalh-inverted
294-bug-report-ex-rail-signalsignalh-inverted
2023-01-23 04:59:54 +10:00
peteGSX
aaf25d5426 Remove excess comments 2023-01-23 04:53:39 +10:00
Neil McKechnie
705617239f Sort out I2C timeout handling, and further I2C diagnostics.
Timeout handling and recovery in loop() function now operative.
Start-up check for I2C signals short to ground added.
Initial I2C device probe speed up.
Possible infinite loops in I2C AVR native driver during fault conditions removed.
2023-01-22 13:13:20 +00:00
Neil McKechnie
bfbc45674f Update IO_AnalogueInputs.h
Add I2C initialisation calls (previously missing).
2023-01-22 12:38:24 +00:00
Neil McKechnie
e079a9e395 Update IO_VL53L0X.h
Improve address changing logic.
2023-01-22 12:37:16 +00:00
peteGSX
fb9170ab8b SIGNAL/SIGNALH operating correctly 2023-01-22 19:25:00 +10:00
Colin Murdoch
286bdc3c4d Create platformio.ini.original 2023-01-21 10:20:49 +00:00
Colin Murdoch
cd46d3c9e0 Remove #ifdef and merge calcs
Remove #idfef statements and merge duplicate routines into CommandDistributor
2023-01-21 10:18:54 +00:00
pmantoine
fb36bd1380 Fix F446 Serial Pin Comment (Rx/Tx) 2023-01-17 20:15:30 +08:00
Colin Murdoch
b62c4da04d Update CommandDistributor.h
Fixed #endif typo.
2023-01-17 10:56:12 +00:00
Neil McKechnie
ccf463b507 IODevice.cpp: Fix error in overlap checking.
The checkNoOverlap() function didn't work correctly in the case where one device has nPins=0.  All devices configured after that were rejected, even when no overlap was present.
2023-01-16 23:03:53 +00:00
Neil McKechnie
abf62dfd85 IO_VL53L0X driver: improve I2C error checking and reporting. 2023-01-16 23:00:58 +00:00
Colin Murdoch
8fac20a451 Add #ifdef selections
Add #ifdef selections linked to #define in config.exampe.h
2023-01-16 18:16:25 +00:00
Neil McKechnie
7c25f22939 Fix error reported in IO_DFPlayer.h when compiling for some platforms 2023-01-14 23:50:33 +00:00
Neil McKechnie
0c054c4d42 Merge branch 'devel' into devel-nmck 2023-01-14 23:37:17 +00:00
Neil McKechnie
538519dd9d Add option to suppress I2C retries.
I2CRB method suppressRetries() added to allow retries to be suppressed.
2023-01-14 18:58:06 +00:00
Neil McKechnie
6e69df2da8 Add I2C retries to Wire and to non-blocking I2CManager. 2023-01-14 18:18:57 +00:00
Neil McKechnie
3c5b7bbcfe HAL updates
Remove redundant deferment of device _begin() calls (no longer necessary).
Improve diagnostic loop measurement.
2023-01-14 17:15:30 +00:00
Neil McKechnie
79437bbf37 Update MotorDriver.cpp
Remove unnecessary and undesirable interrupt disable/enable when writing to HAL driver.
2023-01-14 17:10:45 +00:00
pmantoine
1be382a6ed Fixed comment re Serial1 for STM32F446RE 2023-01-14 12:45:21 +08:00
pmantoine
1f433d0c17 Serial1 for STM32F446RE corrected. 2023-01-14 12:43:05 +08:00
pmantoine
046e62a8b3 Minor fix to DCCTimerSTM32.cpp for F412ZG. 2023-01-13 17:24:26 +08:00
peteGSX
a2c7c7d12a
Merge pull request #292 from DCC-EX:exio-prevent-digital-analogue-conflict
Exio-prevent-digital-analogue-conflict
2023-01-12 08:21:13 +10:00
peteGSX
9b36bdcf46 Logic and diag message done 2023-01-12 08:10:41 +10:00
peteGSX
a8646a2f32 Fix EX-Turntable diag message 2023-01-12 07:33:50 +10:00
peteGSX
22e20f9092 Logic added and working 2023-01-12 07:27:42 +10:00
Colin Murdoch
873d470f86 Supply missing function
Supply missing function
2023-01-11 19:50:39 +00:00
Colin Murdoch
ff7260b9bc Added code for FastClock
Added code for both I2C fastclock and serial clocks
2023-01-11 17:36:11 +00:00
peteGSX
de4954ca3e
Merge pull request #288 from DCC-EX:debug-ex-io-expander-on-mega
Debug-ex-io-expander-on-mega
2023-01-10 20:11:45 +10:00
peteGSX
c26f53e1fa Device driver fixed 2023-01-10 20:05:09 +10:00
peteGSX
e48a40fafb Change to blocking I2CManager calls 2023-01-10 13:07:54 +10:00
peteGSX
5c120efa16 Add being 2023-01-10 08:16:42 +10:00
peteGSX
9abcfb9e4f Add begin delay to test 2023-01-09 20:08:36 +10:00
peteGSX
e01893bcf1 Comment out unused variables 2023-01-09 20:03:18 +10:00
pmantoine
6eff836473 Add -Wunused-variable build flag to Nucleo builds 2023-01-09 17:18:50 +08:00
pmantoine
402e16727c Fix platformio for Nucleo-F446RE 2023-01-09 16:47:29 +08:00
pmantoine
658fca2601 Nucleo-F446RE Build target support 2023-01-09 16:24:29 +08:00
peteGSX
3fccf6a484 Fix EX-IOExpander myHal.cpp example 2023-01-03 08:57:21 +10:00
peteGSX
ace9c1642a
Merge pull request #285 from DCC-EX:add-rotary-encoder
New working rotary encoder branch
2022-12-30 10:38:10 +10:00
peteGSX
ec4dfb8c1e New working rotary encoder branch 2022-12-30 09:46:42 +10:00
peteGSX
6482a421b4
Merge pull request #282 from DCC-EX/add-ex-ioexpander
Add-ex-ioexpander
2022-12-30 08:10:23 +10:00
peteGSX
d02c6b1f61
Merge branch 'devel' into add-ex-ioexpander 2022-12-30 08:04:49 +10:00
Asbelos
94c8dafeb2 renamed macros 2022-12-29 10:38:04 +00:00
peteGSX
322cb3db54 Include driver in IODevice.h 2022-12-29 08:44:08 +10:00
peteGSX
ffdf023de6 Clean up 2022-12-29 05:10:37 +10:00
peteGSX
8f32ae712f Fix myHal example 2022-12-27 10:13:08 +10:00
peteGSX
eea1396997 Remove EX-IO pin macros 2022-12-27 10:10:44 +10:00
Asbelos
b1bd28273d duinoNodes support 2022-12-26 11:06:42 +00:00
Asbelos
0be25f6e7f Squashed commit of the following:
commit e06668f042
Author: Asbelos <asbelos@btinternet.com>
Date:   Mon Dec 26 10:09:34 2022 +0000

    speedup

commit 3e5d3b1caa
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 22:11:56 2022 +0000

    Rename

commit 81099af42b
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 21:35:38 2022 +0000

    spelling and polling

commit 9240e7c6ba
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 25 20:52:07 2022 +0000

    input working

commit 6c1c681a26
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Dec 21 11:18:39 2022 +0000

    input working

    1 board, no kit map, output untested

commit 5ce67fac97
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 18 15:32:37 2022 +0000

    Include IO_DNU08 automatically

commit ac8d453d2c
Author: Asbelos <asbelos@btinternet.com>
Date:   Sun Dec 18 12:28:13 2022 +0000

    BNOU8 HAL driver
2022-12-26 10:41:15 +00:00
peteGSX
71ce913712 Version bugfix 2022-12-26 07:36:12 +10:00
peteGSX
70845b4932 Receive/display EXIO version 2022-12-26 06:44:15 +10:00
peteGSX
c44fb0ac44 Disable device driver version, add myHal example 2022-12-22 07:22:04 +10:00
peteGSX
1c7103c21e Analogue read bugfix 2022-12-21 08:37:23 +10:00
peteGSX
5170147e3e Error checking pin config, code tidy 2022-12-20 19:41:32 +10:00
peteGSX
2ad08029a4 Remove excess DIAG output 2022-12-20 08:05:05 +10:00
peteGSX
25b3250345 Digital read working 2022-12-20 07:08:42 +10:00
peteGSX
3973996344 Digital pin config done, digital read in progress 2022-12-19 14:24:49 +10:00
peteGSX
943494385f Add digital write 2022-12-18 18:59:16 +10:00
peteGSX
c8fea3a4a7 Add version, analogue reads working 2022-12-18 09:43:11 +10:00
Asbelos
1d61a8f3f9 HIGHMEM + WITHROTTLE
EXRAIL HIGHMEM feature affects parser and withrottle.

Ringstream and wifi fixes

Withrottle connecting / reconnecting
2022-12-16 13:14:48 +00:00
peteGSX
a480a5a3d2 Add comments, remove unnecessary functions 2022-12-15 15:10:53 +10:00
peteGSX
070daa37dc Move buffers to constructor 2022-12-15 07:58:21 +10:00
peteGSX
75f1a8f43a More bugs to fix 2022-12-14 07:49:09 +10:00
Harald Barth
ad294ea17e typo 2022-12-13 15:29:20 +01:00
peteGSX
8ecb408da7 Update I2C address, fix bug setting analogue pins 2022-12-13 19:51:41 +10:00
peteGSX
3862f7250d Fix bugs, learn I2CManager 2022-12-12 19:54:20 +10:00
peteGSX
785b515f9e Bug fixes, update registers 2022-12-11 19:44:42 +10:00
peteGSX
9699a44081 Rename pin file 2022-12-11 10:25:29 +10:00
peteGSX
cb9a8bb7a6 Getting somewhere 2022-12-11 10:22:48 +10:00
peteGSX
1d5897d2d2 A bit lost 2022-12-10 19:14:32 +10:00
peteGSX
7bc0433197 Add myHal.cpp example to driver 2022-12-10 08:32:15 +10:00
peteGSX
9104956009 Fix default pin maps 2022-12-10 08:28:20 +10:00
peteGSX
af4d8d4075 Merge branch 'add-ex-ioexpander' of https://github.com/DCC-EX/CommandStation-EX into add-ex-ioexpander 2022-12-10 08:25:05 +10:00
peteGSX
06945bb114 Try to add pin map classes 2022-12-10 08:23:46 +10:00
peteGSX
2d27cb052d Add registers 2022-12-09 14:41:48 +10:00
peteGSX
8cbcf5df32 Basic shell of device driver started 2022-12-08 14:21:01 +10:00
Harald Barth
13368c319a reuse WiThrottle list entries 2022-12-05 15:52:23 +01:00
Harald Barth
cb1fc75077 version 2022-12-04 20:12:04 +01:00
Harald Barth
b671d70dfe fix static IP addr 2022-12-04 20:09:14 +01:00
Harald Barth
45b36c48cb be more strict about int vs char for the wifi diag 2022-11-30 13:16:04 +01:00
pmantoine
d95096ded8 Fixes STM32 compiler warning, and WIT/WIFI diags 2022-11-30 10:11:27 +08:00
pmantoine
a6ae1a48a2 Fixed STM32F4xx MAC address simulation 2022-11-28 10:44:41 +08:00
pmantoine
913f0a0c86 STM32F412ZG/F446ZE serial support update 2022-11-27 21:04:49 +08:00
Harald Barth
984fd2fa08 select better bytes for the faked mac addr 2022-11-25 20:44:16 +01:00
Harald Barth
d062de2eb8 better pseudo random 2022-11-24 20:24:57 +01:00
Harald Barth
e1ec63464c Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2022-11-23 22:45:59 +01:00
Harald Barth
aa02cd11e3 firebox disable, gcc flag for smaller binary 2022-11-23 22:44:29 +01:00
Neil McKechnie
ce5d546b9b Update DCC.cpp to remove diagnostic code. 2022-11-22 17:26:11 +00:00
Neil McKechnie
644cb29a3a Performance improvements in function DCC::issueReminders
Function issueReminders was taking around 1700us to complete.  It has been refactored to optimise calculations and reduce the amount of the loco table that needs to be scanned each time.  It now takes typically under 50us to execute.
2022-11-22 17:24:11 +00:00
Neil McKechnie
70203c3733 Fix to IO_DFPlayer.h - device was ignoring commands
The DFPlayer device does not like successive commands arriving to quickly after one another and may ignore a command.  The driver has been modified to enforce a delay between commands by sending pad characters where necessary.
2022-11-22 17:15:13 +00:00
Harald Barth
f4aa572df2 Remove RAM thief 2022-11-18 20:19:53 +01:00
Harald Barth
01e5d49332 version 2022-11-16 00:14:54 +01:00
Harald Barth
6a3a891682 break to va_end() 2022-11-16 00:14:11 +01:00
Harald Barth
f5b48619bf AVR Mega2560: Set timer reg ADCSRB correct 2022-11-16 00:13:31 +01:00
Harald Barth
4a3d8729c6 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2022-11-09 00:52:31 +01:00
Harald Barth
d874ad8cc3 Make GETFLASHW code more universal 2022-11-09 00:52:07 +01:00
Harald Barth
bd8439c2f9 Fix: Roster indexing when sending to withrottle 2022-11-08 23:35:07 +01:00
pmantoine
65714ed1f2 Rename Nucleo build target 2022-11-08 07:48:35 +11:00
Harald Barth
280e61e1fc Make EthernetInterface code more robust 2022-11-07 11:53:56 +01:00
Harald Barth
b061c0b347 version 2022-11-07 11:22:15 +01:00
Harald Barth
7f3d547541 Initialize outboundRing properly to NULL 2022-11-07 11:20:00 +01:00
Harald Barth
eb0861959c version 2022-11-06 21:33:40 +01:00
Harald Barth
f1d445e056 Do not abort ethernet startup on W5100 2022-11-06 21:32:54 +01:00
Harald Barth
4f19a60621 number of ADC inputs was reversed 2022-11-06 21:30:32 +01:00
Harald Barth
2b3ba514b0 Use X as the questionmark sign in <T 17 X> 2022-11-05 23:11:54 +01:00
Harald Barth
a199de6d3e Make <T nn ?> return long config print 2022-11-04 23:43:26 +01:00
Harald Barth
c0cb643cb5 When sending all turnouts, keep it short 2022-11-04 23:15:29 +01:00
Harald Barth
be2f3b0db7 Ethernet restructure 2022-11-04 16:08:43 +01:00
pmantoine
f939ea0768 Add MEGAAVR ADCeee skeleton. 2022-11-02 13:55:10 +08:00
pmantoine
863c839563 Add Teensy ADCee class skeleton. 2022-11-02 13:46:16 +08:00
Harald Barth
ee4963dfca devel version string update 2022-10-31 19:43:55 +01:00
Asbelos
5e2b416c30 roster list half error 2022-10-31 19:38:49 +01:00
Asbelos
eed1237b9f FIX Driveaway! 2022-10-31 19:14:33 +01:00
peteGSX
07f1d6fc20 Updated .gitignore (#261) 2022-10-31 19:13:43 +01:00
Harald Barth
45504db1ad stacked motor shield example typo fix 2022-10-26 18:59:39 +02:00
Harald Barth
d60a55091f Merge branch 'PORTX_HAL-cursense2' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL-cursense2 2022-10-25 12:01:13 +02:00
Harald Barth
90897ff2d1 stacked motor shield example 2022-10-25 11:59:29 +02:00
pmantoine
88c7e540fa STM32 additional serial port support 2022-10-24 11:12:56 +08:00
pmantoine
888165e987 Skeletal ADCee class for STM32 2022-10-24 08:33:08 +08:00
pmantoine
57aa1457e0 GETFLASHW hack for SAMD/STM32 2022-10-23 17:49:39 +08:00
Harald Barth
1a67930af4 make tones like a Taurus 2022-10-22 00:48:53 +02:00
Harald Barth
604a69e0a8 version 2022-10-20 00:54:35 +02:00
Harald Barth
f56a9a2c43 compiler warning fixes and stupid cast for ESP toolchain 2022-10-20 00:53:05 +02:00
Harald Barth
56ed6ab6dc free memory value updated on ESP 2022-10-20 00:52:19 +02:00
Harald Barth
9afce0a7df change from xstrcmp to xstrncmp 2022-10-19 11:10:37 +02:00
Harald Barth
e8180603ba version tag 2022-10-18 23:41:21 +02:00
Harald Barth
f792e07d40 remove possible RAM corruption and improve findUniqThrottle 2022-10-18 22:48:02 +02:00
Harald Barth
2a51fa6f5d Do not specify inline (because of linker) 2022-10-18 22:47:43 +02:00
pmantoine
1c73a3d7bf SAMD21 IRQ priority exploration 2022-10-16 16:15:02 +08:00
Harald Barth
050eb52a22 Merge branch 'PORTX_HAL-cursense' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL-cursense 2022-10-14 14:20:41 +02:00
Harald Barth
81b5b25430 disable EEPROM on ESP32 2022-10-14 14:20:32 +02:00
pmantoine
f39a9d1510 SAMD21 ADCee fixes 2022-10-14 18:55:12 +08:00
pmantoine
578cbd08e5 SAMD21 senseFactor fix for 12-bit 2022-10-13 09:12:51 +08:00
Harald Barth
cf89fe2a72 make max ADC value a per platform ADC function 2022-10-12 23:45:10 +02:00
pmantoine
65364212ca SAMD21 fix analogresolution in ADCee::init 2022-10-12 17:30:20 +08:00
pmantoine
eb766aa27f Fix to SAMD21 ADCee pin init 2022-10-12 17:12:00 +08:00
pmantoine
4d205be007 SAMD21 ADCee class full implementation 2022-10-12 16:46:37 +08:00
Harald Barth
208905e7b9 explain in comment 2022-10-10 07:59:54 +02:00
Harald Barth
c36234df73 Dont forget ESP32 has 12 bits ADC 2022-10-05 23:43:09 +02:00
Harald Barth
452ffc5725 changed IODevice code to use ADCee 2022-10-05 23:14:07 +02:00
Harald Barth
08c114fd22 Fixed minor typo in ESP32 code which broke everything 2022-10-05 22:27:27 +02:00
Harald Barth
e0bf978f2b Change Adc to ADCee because of SAMD conflict 2022-10-04 22:19:51 +02:00
Harald Barth
3e214ab77a version 2022-10-04 21:56:58 +02:00
Harald Barth
ee568fcd11 make the Adc class functions the normal code path 2022-10-04 21:55:13 +02:00
Harald Barth
367d2dfe20 version 2022-10-04 00:35:58 +02:00
Harald Barth
b7295c4923 add class Adc instead of motordriver specific analog pin read 2022-10-04 00:32:48 +02:00
Harald Barth
5e616a9eb2 make compile for other arch but AVR 2022-10-02 22:53:35 +02:00
Harald Barth
24e5e648b8 improve current sense sampling on AVR 2022-10-02 13:40:46 +02:00
Harald Barth
24a7475482 version 2022-10-02 00:44:46 +02:00
Harald Barth
c47e9b79ca do analogRead without need for noInterrupt - first test 2022-10-02 00:43:46 +02:00
Harald Barth
052f5807f0 installer shell script 2022-09-24 23:32:24 +02:00
Harald Barth
3da44be86f version 4.2.4 2022-09-19 00:08:29 +02:00
Harald Barth
0cf58a996d remove redunant diag 2022-09-19 00:08:12 +02:00
Harald Barth
88f16140f8 document Bluetooth on ESP32 (SERIAL_BT_COMMANDS) 2022-09-18 23:18:40 +02:00
Harald Barth
431dc2bcc6 version 4.2.4 rc3 2022-09-18 22:30:19 +02:00
Harald Barth
27a458a850 handle decoders that do not ack better 2022-09-14 12:27:04 +02:00
Harald Barth
02ed7828c1 EX-RAIL bugfix: Could not read long loco addrs 2022-09-13 22:47:24 +02:00
Harald Barth
ad6c1384c9 EXRAIL bugfix, protect RANDWAIT from division by zero. Triggered by DELAYRANDOM(X, X+99) or less diff 2022-09-05 09:02:35 +02:00
pmantoine
7c4640ad91 STM32 Serial1 defs for F411RE and F446ZE 2022-09-03 13:06:02 +08:00
Harald Barth
c6866aba86 Fundumoto board does not have brake 2022-08-30 22:45:10 +02:00
Harald Barth
a91152be95 example of motor shield that does brake on pwn and that reversed which equals out 2022-08-30 22:17:07 +02:00
Harald Barth
7d1d6bf1e1 make inverted power pin possible, lower DC frequency on Mega and for Uno if inverted def is used 2022-08-30 22:12:10 +02:00
Harald Barth
cec6d6dbe7 need to ifdef that 2022-08-30 09:44:59 +02:00
Harald Barth
08427abe70 Workarounds for bugs and functionality not in the Arduino ESP32 package 2022-08-30 09:31:09 +02:00
Harald Barth
9dabf14aa3 ESP32: Experimental BT support, enable with #define SERIAL_BT_COMMANDS 2022-08-28 22:54:26 +02:00
Harald Barth
2f9c8faa77 ESP32: More WiFi diag 2022-08-28 19:44:07 +02:00
Harald Barth
86215b28ae Bugfix: Preserve direction all times when switching between DCC and DC 2022-08-23 07:56:56 +02:00
Harald Barth
827e4fef86 be consistent about that tripValue is already over limit 2022-08-22 08:48:33 +02:00
Harald Barth
43b7b5d797 adjust max current limit to ADC capability 2022-08-22 08:47:37 +02:00
Harald Barth
3c706926c5 more explanations to motor driver definitions and more conservative max current limit 2022-08-22 08:46:47 +02:00
Harald Barth
a9ce9101e6 return success/fail from <f> and <F> command handling (setFn, parsef) 2022-08-20 18:15:18 +02:00
Harald Barth
58bac3dc51 revert adding space to power reply because of RtDriver app 2022-08-20 17:35:07 +02:00
Harald Barth
4fb53572f5 Diag outgoing messages on ESP32 2022-08-20 11:45:29 +02:00
Harald Barth
11c5dc5f06 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-19 17:04:03 +02:00
Harald Barth
5eb04f77a8 Replace FLUSH-SHOVE with broadcastReply() 2022-08-19 14:33:04 +02:00
pmantoine
9d6931c438 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-17 15:28:54 +08:00
pmantoine
c2c51e32c3 Update STM32 timer 11 commentary 2022-08-17 15:28:52 +08:00
Harald Barth
86538a4902 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-17 09:20:18 +02:00
Harald Barth
04188926b4 Improve messages for power overload 2022-08-17 09:19:47 +02:00
pmantoine
77cca8f6ee STM32 switch to Timer11 for DCC interrupts 2022-08-17 14:29:02 +08:00
Harald Barth
e36e867ec2 ESP32: implement JOIN 2022-08-17 02:11:51 +02:00
Harald Barth
d3dbeaa666 bugfix: Wrong condition for ring->commit() 2022-08-17 00:28:15 +02:00
Harald Barth
cadb82ab6b remove currently unused virtual functions 2022-08-16 22:32:27 +02:00
Harald Barth
1fc5f436fd Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-16 19:23:19 +02:00
Harald Barth
9633e77c0a move mark/commit into CommandDistributor::parse() 2022-08-16 19:22:19 +02:00
Harald Barth
5d415366d8 bigger ringstream on ESP32 2022-08-16 12:43:38 +02:00
Harald Barth
b9c1e779ae move WiFiESP::loop() to core 1 2022-08-16 12:25:49 +02:00
Harald Barth
162e1f9d3e move ringClient into RingStream 2022-08-16 09:38:40 +02:00
Harald Barth
ff28dbd561 no need to estop repeatedly 2022-08-15 10:50:55 +02:00
Harald Barth
60d91eef9d lots of diag 2022-08-15 10:49:56 +02:00
pmantoine
f0c2672835 Corrected comment in DCCTimer for STM32 2022-08-15 13:32:13 +08:00
habazut
8cff51b913
Merge pull request #252 from DCC-EX/add-EX-Turntable
Add ex turntable
2022-08-14 00:47:35 +02:00
peteGSX
9f27759a9c Add myEX-Turntable.example.h 2022-08-14 07:11:38 +10:00
peteGSX
0a6d023373 Add <D TT ...> ready to test 2022-08-14 06:58:20 +10:00
peteGSX
d333a265f4 Add EX-Turntable to PORTX_HAL ready to test 2022-08-14 06:48:03 +10:00
Harald Barth
c49d11573c less verbose diag, version stamp 2022-08-13 18:38:51 +02:00
Harald Barth
82bcae627b Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-13 17:37:37 +02:00
Harald Barth
905b2c0148 protect from NULL pointer deref in ESP32 code when no MAIN rmt cannel is present 2022-08-13 17:36:53 +02:00
pmantoine
4a84ea1b43 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-13 17:32:25 +08:00
Harald Barth
059fd1b193 Send inverted signal (DCC and DC mode) on ESP32 if signalPin2 defined in MotorDriver 2022-08-13 09:12:53 +02:00
Harald Barth
da8b189b43 protect from NULL deref if running without prog track 2022-08-13 01:52:44 +02:00
pmantoine
915de96e80 DC mode portability for ARM (SAMD21/STM32) 2022-08-10 10:45:33 +08:00
Harald Barth
a985356f0c version 2022-08-10 00:51:17 +02:00
Harald Barth
c1993fba87 make DC mode portable to ESP32 2022-08-10 00:14:28 +02:00
Harald Barth
e1fd6e9414 disable ESP32 code that is not used 2022-08-09 16:26:48 +02:00
Harald Barth
ecda69ba32 make sending loop ringbuffer to ESP32 Wifi more simple 2022-08-09 15:25:29 +02:00
Harald Barth
76c5608181 Protect port registers from change during interrupt code in differnet way 2022-08-09 13:12:04 +02:00
pmantoine
84e44df47c Copyright addition 2022-08-09 15:03:22 +08:00
pmantoine
db032d567d Update TEENSY copyright 2022-08-09 14:57:33 +08:00
pmantoine
c9612984e8 STM32F411 fix to DCC period 2022-08-09 12:28:15 +08:00
Harald Barth
f56e3bec9e eliminate wrong ringread due to peek (eliminated peek as well) - new version 2022-08-08 13:00:53 +02:00
Harald Barth
966b9594ef version (milestone marker) 2022-08-07 20:14:26 +02:00
Harald Barth
186fd8adee implement Withrottle client handles to remember clients over reconnects 2022-08-07 20:12:42 +02:00
Harald Barth
5182bb171d ESP32 do not recycle client handles for now 2022-08-07 20:11:31 +02:00
Harald Barth
caca265529 alternate implementation of DCC::issueReminders() 2022-08-07 20:05:40 +02:00
Harald Barth
c115c441e4 ESP32 introduce NetworkClient class to hold state associated to WiFiClient 2022-08-07 01:24:41 +02:00
Harald Barth
6540ffee75 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-06 19:23:57 +02:00
Harald Barth
76137ff24c send some answer to loco enquire even if no locos are found 2022-08-06 19:23:52 +02:00
Harald Barth
64a6412ce2 hearbeat 1s too agressive 2022-08-06 19:08:26 +02:00
Harald Barth
803db81c8f try to recover from ringbuffer read problem 2022-08-06 19:07:35 +02:00
pmantoine
af75297a23 I2CManager support for 1Mhz+ I2C speeds 2022-08-06 18:06:00 +08:00
pmantoine
e7d8d320bd SAMD21 I2C native interrupt capable driver 2022-08-06 17:51:13 +08:00
pmantoine
17bdd2d724 Teensy build support, STM32F411RE first beta 2022-08-06 16:53:14 +08:00
Harald Barth
42c35a11e1 version 2022-08-06 00:35:54 +02:00
Harald Barth
012d427c6e loop over WiThrottle so that broadcast commands get into outbound ring and get sent 2022-08-06 00:34:35 +02:00
Harald Barth
7551c2d2f6 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-05 19:54:29 +02:00
Harald Barth
5f1a263158 do not make delete client conditional 2022-08-05 19:54:01 +02:00
Asbelos
42ac954475 Forget withrottle client on disconnect 2022-08-05 18:14:22 +01:00
Harald Barth
01f129e25f version update 2022-08-05 18:49:35 +02:00
Harald Barth
608c7547fb ESP32 Wifi AP mode no sleep 2022-08-05 16:14:51 +02:00
Harald Barth
5430711672 size send buffer for RMT channel including DCC checksum byte 2022-08-05 15:16:43 +02:00
Harald Barth
704fabd1a4 check M command against max DCC packet size 2022-08-05 15:15:42 +02:00
Harald Barth
6286f5fedf init trackPWM to false so that we do not get false positives later 2022-08-05 12:25:32 +02:00
Harald Barth
67b14ec57d take into account that the reset packets are sent first #repeat packets into the future 2022-08-04 09:50:20 +02:00
Harald Barth
f57fd245a1 temp fix for counting packets code 2022-08-04 04:15:28 +02:00
Harald Barth
03c6f3ab24 Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-08-04 01:22:32 +02:00
Harald Barth
0301b78712 version update 2022-08-04 01:22:07 +02:00
Harald Barth
96f042897a Adopt setTrackMode to RMT channel pins 2022-08-04 01:21:28 +02:00
Harald Barth
7bad16dc59 ESP32 DCCWaveform::begin() bugfix for prog 2022-08-04 01:20:33 +02:00
Harald Barth
87fd1b887e ESP32 RMT pin add function 2022-08-04 01:19:11 +02:00
pmantoine
ad4a9d88b4 ESP32 platformio initial build info 2022-08-03 16:21:58 +08:00
Harald Barth
fad504bc7f extra catch for buggy compiler/preprocessor 2022-08-03 09:31:03 +02:00
Harald Barth
172dbfd444 make whole files ESP32 only 2022-08-03 08:57:34 +02:00
Harald Barth
ee279c9a03 version update 2022-08-02 21:56:06 +02:00
Harald Barth
33327d14c9 Current reading for ACK and overload on ESP32 2022-08-02 21:49:16 +02:00
Harald Barth
6167a949b6 approx conversion factor for 12bit ADC of ESP32 2022-08-02 21:46:00 +02:00
Harald Barth
df767aaa36 go back to idle/reset packet if nothing to do 2022-08-02 21:44:29 +02:00
Harald Barth
37ea688eab do compare with 255 right 2022-08-02 20:58:05 +02:00
Harald Barth
89905f8ed7 check ack in loop and update version 2022-08-02 16:32:21 +02:00
Harald Barth
c7cf8246a7 make sentResetsSincePacket private to the class and replace all accesses with with methods 2022-08-02 15:57:45 +02:00
Harald Barth
863f2f4a85 eliminate resetsCounterP/setResetCounterPointer and replace with isProgTrack/makeProgTrack method 2022-08-02 15:36:51 +02:00
Harald Barth
6b2cd226e2 provide methods to access sentResetsSincePacket 2022-08-02 15:13:29 +02:00
Harald Barth
07600274f1 count packets sent by RMT HW 2022-08-02 12:42:20 +02:00
Harald Barth
167c5db1fe version which does MAIN on ESP32 2022-08-02 01:30:26 +02:00
Harald Barth
2c24bbee17 typo in DIAG 2022-08-02 01:26:02 +02:00
Harald Barth
b09dba1213 Make packetPending private and new access routine. Implement schedulePacket without packetPending variable for ESP32 2022-08-02 01:24:01 +02:00
Harald Barth
33c9155f6e Make own interrupt routine selection array as RMT api only can have one interrupt routine with one argument 2022-08-02 01:22:36 +02:00
Harald Barth
e5ce76e703 idle waveform through RMT 2022-08-01 22:56:56 +02:00
Harald Barth
7a123e7e17 make service start to be outside the DONT_TOUCH_WIFI_CONF area 2022-08-01 09:47:29 +02:00
Harald Barth
ed1b451b85 ESP32 Wifi "good to have" commands 2022-07-31 13:35:25 +02:00
Harald Barth
90d6ff43c5 on ESP32 wifiloop is an own task 2022-07-31 13:31:58 +02:00
Harald Barth
9d4e7903d5 version tag 2022-07-31 10:46:42 +02:00
Harald Barth
eeb70293e0 disable ringstream read from flash 2022-07-31 10:42:03 +02:00
Harald Barth
024c8fc199 repair peek 2022-07-31 10:41:27 +02:00
Harald Barth
ddc0c5ac3c new ringstream peek inline 2022-07-31 09:41:46 +02:00
Harald Barth
8916d1415f Revert "add RinStream::peek()"
This reverts commit 26fc11d1a6.
2022-07-31 09:23:02 +02:00
Harald Barth
2ad0d7ab76 ESP32 wifi should not sleep, otherwise auth errors 2022-07-31 09:20:34 +02:00
Harald Barth
3f0b3ccaf7 ESP32 wifi added to startup and loop 2022-07-31 08:49:12 +02:00
Harald Barth
bdd87e7399 wifi 2nd try kludge 2022-07-31 08:47:58 +02:00
Harald Barth
3aef54c0fe add RinStream::info() 2022-07-31 00:53:26 +02:00
Harald Barth
26fc11d1a6 add RinStream::peek() 2022-07-31 00:50:59 +02:00
Harald Barth
d29219f858 ifdef whole file 2022-07-31 00:23:19 +02:00
Harald Barth
06647ae7e4 make RMT channel compile without dccpacket class 2022-07-31 00:18:35 +02:00
Harald Barth
ca84cd2ea6 Merge ESP32 files of branch 'ESP32-checkpoint' into PORTX_HAL 2022-07-31 00:15:38 +02:00
Harald Barth
fb513b64f9 (c) and version tag 2022-07-30 23:08:42 +02:00
Harald Barth
4316413618 compiles (but no waveform) on ESP32 2022-07-30 23:02:26 +02:00
Harald Barth
bfa81b801e fix compiler Werror 2022-07-30 21:54:28 +02:00
Harald Barth
ec12baa0ca motor driver def for standard shield on uno form factor 2022-07-30 21:50:21 +02:00
Harald Barth
7ce74cfdf8 pin assignment for uno form factor board 2022-07-30 21:33:25 +02:00
Harald Barth
67e8c04314 in principle schedules packets 2022-07-30 21:11:51 +02:00
Harald Barth
37f44709f9 RMT prog track channel start 2022-07-30 15:59:19 +02:00
Harald Barth
83300387d2 working pin assignment in config.example.h 2022-07-30 15:57:35 +02:00
Harald Barth
237846f190 clean up diag, multiple gpio pin test worked 2022-07-30 15:47:19 +02:00
Harald Barth
6c940615f6 make mDC a vector in the Container and bugfixes 2022-07-30 15:33:47 +02:00
Harald Barth
342b9798f0 define GETFLASHP for pointers 2022-07-30 15:32:02 +02:00
Harald Barth
a5d47e0c2c make mDC a singleton static member of MotorDriverContainer 2022-07-30 15:28:09 +02:00
Harald Barth
feebe67ecb fix compiler Werror 2022-07-30 10:40:45 +02:00
Harald Barth
6e8929c89e Force inline because we do not want to use stack space for this function even if compiler has other preferences 2022-07-26 12:32:18 +02:00
Harald Barth
0373f060fb use pragma GCC push and pop correctly 2022-07-26 12:18:33 +02:00
Harald Barth
ee639de5d6 fix diag bug, update version strings 2022-07-15 21:52:38 +02:00
Harald Barth
b7cd4adb5f Merge branch 'TrackManager-PORTX' into PORTX_HAL 2022-07-15 15:48:07 +02:00
Harald Barth
68f0c6681d Remember connection type determined at first connect 2022-07-15 15:45:25 +02:00
Asbelos
3681f0e445 Serial/SerialUSB cleanup 2022-07-08 15:52:46 +01:00
Asbelos
9768083bfe DCCTimer::reset
Moves CPU dependent reset code into correct place.
2022-07-08 15:01:40 +01:00
Asbelos
090acdae44 More SAMD cleanup 2022-07-08 10:46:52 +01:00
Asbelos
aa3c3c2ee4 SAMD fixups and workaround 2022-07-08 10:34:27 +01:00
Asbelos
385afdeb6c Merge remote-tracking branch 'origin/TrackManager_SAMD' into PORTX_HAL 2022-07-07 10:02:28 +01:00
Asbelos
ff46e283ac Reinstate platformio 2022-07-07 00:28:59 +01:00
Asbelos
108c5050ad Update defines.h
Avoid Wifi on uniwifirev2 untile we have a proper wifinina interface.
2022-07-06 23:46:00 +01:00
Asbelos
12b5c2cdba Merge branch 'PORTX_HAL' of https://github.com/DCC-EX/CommandStation-EX into PORTX_HAL 2022-07-06 22:26:44 +01:00
Harald Barth
76d6759d98 improve parameter check for 'a' command 2022-07-06 21:13:22 +02:00
Harald Barth
a7ea96b392 improve parameter check for 'a' command 2022-07-06 21:12:10 +02:00
Harald Barth
632e9335f3 fix broken 'a' command after broken commit c7b3817 2022-07-06 20:37:17 +02:00
Harald Barth
ca3ed95624 fix broken 'a' command after broken commit c7b3817 2022-07-06 20:35:58 +02:00
Asbelos
06d1040da0 Merge remote-tracking branch 'origin/SAMD_Integration_PMA' into PORTX_HAL 2022-07-06 17:13:59 +01:00
Asbelos
14dc569366 Moved CPU type detection to defines.h
And fixed up BIG_RAM/HAS_ENOUGH_RAM  issues.
2022-07-06 17:05:34 +01:00
Asbelos
b7a82a0ad6 Merge branch 'TrackManager-PORTX' into PORTX_HAL 2022-07-05 21:35:16 +01:00
Harald Barth
5cef1ac864 answer always with hearbeat number 2022-07-05 15:09:32 +02:00
pmantoine
44b21fd987 Added code to respond to <D RESET> 2022-07-05 12:40:00 +07:00
Harald Barth
6687c6f46d when brake pin has inverted sense, honor that when running in DC mode as well 2022-07-04 23:15:02 +02:00
Harald Barth
0406ca69cf add comment Pololu TB9051FTG 2022-07-04 22:52:37 +02:00
Harald Barth
695b776493 add Pololu TB9051FTG 2022-07-04 22:52:37 +02:00
pmantoine
959225c252 Added Arduino Zero USB platform support 2022-07-03 18:01:24 +07:00
Asbelos
a5cda1e350 RingStream RAM saver 2022-06-27 12:42:59 +01:00
Asbelos
7d6c2c8afb smaller random
Saves over 300 bytes of progmem on a uno by omitting the random library
2022-06-18 13:58:46 +01:00
Asbelos
6104311ccb Signal fixes and DCC_SIGNAL/VIRTUAL_SIGNAL 2022-06-18 12:31:54 +01:00
Asbelos
d0e71875e0 UNTESTED ONRED/ONAMBER/ONGREEN
with genericl code tidy for other ON handlers.
2022-06-17 11:48:37 +01:00
Asbelos
044b467085 Implement HAL macro in exrail 2022-06-15 11:44:46 +01:00
Asbelos
3496b99197 HAL minor simplification 2022-06-14 17:50:57 +01:00
Asbelos
6b7c2ccdf0 I2C address checks cleaned up 2022-06-14 17:35:29 +01:00
Asbelos
08eaa8ddb7 I2C overlap checks (working but messy)
Needs disgnostic clean and promotion of i2c address to IODevice.
2022-06-14 17:21:11 +01:00
Asbelos
10a0cfcccb change halSetup order 2022-06-14 15:28:13 +01:00
Asbelos
ef937dcacf Privatize HAL constructors
Forces caller to go via create function which includes overlap checks before class is instantiated.
2022-06-14 15:23:27 +01:00
Asbelos
e11d2d08d1 HAL catch pin overlaps 2022-06-14 15:15:42 +01:00
Harald Barth
9a98d10a86 rename fakePORT* to shadowPORT* 2022-06-13 23:18:10 +02:00
Harald Barth
4833eaac65 move all arch dependent port register declarations to MotorDriver.h 2022-06-13 23:15:35 +02:00
Harald Barth
744713769c fix merge error 2022-06-11 22:23:33 +02:00
Harald Barth
7fba96417f Merge branch 'TM_nofloat' into TM-mergetest 2022-06-11 22:14:56 +02:00
Harald Barth
5eb9678437 Merge branch 'master' into TrackManager-PORTX 2022-06-11 21:40:08 +02:00
Harald Barth
2385d0809c copyright adjustments 2022-06-11 20:38:44 +02:00
Harald Barth
808aa9aba9 show high accuracy status with track mode 2022-06-11 20:35:52 +02:00
Harald Barth
4297ed5572 remove global usePWM flag 2022-06-11 20:09:21 +02:00
Harald Barth
40dfda47c7 make usePWM per track (trackPWM) 2nd half 2022-06-11 19:57:45 +02:00
Asbelos
16fafccf15 Command Distributor
Improve and split multi-language responses. Remove dependency on RingStream.
2022-06-10 12:22:28 +01:00
Harald Barth
62e471606d remember the base case 2022-06-07 00:18:48 +02:00
Harald Barth
0ab96d28c4 make compile on Uno 2022-06-06 23:45:28 +02:00
Harald Barth
d37e303bdc tag version 2022-06-06 23:34:40 +02:00
Harald Barth
b24f6b27c6 protect setSignal() changes in setDCSignal from being changed back during interrupt vis setDCCSignal 2022-06-06 23:14:35 +02:00
Asbelos
2b2012ef1d float memory saver 2022-06-06 17:37:23 +01:00
Harald Barth
016bc37b53 clean up getThrottleSpeed functions 2022-06-05 23:07:03 +02:00
pmantoine
712ed0674d Merge branch 'SAMD_Integration_PMA' of https://github.com/DCC-EX/CommandStation-EX into SAMD_Integration_PMA 2022-06-03 17:15:52 +08:00
pmantoine
1d36b03e7a Various SAMC/SAMD defs 2022-06-03 17:15:46 +08:00
pmantoine
c2d7e7169a Starting I2C Native Driver 2022-06-03 17:04:32 +08:00
Harald Barth
06e7ad5c53 prevent usage of pins for DC than can not do PWM 2022-05-25 09:28:28 +02:00
Harald Barth
1c78792dda Revert "test to remove port code"
This reverts commit cfcd61174d.
2022-05-25 07:41:47 +02:00
Harald Barth
cfcd61174d test to remove port code 2022-05-24 23:33:46 +02:00
Harald Barth
55561188e1 reset speed and loosen brake 2022-05-24 08:07:33 +02:00
Asbelos
5f568c05b9 Merge branch '240-command-shows-flags-without-signal-aspect-information' into TrackManager 2022-05-23 14:35:57 +01:00
Harald Barth
55196c2e7d tag 2022-05-23 00:04:35 +02:00
Harald Barth
c7b38170c1 Parse ONOFF with 4 param <a> command 2022-05-22 23:39:46 +02:00
Harald Barth
d3b72dc4fc Send onoff packets from setAccessory 2022-05-22 23:05:09 +02:00
Harald Barth
17fb921678 Explain better and change naming but same functionality 2022-05-22 22:43:06 +02:00
Harald Barth
867e3b3930 Reset track signal when leaving PWM and use port registers in DC mode as well 2022-05-21 10:19:25 +02:00
Harald Barth
79ef114c0d protect from setting unused pin 2022-05-21 09:51:38 +02:00
Harald Barth
6d2a9e3b36 add EXT as a new track mode 2022-05-18 09:40:53 +02:00
pmantoine
dd58e2c462 Fix ESP32 define 2022-05-17 20:04:19 +08:00
pmantoine
6135272c32 SAMD Support Initial Patches 2022-05-17 18:06:08 +08:00
Kcsmith0708
a1a2c9ce5b Update version.h (#223)
Rewrite & Updated the 4.0.0 Section
2022-05-17 09:03:40 +08:00
Asbelos
3c01bd9012 Cleanup version.h 2022-05-17 09:03:36 +08:00
Asbelos
d2fa44eec7 EXRAIL VIRTUAL_TURNOUT 2022-05-16 11:06:41 +08:00
Harald Barth
f8a19de9fb tag it 2022-05-13 01:22:00 +02:00
Harald Barth
f0e8419fea tag it 2022-05-13 01:16:40 +02:00
Harald Barth
8f9da49cc8 Merge branch 'TrackManager' into TrackManager-PORTX 2022-05-13 00:21:02 +02:00
Harald Barth
d7a17b10b4 use ugly macros to make PORTX code portable 2022-05-13 00:05:25 +02:00
Harald Barth
0268304d41 fix type warning 2022-05-12 21:09:43 +02:00
Harald Barth
f66f5785f5 reorder statements in addTrack 2022-05-12 20:59:31 +02:00
Harald Barth
6d382fa0f4 TackManager: Make OFF=zero and when DCC then HA must be off 2022-05-12 20:56:23 +02:00
Harald Barth
af0d381e45 shadow PORTX (PoC) 2022-05-10 23:42:21 +02:00
Harald Barth
4a56998553 inline setSignal ; bugfix HA switching code by doing clearPWM 2022-05-10 23:37:24 +02:00
pmantoine
cb365579d8 Minor edits. 2022-05-05 21:20:49 +08:00
Harald Barth
ac32cd5528 guess value should be 0 not random bute in RAM 2022-05-03 08:38:35 +02:00
Ash-4
e721457844
Updated CV read command <R cv>
Updated CV read command <R cv>
2022-05-02 19:14:04 -05:00
Harald Barth
3e8649f9a1 zero transmit not pending repeats when ack is found 2022-05-02 22:15:02 +02:00
Ash-4
6994139e57
Merge pull request #230 from DCC-EX/ServoSignal
WRITE CV ON PROG <W CV VALUE>
2022-04-30 22:33:54 -05:00
Harald Barth
7a2fd90bfc set the reset packet counter of the prog track to 0 every time the track is turned on 2022-04-30 23:24:51 +02:00
Ash-4
43bac3f78e
ACK defaults now 50-2000-20000 2022-04-29 19:24:56 -05:00
Asbelos
cd0b8790b6 Merge branch 'ServoSignal' into TrackManager 2022-04-29 23:07:04 +01:00
Asbelos
228553013b Merge branch 'ServoSignal' into TrackManager 2022-04-29 20:53:48 +01:00
Asbelos
acd6e7560f Merge branch 'ServoSignal' into TrackManager 2022-04-29 17:09:13 +01:00
Asbelos
5bdbe3895d spelling 2022-04-29 14:55:50 +01:00
Asbelos
bcd1335b08 Merge branch 'ServoSignal' into TrackManager 2022-04-29 13:58:26 +01:00
Asbelos
724dea22d5 Merge branch 'ServoSignal' into TrackManager 2022-04-20 09:10:44 +01:00
Asbelos
21d1f482cf Merge branch 'ServoSignal' into TrackManager 2022-04-19 11:35:17 +01:00
Asbelos
9273265036 Merge branch 'ServoSignal' into TrackManager 2022-04-18 16:59:02 +01:00
Asbelos
8522e05b13 merge issue with prog split 2022-04-12 23:28:21 +01:00
Asbelos
1b0d700009 Merge branch 'ThrottleInfo' into TrackManager 2022-04-12 23:16:41 +01:00
pmantoine
8fa1ba3039 SAMD21 DCC waveform working 2022-04-12 14:32:10 +08:00
pmantoine
a52551babe SAMD timer code 2022-04-08 12:35:19 +08:00
pmantoine
084ddf01e1 More SAMD timer setup 2022-04-08 10:59:30 +08:00
pmantoine
083c5b5cd3 Merge branch 'SAMD_Integration_PMA' of https://github.com/DCC-EX/CommandStation-EX into SAMD_Integration_PMA 2022-04-07 16:55:36 +08:00
pmantoine
63c9ca414d Initial timer setup code 2022-04-07 16:55:33 +08:00
pmantoine
46e4dc2628 Motor driver senseFactor and 10bit ADC 2022-04-07 16:53:50 +08:00
pmantoine
5ccef35074 ADC config 2022-04-05 12:53:11 +08:00
pmantoine
7312951b2b Platformio.ini to test workflow 2022-04-05 09:38:33 +08:00
pmantoine
5dfc014f49 Some useful code plug debug goo 2022-04-05 09:24:29 +08:00
pmantoine
b847419a55 Merge branch 'SAMD_Integration_PMA' of https://github.com/DCC-EX/CommandStation-EX into SAMD_Integration_PMA 2022-04-01 21:29:33 +08:00
pmantoine
cd0dfc565c Initial SAMD defines 2022-04-01 21:29:15 +08:00
pmantoine
f878c1d01c Initial SAMD defines 2022-04-01 21:28:21 +08:00
Asbelos
aaa3e7a83c Merge branch 'ServoSignal' into TrackManager 2022-03-31 22:20:33 +01:00
Asbelos
ece342f037 DC power fix 2022-03-31 22:19:13 +01:00
Asbelos
f9e36e6693 Merge branch 'ServoSignal' into TrackManager 2022-03-31 10:13:53 +01:00
Asbelos
7dd680ccd5 Brake fix 2022-03-31 10:03:27 +01:00
Fred
ef50665c16
Update TrackManager.md
Add note section about zero stretching
2022-03-30 09:28:52 -04:00
Asbelos
3f283620d3 Include default brake pins for standard shield. 2022-03-28 15:21:07 +01:00
Asbelos
5adabcd1af Merge branch 'ServoSignal' into TrackManager 2022-03-28 14:56:27 +01:00
Asbelos
2727332be3 Update version.h 2022-03-28 14:53:16 +01:00
Asbelos
49e0a0e5f5 DC track change fixes 2022-03-28 14:44:41 +01:00
Kcsmith0708
78810d0e36
Update version.h (#220)
* Update version.h

Updated 4.0.2 release features
cleaned up 4.0.0 feature list

* Update version.h

Added and Updated 4.0.0 Features

* Update version.h

modified 4.0.0 new JMRI features

* Update version.h

Updated4.0.0 release information
2022-03-24 11:35:00 -04:00
Asbelos
a10fca2b12 TM Power setting fixes 2022-03-23 17:06:15 +00:00
Asbelos
99c7ff6c3f EXPERIMENTAL vpin for motorDriver power
No need to use fastpin for power, so we can allow a remoted vpin which helps when TrafficManger is running short of pins
2022-03-23 12:30:21 +00:00
Asbelos
2a87a6e997 Created TrackManager.md 2022-03-23 12:09:41 +00:00
Asbelos
dea55fec79 Merge branch 'ServoSignal' into TrackManager 2022-03-21 16:32:45 +00:00
Asbelos
865c8dd3bd DCX Track mode
And unified SET_TRACK Exrail macro
2022-03-19 16:26:29 +00:00
Asbelos
be186b967b CODE TIDY
Moved join code out of DCCWaveform to reduce footprint for ESP32 waveform replacement.
2022-03-19 11:22:31 +00:00
Asbelos
4f2dc0934f prevent DC addr 0 2022-03-18 20:03:19 +00:00
Asbelos
75b16c9047 Change track manager cmd to =
And fix the wrong param number at the same time!
2022-03-18 16:41:52 +00:00
Asbelos
cd952c6ede Merge branch 'ServoSignal' into TrackManager 2022-03-18 13:47:07 +00:00
Asbelos
7e3dcb8e8c Correct merge issues from master 2022-03-07 18:46:29 +00:00
Asbelos
4437f870b6 Merge branch 'master' into TrackManager 2022-03-07 18:40:32 +00:00
Harald Barth
f7e2c0ca99 add ESP defines 2022-03-04 23:37:27 +01:00
Harald Barth
2890a7928b restart with AVR WDT or ESP.restart() 2022-03-04 23:36:31 +01:00
Harald Barth
03372f21e2 rename FAIL to CALLFAIL because of conflict in ESP32 IDE 2022-03-04 23:35:14 +01:00
Asbelos
678cccbd95 Add DC signal generation code. 2022-03-02 15:34:01 +00:00
Asbelos
524afc6caf move more cpu specifics 2022-03-02 14:24:49 +00:00
Asbelos
6fc223d80b Timer stuff with incomplete teensy 2022-03-01 12:52:25 +00:00
Asbelos
dd9152864b Missing DCC startup!!! + EXRAIL POWERON catchup 2022-02-28 10:38:26 +00:00
Asbelos
4f781074eb tidy and shorten loops 2022-02-28 09:32:26 +00:00
Asbelos
b29b8c999e keyword values 2022-02-24 11:58:40 +00:00
Asbelos
99e636974a SET_TRACK_DC/SET_TRACK_DCC
EXRAIL macros for TrackManager
SET_TRACK_DC sets the track to DC using the cab address of the current loco so you can drive..
2022-02-24 11:50:22 +00:00
Asbelos
74bbe595fc DC hooks
still requires pin jiggling
2022-02-23 16:21:45 +00:00
Asbelos
1afb4753ec Cleaner ack check
And drop CPU specific stuff no longer needed.
2022-02-23 16:03:15 +00:00
Asbelos
a7740d652d It builds....
massive track reorganization
2022-02-23 15:44:34 +00:00
Asbelos
8db937e985 Initial Track Manager code 2022-02-22 01:27:27 +00:00
Harald Barth
c711be7980 DCCTrack::schedulePacket allows multiple different motordrivers side by side 2021-11-22 23:26:04 +01:00
Harald Barth
ed2aa4c1d8 remove virtual 2021-11-22 04:01:48 +01:00
Harald Barth
82df3a21dc Rename RMTPin to RMTChannel 2021-11-22 03:55:00 +01:00
Harald Barth
c00d3a825d Shield RMT stuff with ifdef ESP32 2021-11-22 03:24:15 +01:00
Harald Barth
35ee03537d version 2021-11-21 22:56:14 +01:00
Harald Barth
f7e90e7b73 MotorDriverContainer (multi-motordriver) start 2021-11-21 22:53:17 +01:00
Harald Barth
2632d44ec9 remove packetPendingRMT from wrong if 2021-11-21 21:28:56 +01:00
Harald Barth
c8e5123c0a fix compile errors on ESP32 2021-11-21 00:51:59 +01:00
Harald Barth
e7e26551ce Merge branch 'master' into ESP32 2021-11-20 23:38:12 +01:00
Harald Barth
50b854c526 remove extra zero bit 2021-11-19 00:34:56 +01:00
Harald Barth
55a789d65a set RMT clock to microseconds 2021-11-19 00:03:21 +01:00
Harald Barth
a69b7ee113 change to RMT loop mode 2021-11-18 23:57:53 +01:00
Harald Barth
114686d124 cleanup comments 2021-11-15 23:10:23 +01:00
Harald Barth
005ddef665 Transmit DCC packet to loco 2021-11-15 22:28:30 +01:00
Harald Barth
10209ed6f3 remove uneccessary workaround, compensate for interrupt length 2021-11-14 15:35:26 +01:00
Harald Barth
71117bc7a1 special version 2021-11-14 14:49:55 +01:00
Harald Barth
97065e892d transmit preamble and idle 2021-11-14 14:48:32 +01:00
Harald Barth
4668e116f4 preambles running 2021-11-14 13:10:16 +01:00
Harald Barth
5cbf0c2cad defines.h needed to get ESP32 macro on non-ESP32 2021-11-07 00:21:15 +01:00
Harald Barth
c02e976c9f protect ringstream typo fix 2021-11-07 00:12:11 +01:00
Harald Barth
55c7a0a1e8 protect ringstream 2021-11-06 23:51:32 +01:00
Harald Barth
d7e46ac625 set version 2021-11-06 03:04:50 +01:00
Harald Barth
877db433a4 make task startup nicer 2021-11-06 02:59:57 +01:00
Harald Barth
4901f12fcd make own task on core0 for WifiESP::loop() on ESP32 2021-11-06 02:40:49 +01:00
Harald Barth
836ccc143e check power overload only when not ack check 2021-11-03 09:45:30 +01:00
Harald Barth
77ee57eb83 give up eventually 2021-11-02 17:50:32 +01:00
Harald Barth
837b0a9fb6 typo 2021-10-31 23:46:25 +01:00
Harald Barth
a109ba4e01 unknown locos should have speed forward 2021-10-31 23:35:28 +01:00
Harald Barth
c87a80928b special tag 2021-10-31 22:06:22 +01:00
Harald Barth
c5b283bd8c should compile for all boards 2021-10-31 01:10:13 +02:00
Harald Barth
500fe2f717 more diag messages 2021-10-31 00:40:35 +02:00
Harald Barth
278f7618f4 do something i AP mode 2021-10-31 00:10:58 +02:00
Harald Barth
9d74b0f6a5 set pinMode analog 2021-10-29 22:19:23 +02:00
Harald Barth
31059a615c use ESP-IDF ADC functions instead of analogRead() which breaks waveform 2021-10-27 23:03:37 +02:00
Harald Barth
7d7b337f82 on ESP32 currently WIFI should be on 2021-10-24 19:38:07 +02:00
Harald Barth
05eb0d763a explain ESP32 watchdog 2021-10-24 12:59:28 +02:00
Harald Barth
b6cfc39d23 ESP32 watchdog workaround (with diag code) 2021-10-24 12:09:54 +02:00
Harald Barth
8a0ddb0d74 ESP32 I/O info 2021-10-22 08:35:29 +02:00
Harald Barth
faeb3194db ESP32 motorshield as default 2021-10-22 08:21:44 +02:00
Harald Barth
26bd3ac342 Example ESP motor shields 2021-10-05 21:55:13 +02:00
Harald Barth
d174c05127 Wifi connect and waveform 2021-10-05 21:53:02 +02:00
Harald Barth
75dffd9dfa first ESP32 compile 2021-10-05 10:39:08 +02:00
Harald Barth
0a10dbea0b not forget volatile 2021-10-04 23:12:47 +02:00
Harald Barth
43191e225e first stab at ESP32 2021-10-04 23:03:36 +02:00
Harald Barth
50bb1c950b less warnings 2021-10-03 19:58:05 +02:00
Harald Barth
0bb6b577fa Wifi STA or AP mode 2021-10-01 11:32:09 +02:00
Harald Barth
cf0c818138 Cleanup ESP specific details 2021-10-01 09:09:30 +02:00
Harald Barth
426b27f0dd Reworked use of ringbuffer 2021-09-30 22:55:14 +02:00
Harald Barth
19b4893b5f counter should be int, not uint8_t 2021-09-28 21:08:41 +02:00
Harald Barth
1c7a5320d8 more send diag 2021-09-28 17:31:12 +02:00
Harald Barth
afd4626988 send diag 2021-09-28 17:20:44 +02:00
Harald Barth
a194b8965c Ack read outside interrupt 2021-09-27 20:01:46 +02:00
Harald Barth
696d12fc5e test A0 2021-09-26 11:57:15 +02:00
Harald Barth
35cba02ee7 outboundRing uses sendData 2021-09-26 10:59:07 +02:00
Harald Barth
fa1d1619b6 wifi sendData 2021-09-26 08:37:59 +02:00
Harald Barth
b048879eaa Wifi active 2021-09-25 23:18:10 +02:00
Harald Barth
34474cbf5c WifiESP skeleton files 2021-09-21 09:23:52 +02:00
Harald Barth
7397a4089b first waveform on esp 2021-09-21 00:31:05 +02:00
133 changed files with 14184 additions and 4715 deletions

View File

@ -1,80 +0,0 @@
# Bug report GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Bug Report
description: Submit a bug report
labels:
- Bug
title: "Bug Report: "
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to submit a bug report to the DCC-EX team!
In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form.
- type: input
id: version
attributes:
label: Version
description: Please provide the version of EX-CommandStation in use.
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug description
description: Please provide a clear and concise description of what the symptoms of the bug are.
placeholder: |
When attempting to drive a locomotive on the main track, it runs forwards, backwards, spins around, jumps up and down, blows the horn, and then stops.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce the bug
description: Please provide the steps to reproduce the behaviour.
placeholder: |
1. Turn on the CommandStation and track power.
2. Connect Engine Driver to the CommandStation.
3. Select locomotive with address 123.
4. Throttle up to half speed.
validations:
required: true
- type: textarea
id: expectation
attributes:
label: Expected behaviour
description: Please provide a clear and concise description of what you expected to happen.
placeholder: |
The locomotive should accelerate smoothly to half speed in a forward direction.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, upload any screenshots here.
- type: textarea
id: hardware
attributes:
label: Hardware in use
description: Please provide details of hardware in use including microcontroller, motor shield, and any other relevant information.
placeholder: |
Elegoo Mega2560
Arduino R3 motor shield
validations:
required: true
- type: textarea
id: extra-context
attributes:
label: Additional context
description: Please provide any other relevant information that could help us resolve this issue, for example a customised config.h file.

View File

@ -1,12 +0,0 @@
# Configuration file for the template chooser
#
# This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder.
blank_issues_enabled: false
contact_links:
- name: DCC-EX Discord server
url: https://discord.gg/y2sB4Fp
about: For the best support experience, join our Discord server
- name: DCC-EX Contact and Support page
url: https://dcc-ex.com/support/index.html
about: For other support options, refer to our Contact & Support page

View File

@ -1,31 +0,0 @@
# Documentation update GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Documentation Update
description: Submit a request for documentation updates, or to report broken links or inaccuracies
title: "[Documentation Update]: "
labels:
- Needs Documentation
body:
- type: markdown
attributes:
value: |
Use this template to submit a request for updates to our documentation.
This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information.
- type: textarea
id: details
attributes:
label: Documentation details
description: Provide the details of what needs to be documented or corrected.
validations:
required: true
- type: input
id: page
attributes:
label: Page with issues
description: If reporting broken links or inaccuracies, please provide the link to the page here.
placeholder: https://dcc-ex.com/index.html

View File

@ -1,37 +0,0 @@
# Feature Request GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Feature Request
description: Suggest a new feature
title: "[Feature Request]: "
labels:
- Enhancement
body:
- type: markdown
attributes:
value: |
Use this template to suggest a new feature for EX-CommandStation.
- type: textarea
id: description
attributes:
label: Problem/idea statement
description: Please provide the problem you're trying to solve, or share the idea you have.
placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. For example, I'm always frustrated when...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives or workarounds
description: Please provide any alternatives or workarounds you currently use.
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context, screenshots, or files related to the feature request here.

View File

@ -1,39 +0,0 @@
# Support Request GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Support Request
description: Request support or assistance
title: "[Support Request]: "
labels:
- Support Request
body:
- type: markdown
attributes:
value: |
Use this template to request support or assistance with EX-CommandStation.
- type: input
id: version
attributes:
label: Version
description: Please provide the version of the software in use.
validations:
required: true
- type: textarea
id: description
attributes:
label: Issue description
description: Please describe the issue being encountered as accurately and detailed as possible.
validations:
required: true
- type: textarea
id: hardware
attributes:
label: Hardware
description: If appropriate, please provide details of the hardware in use.
placeholder: |
Elegoo Mega2560
Arduino Motor Shield R3

View File

@ -1,24 +0,0 @@
# General To Do item GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: To Do
description: Create a general To Do item
title: "[To Do]: "
labels:
- To Do
body:
- type: markdown
attributes:
value: |
Use this template to create an issue for a general task that needs to be done.
This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated.
- type: textarea
id: description
attributes:
label: Task description
description: Provide the details of what needs to be done.
validations:
required: true

View File

@ -1,156 +0,0 @@
# This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project.
# It will add all issues and pull requests for a repository to the project, and put in the correct status.
#
# Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work.
name: Add Issue or Pull Request to Project
env:
REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }}
PROJECT_NUMBER: 7
ORG: DCC-EX
on:
issues:
types:
- opened
pull_request_target:
types:
- ready_for_review
- opened
- review_requested
jobs:
add_to_project:
runs-on: ubuntu-latest
steps:
- name: Add labels
uses: andymckay/labeler@master
with:
add-labels: ${{ env.REPO_LABEL }}
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
with:
app_id: ${{ secrets.PROJECT_APP_ID }}
private_key: ${{ secrets. PROJECT_APP_KEY }}
- name: Get project data
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
gh api graphql -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectV2(number: $number) {
id
fields(first:20) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV
echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV
echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV
echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ITEM_ID: ${{ github.event.issue.node_id }}
if: github.event_name == 'issues'
run: |
project_item_id="$( gh api graphql -f query='
mutation($project:ID!, $item:ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
- name: Add PR to project
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ITEM_ID: ${{ github.event.pull_request.node_id }}
if: github.event_name == 'pull_request'
run: |
project_item_id="$( gh api graphql -f query='
mutation($project:ID!, $item:ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
- name: Set status - To Do
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request'))
run: |
gh api graphql -f query='
mutation(
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
){
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: {
singleSelectOptionId: $status_value
}
}) {
projectV2Item {
id
}
}
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent
- name: Set status - Review
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request'
run: |
gh api graphql -f query='
mutation(
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
){
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: {
singleSelectOptionId: $status_value
}
}) {
projectV2Item {
id
}
}
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent

4
.gitignore vendored
View File

@ -7,7 +7,9 @@ Release/*
.pio/ .pio/
.vscode/ .vscode/
config.h config.h
my*.cpp mySetup.cpp
myHal.cpp
myFilter.cpp
my*.h my*.h
!my*.example.h !my*.example.h
compile_commands.json compile_commands.json

View File

@ -1,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

12
.vscode/settings.json vendored
View File

@ -1,12 +0,0 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"cstdint": "cpp"
}
}

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
@ -28,100 +29,224 @@
#include "defines.h" #include "defines.h"
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "DCC.h" #include "DCC.h"
#include "TrackManager.h"
#include "StringFormatter.h"
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON) // variables to hold clock time
// This section of CommandDistributor is simply not relevant on a uno or similar int16_t lastclocktime;
const byte NO_CLIENT=255; int8_t lastclockrate;
RingStream * CommandDistributor::ring=0;
byte CommandDistributor::ringClient=NO_CLIENT; #if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
CommandDistributor::clientType CommandDistributor::clients[8]={ // use a buffer to allow broadcast
StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer();
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
broadcastBufferWriter->flush();
StringFormatter::send(broadcastBufferWriter, msg...);
broadcastToClients(type);
}
#else
// on a single USB connection config, write direct to Serial and ignore flush/shove
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
(void)type; //shut up compiler warning
StringFormatter::send(&USB_SERIAL, msg...);
}
#endif
#ifdef CD_HANDLE_RING
// wifi or ethernet ring streams with multiple client types
RingStream * CommandDistributor::ring=0;
CommandDistributor::clientType CommandDistributor::clients[8]={
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
// Parse is called by Withrottle or Ethernet interface to determine which
// protocol the client is using and call the appropriate part of dcc++Ex
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
if (Diag::WIFI && Diag::CMD)
DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer);
ring=stream; ring=stream;
ringClient=stream->peekTargetMark();
if (buffer[0] == '<') { // First check if the client is not known
// yet and in that case determinine type
// NOTE: First character of transmission determines if this
// client is using the DCC++ protocol where all commands start
// with '<'
if (clients[clientId] == NONE_TYPE) {
if (buffer[0] == '<')
clients[clientId]=COMMAND_TYPE; clients[clientId]=COMMAND_TYPE;
DCCEXParser::parse(stream, buffer, ring); else
} else {
clients[clientId]=WITHROTTLE_TYPE; clients[clientId]=WITHROTTLE_TYPE;
}
// mark buffer that is sent to parser
ring->mark(clientId);
// When type is known, send the string
// to the right parser
if (clients[clientId] == COMMAND_TYPE) {
DCCEXParser::parse(stream, buffer, ring);
} else if (clients[clientId] == WITHROTTLE_TYPE) {
WiThrottle::getThrottle(clientId)->parse(ring, buffer); WiThrottle::getThrottle(clientId)->parse(ring, buffer);
} }
ringClient=NO_CLIENT;
if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
// The commit call will either write the length bytes
// OR rollback to the mark because the reply is empty
// or the command generated more output than fits in
// the buffer
if (!ring->commit()) {
DIAG(F("OUTBOUND FULL processing cmd:%s"),buffer);
}
} else {
DIAG(F("CD parse: was alredy committed")); //XXX Could have been committed by broadcastClient?!
}
} }
void CommandDistributor::forget(byte clientId) { void CommandDistributor::forget(byte clientId) {
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE; clients[clientId]=NONE_TYPE;
} }
#endif
// This will not be called on a uno
void CommandDistributor::broadcastToClients(clientType type) {
void CommandDistributor::broadcast(bool includeWithrottleClients) { byte rememberClient;
broadcastBufferWriter->write((byte)'\0'); (void)rememberClient; // shut up compiler warning
/* Boadcast to Serials */ // Broadcast to Serials
SerialManager::broadcast(broadcastBufferWriter); if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString());
#if defined(WIFI_ON) | defined(ETHERNET_ON) #ifdef CD_HANDLE_RING
// If we are broadcasting from a wifi/eth process we need to complete its output // If we are broadcasting from a wifi/eth process we need to complete its output
// before merging broadcasts in the ring, then reinstate it in case // before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client. // the process continues to output to its client.
if (ringClient!=NO_CLIENT) ring->commit(); if (ring) {
if ((rememberClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
/* loop through ring clients */ //DIAG(F("CD precommit client %d"), rememberClient);
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
if (clients[clientId]==NONE_TYPE) continue;
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
ring->mark(clientId);
broadcastBufferWriter->printBuffer(ring);
ring->commit(); ring->commit();
} }
if (ringClient!=NO_CLIENT) ring->mark(ringClient); // loop through ring clients
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
if (clients[clientId]==type) {
//DIAG(F("CD mark client %d"), clientId);
ring->mark(clientId);
ring->print(broadcastBufferWriter->getString());
//DIAG(F("CD commit client %d"), clientId);
ring->commit();
}
}
// at this point ring is committed (NO_CLIENT) either from
// 4 or 13 lines above.
if (rememberClient != RingStream::NO_CLIENT) {
//DIAG(F("CD postmark client %d"), rememberClient);
ring->mark(rememberClient);
}
}
#endif #endif
broadcastBufferWriter->flush();
} }
#else
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
// Redirect ring output ditrect to Serial
#define broadcastBufferWriter &Serial
// and ignore the internal broadcast call.
void CommandDistributor::broadcast(bool includeWithrottleClients) {
(void)includeWithrottleClients;
}
#endif
// Public broadcast functions below
void CommandDistributor::broadcastSensor(int16_t id, bool on ) { void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id); broadcastReply(COMMAND_TYPE, F("<%c %d>\n"), on?'Q':'q', id);
broadcast(false);
} }
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
// The string below contains serial and Withrottle protocols which should // The string below contains serial and Withrottle protocols which should
// be safe for both types. // be safe for both types.
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed); broadcastReply(COMMAND_TYPE, F("<H %d %d>\n"),id, !isClosed);
#if defined(WIFI_ON) | defined(ETHERNET_ON) #ifdef CD_HANDLE_RING
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id); broadcastReply(WITHROTTLE_TYPE, F("PTA%c%d\n"), isClosed?'2':'4', id);
#endif #endif
broadcast(true); }
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// The JMRI clock command is of the form : PFT65871<;>4
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate);
#endif
}
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
// opt - case 1 save the latest time if changed
// case 2 broadcast the time when requested
// case 3 display latest time
switch (opt)
{
case 1:
if (clocktime != lastclocktime){
// CAH. DIAG removed because LCD does it anyway.
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time
RMFT2::clockEvent(clocktime,1);
// Now tell everyone else what the time is.
CommandDistributor::broadcastClockTime(clocktime, clockrate);
lastclocktime = clocktime;
lastclockrate = clockrate;
}
return;
case 2:
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
return;
}
}
int16_t CommandDistributor::retClockTime() {
return lastclocktime;
} }
void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot]; DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"), broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
sp->loco,slot,sp->speedCode,sp->functions); #ifdef SABERTOOTH
broadcast(false); if (Serial2 && sp->loco == SABERTOOTH) {
#if defined(WIFI_ON) | defined(ETHERNET_ON) 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
WiThrottle::markForBroadcast(sp->loco); WiThrottle::markForBroadcast(sp->loco);
#endif #endif
} }
void CommandDistributor::broadcastPower() { void CommandDistributor::broadcastPower() {
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON; bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON; bool prog=TrackManager::getProgPower()==POWERMODE::ON;
bool join=DCCWaveform::progTrackSyncMain; bool join=TrackManager::isJoined();
const FSH * reason=F(""); const FSH * reason=F("");
char state='1'; char state='1';
if (main && prog && join) reason=F(" JOIN"); if (main && prog && join) reason=F(" JOIN");
@ -129,14 +254,17 @@ void CommandDistributor::broadcastPower() {
else if (main) reason=F(" MAIN"); else if (main) reason=F(" MAIN");
else if (prog) reason=F(" PROG"); else if (prog) reason=F(" PROG");
else state='0'; else state='0';
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason);
StringFormatter::send(broadcastBufferWriter, #ifdef CD_HANDLE_RING
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0'); broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0');
#endif
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);
broadcast(true);
} }
void CommandDistributor::broadcastText(const FSH * msg) { void CommandDistributor::broadcastRaw(clientType type, char * msg) {
StringFormatter::send(broadcastBufferWriter,F("%S"),msg); broadcastReply(type, F("%s"),msg);
broadcast(false); }
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format,trackLetter,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
@ -23,26 +25,39 @@
#define CommandDistributor_h #define CommandDistributor_h
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "RingStream.h" #include "RingStream.h"
#include "StringBuffer.h"
#include "defines.h"
#include "EXRAIL2.h"
#if WIFI_ON | ETHERNET_ON
// Command Distributor must handle a RingStream of clients
#define CD_HANDLE_RING
#endif
class CommandDistributor { class CommandDistributor {
public:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
private:
static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter;
#ifdef CD_HANDLE_RING
static RingStream * ring;
static clientType clients[8];
#endif
public : public :
static void parse(byte clientId,byte* buffer, RingStream * ring); static void parse(byte clientId,byte* buffer, RingStream * ring);
static void broadcastLoco(byte slot); static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value); static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed); static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastClockTime(int16_t time, int8_t rate);
static void setClockTime(int16_t time, int8_t rate, byte opt);
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);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId); static void forget(byte clientId);
private:
static void broadcast(bool includeWithrottleClients);
static RingStream * ring;
static RingStream * broadcastBufferWriter;
static byte ringClient;
// each bit in broadcastlist = 1<<clientid
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
static clientType clients[8];
}; };
#endif #endif

View File

@ -18,16 +18,19 @@
#if __has_include ( "config.h") #if __has_include ( "config.h")
#include "config.h" #include "config.h"
#ifndef MOTOR_SHIELD_TYPE
#error Your config.h must include a MOTOR_SHIELD_TYPE definition. If you see this warning in spite not having a config.h, you have a buggy preprocessor and must copy config.example.h to config.h
#endif
#else #else
#warning config.h not found. Using defaults from config.example.h #warning config.h not found. Using defaults from config.example.h
#include "config.example.h" #include "config.example.h"
#endif #endif
/* /*
* © 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
@ -47,6 +50,12 @@
*/ */
#include "DCCEX.h" #include "DCCEX.h"
#include "Display_Implementation.h"
#ifdef CPU_TYPE_ERROR
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
#endif
#ifdef WIFI_WARNING #ifdef WIFI_WARNING
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED #warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
#endif #endif
@ -67,29 +76,39 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
CONDITIONAL_LCD_START { // Initialise HAL layer before reading EEprom or setting up MotorDrivers
// This block is still executed for DIAGS if LCD not in use IODevice::begin();
LCD(0,F("DCC++ EX v%S"),F(VERSION));
// 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 (
// This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3")); LCD(1,F("Lic GPLv3"));
} );
// Responsibility 2: Start all the communications before the DCC engine // Responsibility 2: Start all the communications before the DCC engine
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
// Start Ethernet if it exists // Start Ethernet if it exists
#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
// ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
EthernetInterface::setup(); EthernetInterface::setup();
#endif // ETHERNET_ON #endif // ETHERNET_ON
// 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
DCC::begin(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();
@ -107,8 +126,7 @@ void setup()
LCN_SERIAL.begin(115200); LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL); LCN::init(LCN_SERIAL);
#endif #endif
LCD(3, F("Ready"));
LCD(3,F("Ready"));
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
} }
@ -124,9 +142,15 @@ void loop()
SerialManager::loop(); SerialManager::loop();
// Responsibility 3: Optionally handle any incoming WiFi traffic // Responsibility 3: Optionally handle any incoming WiFi traffic
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON #if WIFI_ON
WifiInterface::loop(); WifiInterface::loop();
#endif //WIFI_ON
#else //ARDUINO_ARCH_ESP32
#ifndef WIFI_TASK_ON_CORE0
WifiESP::loop();
#endif #endif
#endif //ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
EthernetInterface::loop(); EthernetInterface::loop();
#endif #endif
@ -137,7 +161,8 @@ void loop()
LCN::loop(); LCN::loop();
#endif #endif
LCDDisplay::loop(); // ignored if LCD not in use // Display refresh
DisplayInterface::loop();
// Handle/update IO devices. // Handle/update IO devices.
IODevice::loop(); IODevice::loop();
@ -147,7 +172,7 @@ void loop()
// Report any decrease in memory (will automatically trigger on first call) // Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = minimumFreeMemory(); int freeNow = DCCTimer::getMinimumFreeMemory();
if (freeNow < ramLowWatermark) { if (freeNow < ramLowWatermark) {
ramLowWatermark = freeNow; ramLowWatermark = freeNow;
LCD(3,F("Free RAM=%5db"), ramLowWatermark); LCD(3,F("Free RAM=%5db"), ramLowWatermark);

500
DCC.cpp
View File

@ -8,7 +8,7 @@
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* All rights reserved. * All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of DCC-EX
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -35,6 +35,8 @@
#include "IODevice.h" #include "IODevice.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "TrackManager.h"
#include "DCCTimer.h"
// This module is responsible for converting API calls into // This module is responsible for converting API calls into
// messages to be sent to the waveform generator. // messages to be sent to the waveform generator.
@ -56,36 +58,25 @@ const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10; const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL; FSH* DCC::shieldName=NULL;
byte DCC::joinRelay=UNUSED_PIN;
byte DCC::globalSpeedsteps=128; byte DCC::globalSpeedsteps=128;
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { 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(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
// Initialise HAL layer before reading EEprom.
IODevice::begin();
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
// Load stuff from EEprom // Load stuff from EEprom
(void)EEPROM; // tell compiler not to warn this is unused (void)EEPROM; // tell compiler not to warn this is unused
EEStore::init(); EEStore::init();
#endif #endif
#ifndef ARDUINO_ARCH_ESP32 /* On ESP32 started in TrackManager::setTrackMode() */
DCCWaveform::begin(mainDriver,progDriver); DCCWaveform::begin();
#endif
} }
void DCC::setJoinRelayPin(byte joinRelayPin) {
joinRelay=joinRelayPin;
if (joinRelay!=UNUSED_PIN) {
pinMode(joinRelay,OUTPUT);
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
}
}
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
byte speedCode = (tSpeed & 0x7F) + tDirection * 128; byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
setThrottle2(cab, speedCode); setThrottle2(cab, speedCode);
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
// retain speed for loco reminders // retain speed for loco reminders
updateLocoReminder(cab, speedCode ); updateLocoReminder(cab, speedCode );
} }
@ -145,12 +136,25 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
DCCWaveform::mainTrack.schedulePacket(b, nB, 0); DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
} }
uint8_t DCC::getThrottleSpeed(int cab) { // returns speed steps 0 to 127 (1 == emergency stop)
// or -1 on "loco not found"
int8_t DCC::getThrottleSpeed(int cab) {
int reg=lookupSpeedTable(cab); int reg=lookupSpeedTable(cab);
if (reg<0) return -1; if (reg<0) return -1;
return speedTable[reg].speedCode & 0x7F; return speedTable[reg].speedCode & 0x7F;
} }
// returns speed code byte
// or 128 (speed 0, dir forward) on "loco not found".
uint8_t DCC::getThrottleSpeedByte(int cab) {
int reg=lookupSpeedTable(cab);
if (reg<0)
return 128;
return speedTable[reg].speedCode;
}
// returns direction on loco
// or true/forward on "loco not found"
bool DCC::getThrottleDirection(int cab) { bool DCC::getThrottleDirection(int cab) {
int reg=lookupSpeedTable(cab); int reg=lookupSpeedTable(cab);
if (reg<0) return true; if (reg<0) return true;
@ -158,8 +162,9 @@ bool DCC::getThrottleDirection(int cab) {
} }
// Set function to value on or off // Set function to value on or off
void DCC::setFn( int cab, int16_t functionNumber, bool on) { bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
if (cab<=0 ) return; if (cab<=0 ) return false;
if (functionNumber < 0) return false;
if (functionNumber>28) { if (functionNumber>28) {
//non reminding advanced binary bit set //non reminding advanced binary bit set
@ -178,11 +183,11 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
b[nB++] = functionNumber >>7 ; // high order bits b[nB++] = functionNumber >>7 ; // high order bits
} }
DCCWaveform::mainTrack.schedulePacket(b, nB, 4); DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
return; return true;
} }
int reg = lookupSpeedTable(cab); int reg = lookupSpeedTable(cab);
if (reg<0) return; if (reg<0) return false;
// Take care of functions: // Take care of functions:
// Set state of function // Set state of function
@ -197,6 +202,7 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
updateGroupflags(speedTable[reg].groupFlags, functionNumber); updateGroupflags(speedTable[reg].groupFlags, functionNumber);
CommandDistributor::broadcastLoco(reg); CommandDistributor::broadcastLoco(reg);
} }
return true;
} }
// Flip function state // Flip function state
@ -237,24 +243,39 @@ uint32_t DCC::getFunctionMap(int cab) {
return (reg<0)?0:speedTable[reg].functions; return (reg<0)?0:speedTable[reg].functions;
} }
void DCC::setAccessory(int address, byte number, bool activate) { void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
// onoff is tristate:
// 0 => send off packet
// 1 => send on packet
// >1 => send both on and off packets.
// An accessory has an address, 4 ports and 2 gates (coils) each. That's how
// the initial decoders were orgnized and that influenced how the DCC
// standard was made.
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate); DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate);
#endif #endif
// use masks to detect wrong values and do nothing // use masks to detect wrong values and do nothing
if(address != (address & 511)) if(address != (address & 511))
return; return;
if(number != (number & 3)) if(port != (port & 3))
return; return;
byte b[2]; byte b[2];
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate // second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil).
b[0] = address % 64 + 128;
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
if (onoff != 0) {
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times
#if defined(EXRAIL_ACTIVE) #if defined(EXRAIL_ACTIVE)
RMFT2::activateEvent(address<<2|number,activate); RMFT2::activateEvent(address<<2|port,gate);
#endif #endif
}
if (onoff != 1) {
b[1] &= ~0x08; // set C to 0
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times
}
} }
// //
@ -296,14 +317,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
DCCWaveform::mainTrack.schedulePacket(b, nB, 4); DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
} }
void DCC::setProgTrackSyncMain(bool on) {
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
DCCWaveform::progTrackSyncMain=on;
}
void DCC::setProgTrackBoost(bool on) {
DCCWaveform::progTrackBoosted=on;
}
FSH* DCC::getMotorShieldName() { FSH* DCC::getMotorShieldName() {
return shieldName; return shieldName;
} }
@ -313,14 +326,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
W0,WACK, W0,WACK,
V0, WACK, // validate bit is 0 V0, WACK, // validate bit is 0
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
FAIL // callback (-1) CALLFAIL // callback (-1)
}; };
const ackOp FLASH WRITE_BIT1_PROG[] = { const ackOp FLASH WRITE_BIT1_PROG[] = {
BASELINE, BASELINE,
W1,WACK, W1,WACK,
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
FAIL // callback (-1) CALLFAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BIT0_PROG[] = { const ackOp FLASH VERIFY_BIT0_PROG[] = {
@ -329,7 +342,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
ITC0, // if acked, callback(0) ITC0, // if acked, callback(0)
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, ITC1,
FAIL // callback (-1) CALLFAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BIT1_PROG[] = { const ackOp FLASH VERIFY_BIT1_PROG[] = {
BASELINE, BASELINE,
@ -337,7 +350,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
V0, WACK, V0, WACK,
ITC0, ITC0,
FAIL // callback (-1) CALLFAIL // callback (-1)
}; };
const ackOp FLASH READ_BIT_PROG[] = { const ackOp FLASH READ_BIT_PROG[] = {
@ -346,7 +359,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
V0, WACK, // validate bit is zero V0, WACK, // validate bit is zero
ITC0, // if acked callback 0 ITC0, // if acked callback 0
FAIL // bit not readable CALLFAIL // bit not readable
}; };
const ackOp FLASH WRITE_BYTE_PROG[] = { const ackOp FLASH WRITE_BYTE_PROG[] = {
@ -354,7 +367,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
WB,WACK,ITC1, // Write and callback(1) if ACK WB,WACK,ITC1, // Write and callback(1) if ACK
// handle decoders that dont ack a write // handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct VB,WACK,ITC1, // validate byte and callback(1) if correct
FAIL // callback (-1) CALLFAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BYTE_PROG[] = { const ackOp FLASH VERIFY_BYTE_PROG[] = {
@ -380,7 +393,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
FAIL }; CALLFAIL };
const ackOp FLASH READ_CV_PROG[] = { const ackOp FLASH READ_CV_PROG[] = {
@ -403,7 +416,7 @@ const ackOp FLASH READ_CV_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and return it if acked ok VB, WACK, ITCB, // verify merged byte and return it if acked ok
FAIL }; // verification failed CALLFAIL }; // verification failed
const ackOp FLASH LOCO_ID_PROG[] = { const ackOp FLASH LOCO_ID_PROG[] = {
@ -469,7 +482,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and callback VB, WACK, ITCB, // verify merged byte and callback
FAIL CALLFAIL
}; };
const ackOp FLASH SHORT_LOCO_ID_PROG[] = { const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
@ -484,9 +497,9 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
V0,WACK,NAKFAIL, V0,WACK,NAKFAIL,
SETCV, (ackOp)1, SETCV, (ackOp)1,
SETBYTEL, // low byte of word SETBYTEL, // low byte of word
WB,WACK, // some decoders don't ACK writes WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
VB,WACK,ITCB, VB,WACK,ITC1, // Some decoders do not ack and need verify
FAIL CALLFAIL
}; };
const ackOp FLASH LONG_LOCO_ID_PROG[] = { const ackOp FLASH LONG_LOCO_ID_PROG[] = {
@ -503,46 +516,50 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
// Store high byte of address in cv 17 // Store high byte of address in cv 17
SETCV, (ackOp)17, SETCV, (ackOp)17,
SETBYTEH, // high byte of word SETBYTEH, // high byte of word
WB,WACK, WB,WACK, // do write
VB,WACK,NAKFAIL, ITSKIP, // if ACK, jump to SKIPTARGET
VB,WACK, // try verify instead
ITSKIP, // if ACK, jump to SKIPTARGET
CALLFAIL, // if still here, fail
SKIPTARGET,
// store // store
SETCV, (ackOp)18, SETCV, (ackOp)18,
SETBYTEL, // low byte of word SETBYTEL, // low byte of word
WB,WACK, WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
VB,WACK,ITC1, // callback(1) means Ok VB,WACK,ITC1, // Some decoders do not ack and need verify
FAIL CALLFAIL
}; };
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback); DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback);
} }
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1); if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
} }
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback); DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback);
} }
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1); if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
} }
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) { void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1); if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback); else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback);
} }
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) { void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
ackManagerSetup(cv, 0,READ_CV_PROG, callback); DCCACK::Setup(cv, 0,READ_CV_PROG, callback);
} }
void DCC::getLocoId(ACK_CALLBACK callback) { void DCC::getLocoId(ACK_CALLBACK callback) {
ackManagerSetup(0,0, LOCO_ID_PROG, callback); DCCACK::Setup(0,0, LOCO_ID_PROG, callback);
} }
void DCC::setLocoId(int id,ACK_CALLBACK callback) { void DCC::setLocoId(int id,ACK_CALLBACK callback) {
@ -551,16 +568,18 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
return; return;
} }
if (id<=HIGHEST_SHORT_ADDR) if (id<=HIGHEST_SHORT_ADDR)
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback); DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback);
else else
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback); DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
} }
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab); int reg=lookupSpeedTable(cab, false);
if (reg>=0) speedTable[reg].loco=0; if (reg>=0) {
speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track setThrottle2(cab,1); // ESTOP if this loco still on track
}
} }
void DCC::forgetAllLocos() { // removes all speed reminders void DCC::forgetAllLocos() { // removes all speed reminders
setThrottle2(0,1); // ESTOP all locos still on track setThrottle2(0,1); // ESTOP all locos still on track
@ -570,26 +589,22 @@ void DCC::forgetAllLocos() { // removes all speed reminders
byte DCC::loopStatus=0; byte DCC::loopStatus=0;
void DCC::loop() { void DCC::loop() {
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks TrackManager::loop(); // power overload checks
ackManagerLoop(); // maintain prog track ack manager
issueReminders(); issueReminders();
} }
void DCC::issueReminders() { void DCC::issueReminders() {
// if the main track transmitter still has a pending packet, skip this time around. // if the main track transmitter still has a pending packet, skip this time around.
if ( DCCWaveform::mainTrack.packetPending) return; if ( DCCWaveform::mainTrack.getPacketPending()) return;
// Move to next loco slot. If occupied, send a reminder.
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around int reg = lastLocoReminder+1;
for (int reg=0;reg<MAX_LOCOS;reg++) { if (reg > highestUsedReg) reg = 0; // Go to start of table
int slot=reg+nextLoco; if (speedTable[reg].loco > 0) {
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS; // have found loco to remind
if (speedTable[slot].loco > 0) { if (issueReminder(reg))
// have found the next loco to remind lastLocoReminder = reg;
// issueReminder will return true if this loco is completed (ie speed and functions) } else
if (issueReminder(slot)) nextLoco=slot+1; lastLocoReminder = reg;
return;
}
}
} }
bool DCC::issueReminder(int reg) { bool DCC::issueReminder(int reg) {
@ -669,6 +684,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
speedTable[reg].groupFlags=0; speedTable[reg].groupFlags=0;
speedTable[reg].functions=0; speedTable[reg].functions=0;
} }
if (reg > highestUsedReg) highestUsedReg = reg;
return reg; return reg;
} }
@ -676,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 < MAX_LOCOS; 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) {
@ -696,326 +712,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
} }
DCC::LOCO DCC::speedTable[MAX_LOCOS]; DCC::LOCO DCC::speedTable[MAX_LOCOS];
int DCC::nextLoco = 0; int DCC::lastLocoReminder = 0;
int DCC::highestUsedReg = 0;
//ACK MANAGER
ackOp const * DCC::ackManagerProg;
ackOp const * DCC::ackManagerProgStart;
byte DCC::ackManagerByte;
byte DCC::ackManagerByteVerify;
byte DCC::ackManagerStash;
int DCC::ackManagerWord;
byte DCC::ackManagerRetry;
byte DCC::ackRetry = 2;
int16_t DCC::ackRetrySum;
int16_t DCC::ackRetryPSum;
int DCC::ackManagerCv;
byte DCC::ackManagerBitNum;
bool DCC::ackReceived;
bool DCC::ackManagerRejoin;
CALLBACK_STATE DCC::callbackState=READY;
ACK_CALLBACK DCC::ackManagerCallback;
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
callback(-2);
return;
}
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
if (ackManagerRejoin ) {
// Change from JOIN must zero resets packet.
setProgTrackSyncMain(false);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
DCCWaveform::progTrack.autoPowerOff=false;
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on"));
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
ackManagerCv = cv;
ackManagerProg = program;
ackManagerProgStart = program;
ackManagerRetry = ackRetry;
ackManagerByte = byteValueOrBitnum;
ackManagerByteVerify = byteValueOrBitnum;
ackManagerBitNum=byteValueOrBitnum;
ackManagerCallback = callback;
}
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
ackManagerWord=wordval;
ackManagerSetup(0, 0, program, callback);
}
const byte RESET_MIN=8; // tuning of reset counter before sending message
// checkRessets return true if the caller should yield back to loop and try later.
bool DCC::checkResets(uint8_t numResets) {
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
}
void DCC::ackManagerLoop() {
while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg);
// breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
switch (opcode) {
case BASELINE:
if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return;
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
DCCWaveform::progTrack.setAckBaseline();
callbackState=READY;
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case WB: // write byte
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
}
break;
case V0:
case V1: // Issue validate bit=0 or bit=1 packet
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
}
break;
case WACK: // wait for ack (or absence of ack)
{
byte ackState=2; // keep polling
ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling
ackReceived=ackState==1;
break; // we have a genuine ACK result
}
case ITC0:
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
if (ackReceived) {
callback(opcode==ITC0?0:1);
return;
}
break;
case ITCB: // If True callback(byte)
if (ackReceived) {
callback(ackManagerByte);
return;
}
break;
case ITCBV: // If True callback(byte) - Verify
if (ackReceived) {
if (ackManagerByte == ackManagerByteVerify) {
ackRetrySum ++;
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
}
callback(ackManagerByte);
return;
}
break;
case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) {
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
callback(-1);
return;
}
break;
case FAIL: // callback(-1)
callback(-1);
return;
case BIV: // ackManagerByte initial value
ackManagerByte = ackManagerByteVerify;
break;
case STARTMERGE:
ackManagerBitNum=7;
ackManagerByte=0;
break;
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
ackManagerByte <<= 1;
// ackReceived means bit is zero.
if (!ackReceived) ackManagerByte |= 1;
ackManagerBitNum--;
break;
case SETBIT:
ackManagerProg++;
ackManagerBitNum=GETFLASH(ackManagerProg);
break;
case SETCV:
ackManagerProg++;
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
break;
case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return;
case ITSKIP:
if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:
DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1);
return;
} // end of switch
ackManagerProg++;
}
}
void DCC::callback(int value) {
// check for automatic retry
if (value == -1 && ackManagerRetry > 0) {
ackRetrySum ++;
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
ackManagerRetry --;
ackManagerProg = ackManagerProgStart;
return;
}
static unsigned long callbackStart;
// We are about to leave programming mode
// Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) {
case AFTER_WRITE: // first attempt to callback after a write operation
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
callbackState=READY;
break;
} // lines 906-910 added. avoid wait after write. use 1 PROG
callbackStart=millis();
callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS"));
break;
case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS
// If we are going to power off anyway, it doesnt matter
// but if we will keep the power on, we must off it for 30mS
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
}
break;
case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break;
//power has been off for 30mS
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
callbackState=READY;
break;
case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on
if (DCCWaveform::progTrack.autoPowerOff) {
if (Diag::ACK) DIAG(F("Auto Prog power off"));
DCCWaveform::progTrack.doAutoPowerOff();
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
}
// Restore <1 JOIN> to state before BASELINE
if (ackManagerRejoin) {
setProgTrackSyncMain(true);
if (Diag::ACK) DIAG(F("Auto JOIN"));
}
ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value);
}
}
void DCC::displayCabList(Print * stream) { void DCC::displayCabList(Print * stream) {
int used=0; int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) { for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco>0) { if (speedTable[reg].loco>0) {
used ++; used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"), StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),

127
DCC.h
View File

@ -36,82 +36,42 @@
#error short addr greater than 127 does not make sense #error short addr greater than 127 does not make sense
#endif #endif
#endif #endif
#include "DCCACK.h"
const uint16_t LONG_ADDR_MARKER = 0x4000; const uint16_t LONG_ADDR_MARKER = 0x4000;
typedef void (*ACK_CALLBACK)(int16_t result);
enum ackOp : byte
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCBV, // If True callback(byte) - end of Verify Byte
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
BIV, // Set ackManagerByte to initial value for Verify retry
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
SETBIT, // sets bit number to next prog byte
SETCV, // sets cv number to next prog byte
SETBYTE, // sets current byte to next prog byte
SETBYTEH, // sets current byte to word high byte
SETBYTEL, // sets current byte to word low byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
SKIPTARGET = 0xFF // jump to target
};
enum CALLBACK_STATE : byte {
AFTER_WRITE, // Start callback sequence after something was written to the decoder
WAITING_100, // Waiting for 100mS of stable power
WAITING_30, // waiting to 30ms of power off gap.
READY, // Ready to complete callback
};
// Allocations with memory implications..! // Allocations with memory implications..!
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
#if defined(ARDUINO_AVR_UNO) #if defined(HAS_ENOUGH_MEMORY)
const byte MAX_LOCOS = 20;
#elif defined(ARDUINO_AVR_NANO)
const byte MAX_LOCOS = 30;
#else
const byte MAX_LOCOS = 50; const byte MAX_LOCOS = 50;
#else
const byte MAX_LOCOS = 30;
#endif #endif
class DCC class DCC
{ {
public: public:
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); static inline void setShieldName(const FSH * motorShieldName) {
static void setJoinRelayPin(byte joinRelayPin); shieldName=(FSH *)motorShieldName;
};
static void begin();
static void loop(); static void loop();
// Public DCC API functions // Public DCC API functions
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
static uint8_t getThrottleSpeed(int cab); static int8_t getThrottleSpeed(int cab);
static uint8_t getThrottleSpeedByte(int cab);
static bool getThrottleDirection(int cab); static bool getThrottleDirection(int cab);
static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVByteMain(int cab, int cv, byte bValue);
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
static void setFunction(int cab, byte fByte, byte eByte); static void setFunction(int cab, byte fByte, byte eByte);
static void setFn(int cab, int16_t functionNumber, bool on); static bool setFn(int cab, int16_t functionNumber, bool on);
static void changeFn(int cab, int16_t functionNumber); static void changeFn(int cab, int16_t functionNumber);
static int getFn(int cab, int16_t functionNumber); static int getFn(int cab, int16_t functionNumber);
static uint32_t getFunctionMap(int cab); static uint32_t getFunctionMap(int cab);
static void updateGroupflags(byte &flags, int16_t functionNumber); static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate); static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
static bool writeTextPacket(byte *b, int nBytes); static bool writeTextPacket(byte *b, int nBytes);
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
static void readCV(int16_t cv, ACK_CALLBACK callback); static void readCV(int16_t cv, ACK_CALLBACK callback);
@ -132,12 +92,6 @@ public:
static inline void setGlobalSpeedsteps(byte s) { static inline void setGlobalSpeedsteps(byte s) {
globalSpeedsteps = s; globalSpeedsteps = s;
}; };
static inline int16_t setAckRetry(byte retry) {
ackRetry = retry;
ackRetryPSum = ackRetrySum;
ackRetrySum = 0; // reset running total
return ackRetryPSum;
};
struct LOCO struct LOCO
{ {
@ -148,45 +102,23 @@ public:
}; };
static LOCO speedTable[MAX_LOCOS]; static LOCO speedTable[MAX_LOCOS];
static int lookupSpeedTable(int locoId, bool autoCreate=true); static int lookupSpeedTable(int locoId, bool autoCreate=true);
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
private: private:
static byte joinRelay;
static byte loopStatus; static byte loopStatus;
static void setThrottle2(uint16_t cab, uint8_t speedCode); static void setThrottle2(uint16_t cab, uint8_t speedCode);
static void updateLocoReminder(int loco, byte speedCode); static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal(int cab, byte fByte, byte eByte); static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg); static bool issueReminder(int reg);
static int nextLoco; static int lastLocoReminder;
static int highestUsedReg;
static FSH *shieldName; static FSH *shieldName;
static byte globalSpeedsteps; static byte globalSpeedsteps;
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
static void issueReminders(); static void issueReminders();
static void callback(int value); static void callback(int value);
// ACK MANAGER
static ackOp const *ackManagerProg;
static ackOp const *ackManagerProgStart;
static byte ackManagerByte;
static byte ackManagerByteVerify;
static byte ackManagerBitNum;
static int ackManagerCv;
static byte ackManagerRetry;
static byte ackRetry;
static int16_t ackRetrySum;
static int16_t ackRetryPSum;
static int ackManagerWord;
static byte ackManagerStash;
static bool ackReceived;
static bool ackManagerRejoin;
static ACK_CALLBACK ackManagerCallback;
static CALLBACK_STATE callbackState;
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
static void ackManagerLoop();
static bool checkResets( uint8_t numResets);
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
// NMRA codes # // NMRA codes #
static const byte SET_SPEED = 0x3f; static const byte SET_SPEED = 0x3f;
@ -201,31 +133,4 @@ private:
static const byte BIT_OFF = 0x00; static const byte BIT_OFF = 0x00;
}; };
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
#define ARDUINO_AVR_MEGA2560
#endif
#if defined(ARDUINO_AVR_UNO)
#define ARDUINO_TYPE "UNO"
#elif defined(ARDUINO_AVR_NANO)
#define ARDUINO_TYPE "NANO"
#elif defined(ARDUINO_AVR_MEGA2560)
#define ARDUINO_TYPE "MEGA"
#elif defined(ARDUINO_ARCH_MEGAAVR)
#define ARDUINO_TYPE "MEGAAVR"
#elif defined(ARDUINO_TEENSY32)
#define ARDUINO_TYPE "TEENSY32"
#elif defined(ARDUINO_TEENSY35)
#define ARDUINO_TYPE "TEENSY35"
#elif defined(ARDUINO_TEENSY36)
#define ARDUINO_TYPE "TEENSY36"
#elif defined(ARDUINO_TEENSY40)
#define ARDUINO_TYPE "TEENSY40"
#elif defined(ARDUINO_TEENSY41)
#define ARDUINO_TYPE "TEENSY41"
#else
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
#endif
#endif #endif

469
DCCACK.cpp Normal file
View File

@ -0,0 +1,469 @@
/*
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DCCACK.h"
#include "DIAG.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "TrackManager.h"
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
MotorDriver * DCCACK::progDriver=NULL;
ackOp const * DCCACK::ackManagerProg;
ackOp const * DCCACK::ackManagerProgStart;
byte DCCACK::ackManagerByte;
byte DCCACK::ackManagerByteVerify;
byte DCCACK::ackManagerStash;
int DCCACK::ackManagerWord;
byte DCCACK::ackManagerRetry;
byte DCCACK::ackRetry = 2;
int16_t DCCACK::ackRetrySum;
int16_t DCCACK::ackRetryPSum;
int DCCACK::ackManagerCv;
byte DCCACK::ackManagerBitNum;
bool DCCACK::ackReceived;
bool DCCACK::ackManagerRejoin;
volatile uint8_t DCCACK::numAckGaps=0;
volatile uint8_t DCCACK::numAckSamples=0;
uint8_t DCCACK::trailingEdgeCounter=0;
unsigned int DCCACK::ackPulseDuration; // micros
unsigned long DCCACK::ackPulseStart; // micros
volatile bool DCCACK::ackDetected;
unsigned long DCCACK::ackCheckStart; // millis
volatile bool DCCACK::ackPending;
bool DCCACK::autoPowerOff;
int DCCACK::ackThreshold;
int DCCACK::ackLimitmA = 50;
int DCCACK::ackMaxCurrent;
unsigned int DCCACK::ackCheckDuration; // millis
CALLBACK_STATE DCCACK::callbackState=READY;
ACK_CALLBACK DCCACK::ackManagerCallback;
void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
ackManagerRejoin=TrackManager::isJoined();
if (ackManagerRejoin) {
// Change from JOIN must zero resets packet.
TrackManager::setJoin(false);
DCCWaveform::progTrack.clearResets();
}
progDriver=TrackManager::getProgDriver();
if (progDriver==NULL) {
TrackManager::setJoin(ackManagerRejoin);
callback(-3); // we dont have a prog track!
return;
}
if (!progDriver->canMeasureCurrent()) {
TrackManager::setJoin(ackManagerRejoin);
callback(-2); // our prog track cant measure current
return;
}
autoPowerOff=false;
if (progDriver->getPower() == POWERMODE::OFF) {
autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on"));
progDriver->setPower(POWERMODE::ON);
/* TODO !!! in MotorDriver surely!
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.clearResets();
**/
}
ackManagerCv = cv;
ackManagerProg = program;
ackManagerProgStart = program;
ackManagerRetry = ackRetry;
ackManagerByte = byteValueOrBitnum;
ackManagerByteVerify = byteValueOrBitnum;
ackManagerBitNum=byteValueOrBitnum;
ackManagerCallback = callback;
}
void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
ackManagerWord=wordval;
Setup(0, 0, program, callback);
}
const byte RESET_MIN=8; // tuning of reset counter before sending message
// checkRessets return true if the caller should yield back to loop and try later.
bool DCCACK::checkResets(uint8_t numResets) {
return DCCWaveform::progTrack.getResets() < numResets;
}
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
void DCCACK::setAckBaseline() {
int baseline=progDriver->getCurrentRaw();
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
baseline,progDriver->raw2mA(baseline),
ackThreshold,progDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
}
void DCCACK::setAckPending() {
ackMaxCurrent=0;
ackPulseStart=0;
ackPulseDuration=0;
ackDetected=false;
ackCheckStart=millis();
numAckSamples=0;
numAckGaps=0;
ackPending=true; // interrupt routines will now take note
}
byte DCCACK::getAck() {
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.
}
#ifndef DISABLE_PROG
void DCCACK::loop() {
while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg);
// breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
switch (opcode) {
case BASELINE:
if (progDriver->getPower()==POWERMODE::OVERLOAD) return;
if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
setAckBaseline();
callbackState=AFTER_READ;
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
callbackState=AFTER_WRITE;
}
break;
case WB: // write byte
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
callbackState=AFTER_WRITE;
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
}
break;
case V0:
case V1: // Issue validate bit=0 or bit=1 packet
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
}
break;
case WACK: // wait for ack (or absence of ack)
{
byte ackState=2; // keep polling
ackState=getAck();
if (ackState==2) return; // keep polling
ackReceived=ackState==1;
break; // we have a genuine ACK result
}
case ITC0:
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
if (ackReceived) {
callback(opcode==ITC0?0:1);
return;
}
break;
case ITCB: // If True callback(byte)
if (ackReceived) {
callback(ackManagerByte);
return;
}
break;
case ITCBV: // If True callback(byte) - Verify
if (ackReceived) {
if (ackManagerByte == ackManagerByteVerify) {
ackRetrySum ++;
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
}
callback(ackManagerByte);
return;
}
break;
case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) {
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
callback(-1);
return;
}
break;
case CALLFAIL: // callback(-1)
callback(-1);
return;
case BIV: // ackManagerByte initial value
ackManagerByte = ackManagerByteVerify;
break;
case STARTMERGE:
ackManagerBitNum=7;
ackManagerByte=0;
break;
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
ackManagerByte <<= 1;
// ackReceived means bit is zero.
if (!ackReceived) ackManagerByte |= 1;
ackManagerBitNum--;
break;
case SETBIT:
ackManagerProg++;
ackManagerBitNum=GETFLASH(ackManagerProg);
break;
case SETCV:
ackManagerProg++;
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
break;
case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return;
case ITSKIP:
if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:
DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1);
return;
} // end of switch
ackManagerProg++;
}
}
void DCCACK::callback(int value) {
// check for automatic retry
if (value == -1 && ackManagerRetry > 0) {
ackRetrySum ++;
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
ackManagerRetry --;
ackManagerProg = ackManagerProgStart;
return;
}
static unsigned long callbackStart;
// We are about to leave programming mode
// Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) {
case AFTER_READ:
if (ackManagerRejoin && autoPowerOff) {
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
} else {
callbackState=READY;
}
break;
case AFTER_WRITE: // first attempt to callback after a write operation
if (!ackManagerRejoin && !autoPowerOff) {
callbackState=READY;
break;
} // lines 906-910 added. avoid wait after write. use 1 PROG
callbackStart=millis();
callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS"));
break;
case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS
// If we are going to power off anyway, it doesnt matter
// but if we will keep the power on, we must off it for 30mS
if (autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
}
break;
case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break;
//power has been off for 30mS
progDriver->setPower(POWERMODE::ON);
callbackState=READY;
break;
case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on
if (autoPowerOff) {
if (Diag::ACK) DIAG(F("Auto Prog power off"));
progDriver->setPower(POWERMODE::OFF);
/* TODO
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
**/
}
// Restore <1 JOIN> to state before BASELINE
if (ackManagerRejoin) {
TrackManager::setJoin(true);
if (Diag::ACK) DIAG(F("Auto JOIN"));
}
ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value);
}
}
#endif
void DCCACK::checkAck(byte sentResetsSincePacket) {
if (!ackPending) return;
// This function operates in interrupt() time so must be fast and can't DIAG
if (sentResetsSincePacket > 6) { //ACK timeout
ackCheckDuration=millis()-ackCheckStart;
ackPending = false;
return;
}
int current=progDriver->getCurrentRaw(true); // true means "from interrupt"
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
if (current>ackThreshold) {
if (trailingEdgeCounter > 0) {
numAckGaps++;
trailingEdgeCounter = 0;
}
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
// not in pulse
if (ackPulseStart==0) return; // keep waiting for leading edge
// if we reach to this point, we have
// detected trailing edge of pulse
if (trailingEdgeCounter == 0) {
ackPulseDuration=micros()-ackPulseStart;
}
// but we do not trust it yet and return (which will force another
// measurement) and first the third time around with low current
// the ack detection will be finalized.
if (trailingEdgeCounter < 2) {
trailingEdgeCounter++;
return;
}
trailingEdgeCounter = 0;
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;
DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets
return; // we have a genuine ACK result
}
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
}

156
DCCACK.h Normal file
View File

@ -0,0 +1,156 @@
/*
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DCCACK_h
#define DCCACK_h
#include "MotorDriver.h"
typedef void (*ACK_CALLBACK)(int16_t result);
enum ackOp : byte
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCBV, // If True callback(byte) - end of Verify Byte
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
CALLFAIL, // callback(-1)
BIV, // Set ackManagerByte to initial value for Verify retry
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
SETBIT, // sets bit number to next prog byte
SETCV, // sets cv number to next prog byte
SETBYTE, // sets current byte to next prog byte
SETBYTEH, // sets current byte to word high byte
SETBYTEL, // sets current byte to word low byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
SKIPTARGET = 0xFF // jump to target
};
enum CALLBACK_STATE : byte {
AFTER_READ, // Start callback sequence after something was read from the decoder
AFTER_WRITE, // Start callback sequence after something was written to the decoder
WAITING_100, // Waiting for 100mS of stable power
WAITING_30, // waiting to 30ms of power off gap.
READY, // Ready to complete callback
};
class DCCACK {
public:
static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker
static inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
static inline void setMinAckPulseDuration(unsigned int i) {
minAckPulseDuration = i;
}
static inline void setMaxAckPulseDuration(unsigned int i) {
maxAckPulseDuration = i;
}
static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback);
static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback);
static void loop();
static bool isActive() { return ackManagerProg!=NULL;}
static inline int16_t setAckRetry(byte retry) {
ackRetry = retry;
ackRetryPSum = ackRetrySum;
ackRetrySum = 0; // reset running total
return ackRetryPSum;
};
private:
static const byte SET_SPEED = 0x3f;
static const byte WRITE_BYTE = 0x7C;
static const byte VERIFY_BYTE = 0x74;
static const byte BIT_MANIPULATE = 0x78;
static const byte WRITE_BIT = 0xF0;
static const byte VERIFY_BIT = 0xE0;
static const byte BIT_ON = 0x08;
static const byte BIT_OFF = 0x00;
static void setAckBaseline();
static void setAckPending();
static void callback(int value);
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
// ACK management (Prog track only)
static void checkAck();
static bool checkResets(uint8_t numResets);
static volatile bool ackPending;
static volatile bool ackDetected;
static int ackThreshold;
static int ackLimitmA;
static int ackMaxCurrent;
static unsigned long ackCheckStart; // millis
static unsigned int ackCheckDuration; // millis
static unsigned int ackPulseDuration; // micros
static unsigned long ackPulseStart; // micros
static unsigned int minAckPulseDuration ; // micros
static unsigned int maxAckPulseDuration ; // micros
static MotorDriver* progDriver;
static volatile uint8_t numAckGaps;
static volatile uint8_t numAckSamples;
static uint8_t trailingEdgeCounter;
static ackOp const * ackManagerProg;
static ackOp const * ackManagerProgStart;
static byte ackManagerByte;
static byte ackManagerByteVerify;
static byte ackManagerStash;
static int ackManagerWord;
static byte ackManagerRetry;
static byte ackRetry;
static int16_t ackRetrySum;
static int16_t ackRetryPSum;
static int ackManagerCv;
static byte ackManagerBitNum;
static bool ackReceived;
static bool ackManagerRejoin;
static bool autoPowerOff;
static CALLBACK_STATE callbackState;
static ACK_CALLBACK ackManagerCallback;
};
#endif

View File

@ -32,18 +32,23 @@
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "SerialManager.h" #include "SerialManager.h"
#include "version.h" #include "version.h"
#ifndef ARDUINO_ARCH_ESP32
#include "WifiInterface.h" #include "WifiInterface.h"
#else
#include "WifiESP32.h"
#endif
#if ETHERNET_ON == true #if ETHERNET_ON == true
#include "EthernetInterface.h" #include "EthernetInterface.h"
#endif #endif
#include "LCD_Implementation.h" #include "Display_Implementation.h"
#include "LCN.h" #include "LCN.h"
#include "freeMemory.h"
#include "IODevice.h" #include "IODevice.h"
#include "Turnouts.h" #include "Turnouts.h"
#include "Sensors.h" #include "Sensors.h"
#include "Outputs.h" #include "Outputs.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "TrackManager.h"
#include "DCCTimer.h"
#include "EXRAIL.h" #include "EXRAIL.h"
#endif #endif

View File

@ -1,11 +1,13 @@
/* /*
* © 2022 Paul M Antoine
* © 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
@ -30,31 +32,29 @@
#include "Turnouts.h" #include "Turnouts.h"
#include "Outputs.h" #include "Outputs.h"
#include "Sensors.h" #include "Sensors.h"
#include "freeMemory.h"
#include "GITHUB_SHA.h" #include "GITHUB_SHA.h"
#include "version.h" #include "version.h"
#include "defines.h" #include "defines.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "EEStore.h" #include "EEStore.h"
#include "DIAG.h" #include "DIAG.h"
#include "TrackManager.h"
#include "DCCTimer.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
#include <avr/wdt.h>
//////////////////////////////////////////////////////////////////////////////// // This macro can't be created easily as a portable function because the
// // flashlist requires a far pointer for high flash access.
// Figure out if we have enough memory for advanced features #define SENDFLASHLIST(stream,flashList) \
// for (int16_t i=0;;i+=sizeof(flashList[0])) { \
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) int16_t value=GETHIGHFLASHW(flashList,i); \
// nope if (value==INT16_MAX) break; \
#else if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
#define HAS_ENOUGH_MEMORY }
#endif
// 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;
@ -62,7 +62,11 @@ const int16_t HASH_KEYWORD_ACK = 3113;
const int16_t HASH_KEYWORD_ON = 2657; const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436; const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209; const int16_t HASH_KEYWORD_SLOW = -17209;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_PROGBOOST = -6353; const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#endif
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
const int16_t HASH_KEYWORD_EEPROM = -7168; const int16_t HASH_KEYWORD_EEPROM = -7168;
#endif #endif
@ -74,11 +78,15 @@ const int16_t HASH_KEYWORD_RETRY = 25704;
const int16_t HASH_KEYWORD_SPEED28 = -17064; const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816; const int16_t HASH_KEYWORD_SPEED128 = 25816;
const int16_t HASH_KEYWORD_SERVO=27709; const int16_t HASH_KEYWORD_SERVO=27709;
const int16_t HASH_KEYWORD_TT=2688;
const int16_t HASH_KEYWORD_VPIN=-415; const int16_t HASH_KEYWORD_VPIN=-415;
const int16_t HASH_KEYWORD_A='A'; const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C'; const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_G='G';
const int16_t HASH_KEYWORD_I='I';
const int16_t HASH_KEYWORD_R='R'; const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T'; const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
const int16_t HASH_KEYWORD_LCN = 15137; const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853; const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309; const int16_t HASH_KEYWORD_SHOW = -21309;
@ -187,10 +195,10 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
// Parse an F() string // Parse an F() string
void DCCEXParser::parse(const FSH * cmd) { void DCCEXParser::parse(const FSH * cmd) {
DIAG(F("SETUP(\"%S\")"),cmd); DIAG(F("SETUP(\"%S\")"),cmd);
int size=strlen_P((char *)cmd)+1; int size=STRLEN_P((char *)cmd)+1;
char buffer[size]; char buffer[size];
strcpy_P(buffer,(char *)cmd); STRCPY_P(buffer,(char *)cmd);
parse(&Serial,(byte *)buffer,NULL); parse(&USB_SERIAL,(byte *)buffer,NULL);
} }
// See documentation on DCC class for info on this section // See documentation on DCC class for info on this section
@ -211,6 +219,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{ {
#ifdef DISABLE_PROG
(void)ringStream;
#endif
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused (void)EEPROM; // tell compiler not to warn this is unused
#endif #endif
@ -280,6 +291,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (direction < 0 || direction > 1) if (direction < 0 || direction > 1)
break; // invalid direction code break; // invalid direction code
if (cab > 10239 || cab < 0)
break; // beyond DCC range
DCC::setThrottle(cab, tspeed, direction); DCC::setThrottle(cab, tspeed, direction);
if (params == 4) // send obsolete format T response if (params == 4) // send obsolete format T response
@ -292,33 +305,44 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
break; break;
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE> case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
{ {
int address; int address;
byte subaddress; byte subaddress;
byte activep; byte activep;
byte onoff;
if (params==2) { // <a LINEARADDRESS ACTIVATE> if (params==2) { // <a LINEARADDRESS ACTIVATE>
address=(p[0] - 1) / 4 + 1; address=(p[0] - 1) / 4 + 1;
subaddress=(p[0] - 1) % 4; subaddress=(p[0] - 1) % 4;
activep=1; activep=1;
onoff=2; // send both
} }
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE> else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
address=p[0]; address=p[0];
subaddress=p[1]; subaddress=p[1];
activep=2; activep=2;
onoff=2; // send both
}
else if (params==4) { // <a ADDRESS SUBADDRESS ACTIVATE ONOFF>
address=p[0];
subaddress=p[1];
activep=2;
if ((p[3] < 0) || (p[3] > 1)) // invalid onoff 0|1
break;
onoff=p[3];
} }
else break; // invalid no of parameters else break; // invalid no of parameters
if ( if (
((address & 0x01FF) != address) // invalid address (limit 9 bits ) ((address & 0x01FF) != address) // invalid address (limit 9 bits)
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits)
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 || (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1
) break; ) break;
// Honour the configuration option (config.h) which allows the <a> command to be reversed // Honour the configuration option (config.h) which allows the <a> command to be reversed
#ifdef DCC_ACCESSORY_COMMAND_REVERSE #ifdef DCC_ACCESSORY_COMMAND_REVERSE
DCC::setAccessory(address, subaddress,p[activep]==0); DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
#else #else
DCC::setAccessory(address, subaddress,p[activep]==1); DCC::setAccessory(address, subaddress,p[activep]==1,onoff);
#endif #endif
} }
return; return;
@ -328,6 +352,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
break; break;
case 'z': // direct pin manipulation
if (p[0]==0) break;
if (params==1) { // <z vpin | -vpin>
if (p[0]>0) IODevice::write(p[0],HIGH);
else IODevice::write(-p[0],LOW);
return;
}
if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
// unused params default to 0
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
return;
}
break;
case 'Z': // OUTPUT <Z ...> case 'Z': // OUTPUT <Z ...>
if (parseZ(stream, params, p)) if (parseZ(stream, params, p))
return; return;
@ -338,6 +376,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
break; break;
#ifndef DISABLE_PROG
case 'w': // WRITE CV on MAIN <w CAB CV VALUE> case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(p[0], p[1], p[2]); DCC::writeCVByteMain(p[0], p[1], p[2]);
return; return;
@ -345,12 +384,16 @@ 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;
if (params > MAX_PACKET_SIZE) break;
{ {
byte packet[params]; byte packet[params];
for (int i=0;i<params;i++) { for (int i=0;i<params;i++) {
@ -361,6 +404,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
} }
return; return;
#ifndef DISABLE_PROG
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB> case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream)) if (!stashCallback(stream, p, ringStream))
break; break;
@ -400,7 +444,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{ // <R CV> -- uses verify callback { // <R CV> -- uses verify callback
if (!stashCallback(stream, p, ringStream)) if (!stashCallback(stream, p, ringStream))
break; break;
DCC::verifyCVByte(p[0], p[1], callback_Vbyte); DCC::verifyCVByte(p[0], 0, callback_Vbyte);
return; return;
} }
if (params == 3) if (params == 3)
@ -418,6 +462,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
} }
break; break;
#endif
case '1': // POWERON <1 [MAIN|PROG|JOIN]> case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{ {
@ -425,27 +470,29 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool prog=false; bool prog=false;
bool join=false; bool join=false;
if (params > 1) break; if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually if (params==0) { // All
main=true; main=true;
prog=true; prog=true;
} }
if (params==1) { if (params==1) {
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true; main=true;
prog=true; prog=true;
join=true; join=true;
} }
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true; prog=true;
} }
#endif
else break; // will reply <X> else break; // will reply <X>
} }
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); TrackManager::setJoin(join);
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); if (main) TrackManager::setMainPower(POWERMODE::ON);
DCC::setProgTrackSyncMain(join); if (prog) TrackManager::setProgPower(POWERMODE::ON);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
@ -456,7 +503,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool main=false; bool main=false;
bool prog=false; bool prog=false;
if (params > 1) break; if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually if (params==0) { // All
main=true; main=true;
prog=true; prog=true;
} }
@ -464,18 +511,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true; main=true;
} }
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true; prog=true;
} }
#endif
else break; // will reply <X> else break; // will reply <X>
} }
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) { if (prog) {
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); TrackManager::setProgPower(POWERMODE::OFF);
} }
DCC::setProgTrackSyncMain(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
@ -486,10 +535,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
case 'c': // SEND METER RESPONSES <c> case 'c': // SEND METER RESPONSES <c>
// <c MeterName value C/V unit min max res warn> // No longer useful because of multiple tracks See <JG> and <JI>
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(), if (params>0) break;
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA()); TrackManager::reportObsoleteCurrent(stream);
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
return; return;
case 'Q': // SENSORS <Q> case 'Q': // SENSORS <Q>
@ -497,12 +545,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
case 's': // <s> case 's': // <s>
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states Turnout::printAll(stream); //send all Turnout states
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states Sensor::printAll(stream); //send all Sensor states
// TODO Send stats of speed reminders table
return; return;
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
@ -525,6 +571,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
return; return;
case '=': // <= Track manager control >
if (TrackManager::parseJ(stream, params, p))
return;
break;
case '#': // NUMBER OF LOCOSLOTS <#> case '#': // NUMBER OF LOCOSLOTS <#>
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS); StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
return; return;
@ -539,14 +590,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if(params!=3) break; if(params!=3) break;
if (Diag::CMD) if (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
DCC::setFn(p[0], p[1], p[2] == 1); if (DCC::setFn(p[0], p[1], p[2] == 1)) return;
return; break;
#if WIFI_ON #if WIFI_ON
case '+': // Complex Wifi interface command (not usual parse) case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback && !ringStream) { if (atCommandCallback && !ringStream) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); TrackManager::setPower(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
atCommandCallback((HardwareSerial *)stream,com); atCommandCallback((HardwareSerial *)stream,com);
return; return;
} }
@ -555,15 +605,35 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'J' : // throttle info access case 'J' : // throttle info access
{ {
if ((params<1) | (params>2)) break; // <J> if ((params<1) | (params>3)) break; // <J>
//if ((params<1) | (params>2)) break; // <J>
int16_t id=(params==2)?p[1]:0; int16_t id=(params==2)?p[1]:0;
switch(p[0]) { switch(p[0]) {
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
if (params==1) { // <JC> returns latest time
int16_t x = CommandDistributor::retClockTime();
StringFormatter::send(stream, F("<jC %d>\n"), x);
return;
}
CommandDistributor::setClockTime(p[1], p[2], 1);
return;
case HASH_KEYWORD_G: // <JG> current gauge limits
if (params>1) break;
TrackManager::reportGauges(stream); // <g limit...limit>
return;
case HASH_KEYWORD_I: // <JI> current values
if (params>1) break;
TrackManager::reportCurrent(stream); // <g limit...limit>
return;
case HASH_KEYWORD_A: // <JA> returns automations/routes case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA")); StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA> if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList); SENDFLASHLIST(stream,RMFT2::routeIdList)
sendFlashList(stream,RMFT2::automationIdList); SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif #endif
} }
else { // <JA id> else { // <JA id>
@ -582,9 +652,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR")); StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList); if (params==1) {
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), SENDFLASHLIST(stream,RMFT2::rosterIdList)
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id)); }
else {
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;
@ -630,14 +706,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<X>\n")); StringFormatter::send(stream, F("<X>\n"));
} }
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
for (int16_t i=0;;i++) {
int16_t value=GETFLASHW(flashList+i);
if (value==0) return;
StringFormatter::send(stream,F(" %d"),value);
}
}
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
{ {
@ -688,41 +756,37 @@ bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
{ {
// JMRI sends this info in DCC message format but it's not exactly // JMRI sends this info in DCC message format but it's not exactly
// convenient for other processing // convenient for other processing
if (params == 2) if (params == 2) {
{
byte instructionField = p[1] & 0xE0; // 1110 0000 byte instructionField = p[1] & 0xE0; // 1110 0000
if (instructionField == 0x80) // 1000 0000 Function group 1 if (instructionField == 0x80) { // 1000 0000 Function group 1
{
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0 // Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
funcmap(p[0], normalized, 0, 4); return (funcmap(p[0], normalized, 0, 4));
} } else if (instructionField == 0xA0) { // 1010 0000 Function group 2
else if (instructionField == 0xA0) // 1010 0000 Function group 2
{
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12 if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
funcmap(p[0], p[1], 5, 8); return (funcmap(p[0], p[1], 5, 8));
else else
funcmap(p[0], p[1], 9, 12); return (funcmap(p[0], p[1], 9, 12));
} }
} }
if (params == 3) if (params == 3) {
{ if (p[1] == 222) {
if (p[1] == 222) return (funcmap(p[0], p[2], 13, 20));
funcmap(p[0], p[2], 13, 20); } else if (p[1] == 223) {
else if (p[1] == 223) return (funcmap(p[0], p[2], 21, 28));
funcmap(p[0], p[2], 21, 28); }
} }
(void)stream; // NO RESPONSE (void)stream; // NO RESPONSE
return true; return false;
} }
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop) bool DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
{ {
for (int16_t i = fstart; i <= fstop; i++) for (int16_t i = fstart; i <= fstop; i++) {
{ if (! DCC::setFn(cab, i, value & 1)) return false;
DCC::setFn(cab, i, value & 1);
value >>= 1; value >>= 1;
} }
return true;
} }
//=================================== //===================================
@ -731,15 +795,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
switch (params) switch (params)
{ {
case 0: // <T> list turnout definitions case 0: // <T> list turnout definitions
{ return Turnout::printAll(stream); // will <X> if none found
bool gotOne = false;
for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
{
gotOne = true;
tt->print(stream);
}
return gotOne; // will <X> if none found
}
case 1: // <T id> delete turnout case 1: // <T id> delete turnout
if (!Turnout::remove(p[0])) if (!Turnout::remove(p[0]))
@ -760,12 +816,19 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_T: case HASH_KEYWORD_T:
state= false; state= false;
break; break;
default: case HASH_KEYWORD_X:
return false; // Invalid parameter {
Turnout *tt = Turnout::get(p[0]);
if (tt) {
tt->print(stream);
return true;
}
return false;
}
default: // Invalid parameter
return false;
} }
if (!Turnout::setClosed(p[0], state)) return false; if (!Turnout::setClosed(p[0], state)) return false;
return true; return true;
} }
@ -848,29 +911,31 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
return true; return true;
case HASH_KEYWORD_RAM: // <D RAM> case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory()); 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) {
DCCWaveform::progTrack.setAckLimit(p[2]); DCCACK::setAckLimit(p[2]);
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42> LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
} else if (p[1] == HASH_KEYWORD_MIN) { } else if (p[1] == HASH_KEYWORD_MIN) {
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]); DCCACK::setMinAckPulseDuration(p[2]);
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500> LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
} else if (p[1] == HASH_KEYWORD_MAX) { } else if (p[1] == HASH_KEYWORD_MAX) {
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]); DCCACK::setMaxAckPulseDuration(p[2]);
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000> LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
} else if (p[1] == HASH_KEYWORD_RETRY) { } else if (p[1] == HASH_KEYWORD_RETRY) {
if (p[2] >255) p[2]=3; if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2> LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
} }
} else { } else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
Diag::ACK = onOff; Diag::ACK = onOff;
} }
return true; return true;
#endif
case HASH_KEYWORD_CMD: // <D CMD ON/OFF> case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff; Diag::CMD = onOff;
@ -893,17 +958,15 @@ 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:
DCC::setProgTrackBoost(true); TrackManager::progTrackBoosted=true;
return true; return true;
#endif
case HASH_KEYWORD_RESET: case HASH_KEYWORD_RESET:
{ DCCTimer::reset();
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
break; // and <X> if we didnt restart break; // and <X> if we didnt restart
}
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries> case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
@ -928,16 +991,22 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break; break;
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1])); DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
break; break;
#if !defined(IO_MINIMAL_HAL) #if !defined(IO_NO_HAL)
case HASH_KEYWORD_HAL: case HASH_KEYWORD_HAL:
if (p[1] == HASH_KEYWORD_SHOW) if (p[1] == HASH_KEYWORD_SHOW)
IODevice::DumpAll(); IODevice::DumpAll();
else if (p[1] == HASH_KEYWORD_RESET)
IODevice::reset();
break; break;
#endif #endif
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break;
default: // invalid/unknown default: // invalid/unknown
break; break;
} }

View File

@ -71,7 +71,7 @@ struct DCCEXParser
static FILTER_CALLBACK filterCallback; static FILTER_CALLBACK filterCallback;
static FILTER_CALLBACK filterRMFTCallback; static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback; static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop); static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
static void sendFlashList(Print * stream,const int16_t flashList[]); static void sendFlashList(Print * stream,const int16_t flashList[]);
}; };

239
DCCRMT.cpp Normal file
View File

@ -0,0 +1,239 @@
/*
* © 2021-2022, Harald Barth.
*
* This file is part of DCC-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/>.
*/
#if defined(ARDUINO_ARCH_ESP32)
#include "defines.h"
#include "DIAG.h"
#include "DCCRMT.h"
#include "DCCTimer.h"
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
#include "soc/gpio_sig_map.h"
// Number of bits resulting out of X bytes of DCC payload data
// Each byte has one bit extra and at the end we have one EOF marker
#define DATA_LEN(X) ((X)*9+1)
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
#error wrong IDF version
#endif
void setDCCBit1(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_1_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_1_HALFPERIOD;
}
void setDCCBit0(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD;
}
// special long zero to trigger scope
void setDCCBit0Long(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
}
void setEOT(rmt_item32_t* item) {
item->val = 0;
}
// This is an array that contains the this pointers
// to all uses channel objects. This is used to determine
// which of the channels was triggering the ISR as there
// is only ONE common ISR routine for all channels.
RMTChannel *channelHandle[8] = { 0 };
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
RMTChannel *tt = channelHandle[channel];
if (tt) tt->RMTinterrupt();
if (channel == 0)
DCCTimer::updateMinimumFreeMemoryISR(0);
}
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
byte ch;
byte plen;
if (isMain) {
ch = 0;
plen = PREAMBLE_BITS_MAIN;
} else {
ch = 2;
plen = PREAMBLE_BITS_PROG;
}
// preamble
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
for (byte n=0; n<plen; n++)
setDCCBit1(preamble + n); // preamble bits
#ifdef SCOPE
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
#else
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
#endif
setEOT(preamble + plen + 1); // EOT marker
// idle
idleLen = 28;
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
if (isMain) {
for (byte n=0; n<8; n++) // 0 to 7
setDCCBit1(idle + n);
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
setDCCBit0(idle + n);
for (byte n=18; n<26; n++) // 18 to 25
setDCCBit1(idle + n);
} else {
for (byte n=0; n<26; n++) // all zero
setDCCBit0(idle + n);
}
setDCCBit1(idle + 26); // end bit
setEOT(idle + 27); // EOT marker
// data: max packet size today is 5 + checksum
maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
rmt_config_t config;
// Configure the RMT channel for TX
bzero(&config, sizeof(rmt_config_t));
config.rmt_mode = RMT_MODE_TX;
config.channel = channel = (rmt_channel_t)ch;
config.clk_div = RMT_CLOCK_DIVIDER;
config.gpio_num = (gpio_num_t)pins.pin;
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
// number of bits needed is 22preamble + start +
// 11*9 + extrazero + EOT = 124
// 2 mem block of 64 RMT items should be enough
ESP_ERROR_CHECK(rmt_config(&config));
addPin(pins.invpin, true);
/*
// test: config another gpio pin
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
*/
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
// DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
channelHandle[channel] = this; // used by interrupt
rmt_register_tx_end_callback(interrupt, 0);
rmt_set_tx_intr_en(channel, true);
DIAG(F("Channel %d DCC signal for %s start"), config.channel, isMain ? "MAIN" : "PROG");
// send one bit to kickstart the signal, remaining data will come from the
// packet queue. We intentionally do not wait for the RMT TX complete here.
//rmt_write_items(channel, preamble, preambleLen, false);
RMTprefill();
dataReady = false;
}
void RMTChannel::RMTprefill() {
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
}
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
//int RMTChannel::RMTfillData(dccPacket packet) {
// dataReady: Signals to then interrupt routine. It is set when
// we have data in the channel buffer which can be copied out
// to the HW. dataRepeat on the other hand signals back to
// the caller of this function if the data has been sent enough
// times (0 to 3 means 1 to 4 times in total).
if (dataRepeat > 0) // we have still old work to do
return dataRepeat;
if (dataReady == true) // the packet is not copied out yet
return 1000;
if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen);
return -1; // something very broken, can not convert packet
}
// convert bytes to RMT stream of "bits"
byte bitcounter = 0;
for(byte n=0; n<byteCount; n++) {
for(byte bit=0; bit<8; bit++) {
if (buffer[n] & transmitMask[bit])
setDCCBit1(data + bitcounter++);
else
setDCCBit0(data + bitcounter++);
}
setDCCBit0(data + bitcounter++); // zero at end of each byte
}
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter;
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
dataReady = true;
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
interrupts();
return 0;
}
void IRAM_ATTR RMTChannel::RMTinterrupt() {
//no rmt_tx_start(channel,true) as we run in loop mode
//preamble is always loaded at beginning of buffer
packetCounter++;
if (!dataReady && dataRepeat == 0) { // we did run empty
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
return; // nothing to do about that
}
// take care of incoming data
if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
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
dataRepeat--;
}
bool RMTChannel::addPin(byte pin, bool inverted) {
if (pin == UNUSED_PIN)
return true;
gpio_num_t gpioNum = (gpio_num_t)(pin);
esp_err_t err;
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
if (err != ESP_OK) return false;
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, inverted, 0);
if (err != ESP_OK) return false;
return true;
}
bool RMTChannel::addPin(pinpair pins) {
return addPin(pins.pin) && addPin(pins.invpin, true);
}
#endif //ESP32

66
DCCRMT.h Normal file
View File

@ -0,0 +1,66 @@
/*
* © 2021-2022, Harald Barth.
*
* This file is part of DCC-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/>.
*/
#if defined(ARDUINO_ARCH_ESP32)
#pragma once
#include <Arduino.h>
#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "soc/rmt_struct.h"
#include "MotorDriver.h" // for class pinpair
// make calculations easy and set up for microseconds
#define RMT_CLOCK_DIVIDER 80
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
#define DCC_0_HALFPERIOD 100 //8000
class RMTChannel {
public:
RMTChannel(pinpair pins, bool isMain);
bool addPin(byte pin, bool inverted=0);
bool addPin(pinpair pins);
void IRAM_ATTR RMTinterrupt();
void RMTprefill();
//int RMTfillData(dccPacket packet);
int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
inline bool busy() {
if (dataRepeat > 0) // we have still old work to do
return true;
return dataReady;
};
inline uint32_t packetCount() { return packetCounter; };
private:
rmt_channel_t channel;
// 3 types of data to send, preamble and then idle or data
// if this is prog track, idle will contain reset instead
rmt_item32_t *idle;
byte idleLen;
rmt_item32_t *preamble;
byte preambleLen;
rmt_item32_t *data;
byte dataLen;
byte maxDataLen;
uint32_t packetCounter = 0;
// flags
volatile bool dataReady = false; // do we have real data available or send idle
volatile byte dataRepeat = 0;
};
#endif //ESP32

View File

@ -1,218 +0,0 @@
/*
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
/* This timer class is used to manage the single timer required to handle the DCC waveform.
* All timer access comes through this class so that it can be compiled for
* various hardware CPU types.
*
* DCCEX works on a single timer interrupt at a regular 58uS interval.
* The DCCWaveform class generates the signals to the motor shield
* based on this timer.
*
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
* (see isPWMPin() function. )
* then this allows us to use a hardware driven pin switching arrangement which is
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
* the required pin state. (see setPWM())
* This is more accurate than the software interrupt but at the expense of
* limiting the choice of available pins.
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
*
* Because the PWM-based waveform is effectively set half a cycle after the software version,
* it is not acceptable to drive the two tracks on different methiods or it would cause
* problems for <1 JOIN> etc.
*
*/
#include "DCCTimer.h"
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
INTERRUPT_CALLBACK interruptHandler=0;
#ifdef ARDUINO_ARCH_MEGAAVR
// Arduino unoWifi Rev2 and nanoEvery architectire
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
TCB0.CNT = 0;
TCB0.CTRLA |= TCB_ENABLE_bm; // start
interrupts();
}
// ISR called by timer interrupt every 58uS
ISR(TCB0_INT_vect){
TCB0.INTFLAGS = TCB_CAPT_bm;
interruptHandler();
}
bool DCCTimer::isPWMPin(byte pin) {
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
(void) pin;
(void) high;
// TODO what are the relevant pins?
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
#elif defined(TEENSYDUINO)
IntervalTimer myDCCTimer;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
}
bool DCCTimer::isPWMPin(byte pin) {
//Teensy: digitalPinHasPWM, todo
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO what are the relevant pins?
(void) pin;
(void) high;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
uint32_t m1 = HW_OCOTP_MAC1;
uint32_t m2 = HW_OCOTP_MAC0;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
#else
read_mac(mac);
#endif
}
#if !defined(__IMXRT1062__)
void DCCTimer::read_mac(byte mac[6]) {
read(0xe,mac,0);
read(0xf,mac,3);
}
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
FTFL_FCCOB1 = word; // read the given word of read once area
// launch command and wait until complete
FTFL_FSTAT = FTFL_FSTAT_CCIF;
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
}
#endif
#else
// Arduino nano, uno, mega etc
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define TIMER1_A_PIN 11
#define TIMER1_B_PIN 12
#define TIMER1_C_PIN 13
#else
#define TIMER1_A_PIN 9
#define TIMER1_B_PIN 10
#endif
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
TCCR1A = 0;
ICR1 = CLOCK_CYCLES;
TCNT1 = 0;
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
interrupts();
}
// ISR called by timer interrupt every 58uS
ISR(TIMER1_OVF_vect){ interruptHandler(); }
// Alternative pin manipulation via PWM control.
bool DCCTimer::isPWMPin(byte pin) {
return pin==TIMER1_A_PIN
|| pin==TIMER1_B_PIN
#ifdef TIMER1_C_PIN
|| pin==TIMER1_C_PIN
#endif
;
}
void DCCTimer::setPWM(byte pin, bool high) {
if (pin==TIMER1_A_PIN) {
TCCR1A |= _BV(COM1A1);
OCR1A= high?1024:0;
}
else if (pin==TIMER1_B_PIN) {
TCCR1A |= _BV(COM1B1);
OCR1B= high?1024:0;
}
#ifdef TIMER1_C_PIN
else if (pin==TIMER1_C_PIN) {
TCCR1A |= _BV(COM1C1);
OCR1C= high?1024:0;
}
#endif
}
#include <avr/boot.h>
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
for (byte i=0; i<6; i++) {
mac[i]=boot_signature_byte_get(0x0E + i);
}
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
#endif

View File

@ -1,6 +1,7 @@
/* /*
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S * © 2021 Mike S
* © 2021 Harald Barth * © 2021-2023 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
* All rights reserved. * All rights reserved.
* *
@ -20,6 +21,34 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* There are several different implementations of this class which the compiler will select
according to the hardware.
*/
/* This timer class is used to manage the single timer required to handle the DCC waveform.
* All timer access comes through this class so that it can be compiled for
* various hardware CPU types.
*
* DCCEX works on a single timer interrupt at a regular 58uS interval.
* The DCCWaveform class generates the signals to the motor shield
* based on this timer.
*
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
* (see isPWMPin() function. )
* then this allows us to use a hardware driven pin switching arrangement which is
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
* the required pin state. (see setPWM())
* This is more accurate than the software interrupt but at the expense of
* limiting the choice of available pins.
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
*
* Because the PWM-based waveform is effectively set half a cycle after the software version,
* it is not acceptable to drive the two tracks on different methiods or it would cause
* problems for <1 JOIN> etc.
*
*/
#ifndef DCCTimer_h #ifndef DCCTimer_h
#define DCCTimer_h #define DCCTimer_h
#include "Arduino.h" #include "Arduino.h"
@ -32,11 +61,76 @@ class DCCTimer {
static void getSimulatedMacAddress(byte mac[6]); static void getSimulatedMacAddress(byte mac[6]);
static bool isPWMPin(byte pin); static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high); static void setPWM(byte pin, bool high);
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__)) static void clearPWM();
static void read_mac(byte mac[6]); static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
static void read(uint8_t word, uint8_t *mac, uint8_t offset); static void DCCEXanalogWrite(uint8_t pin, int value);
// Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other
// called subroutines. Must be called with interrupts disabled.
//
// Although __brkval may go up and down as heap memory is allocated
// and freed, this function records only the worst case encountered.
// So even if all of the heap is freed, the reported minimum free
// memory will not increase.
//
static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0)
__attribute__((always_inline)) {
int spare = freeMemory()-extraBytes;
if (spare < 0) spare = 0;
if (spare < minimum_free_memory) minimum_free_memory = spare;
};
static int getMinimumFreeMemory();
static void reset();
private:
static int freeMemory();
static volatile int minimum_free_memory;
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency
static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
#else
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
#endif #endif
private:
}; };
// Class ADCee implements caching of the ADC value for platforms which
// have a too slow ADC read to wait for. On these platforms the ADC is
// scanned continiously in the background from an ISR. On such
// architectures that use the analog read during DCC waveform with
// specially configured ADC, for example AVR, init must be called
// PRIOR to the start of the waveform. It returns the current value so
// that an offset can be initialized.
class ADCee {
public:
// 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
// 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);
// 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
// it was called from ISR because for some implementations that
// makes a difference.
static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return
static int16_t ADCmax();
private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
// bit array of used pins (max 16)
static uint16_t usedpins;
static uint8_t highestPin;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;
// friend so that we can call scan() and begin()
friend class DCCWaveform;
};
#endif #endif

250
DCCTimerAVR.cpp Normal file
View File

@ -0,0 +1,250 @@
/*
* © 2021 Mike S
* © 2021-2023 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
// ATTENTION: this file only compiles on a UNO or MEGA
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_AVR
#include <avr/boot.h>
#include <avr/wdt.h>
#include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
INTERRUPT_CALLBACK interruptHandler=0;
// Arduino nano, uno, mega etc
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define TIMER1_A_PIN 11
#define TIMER1_B_PIN 12
#define TIMER1_C_PIN 13
#else
#define TIMER1_A_PIN 9
#define TIMER1_B_PIN 10
#endif
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
TCCR1A = 0;
ICR1 = CLOCK_CYCLES;
TCNT1 = 0;
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
interrupts();
}
// ISR called by timer interrupt every 58uS
ISR(TIMER1_OVF_vect){ interruptHandler(); }
// Alternative pin manipulation via PWM control.
bool DCCTimer::isPWMPin(byte pin) {
return pin==TIMER1_A_PIN
|| pin==TIMER1_B_PIN
#ifdef TIMER1_C_PIN
|| pin==TIMER1_C_PIN
#endif
;
}
void DCCTimer::setPWM(byte pin, bool high) {
if (pin==TIMER1_A_PIN) {
TCCR1A |= _BV(COM1A1);
OCR1A= high?1024:0;
}
else if (pin==TIMER1_B_PIN) {
TCCR1A |= _BV(COM1B1);
OCR1B= high?1024:0;
}
#ifdef TIMER1_C_PIN
else if (pin==TIMER1_C_PIN) {
TCCR1A |= _BV(COM1C1);
OCR1C= high?1024:0;
}
#endif
}
void DCCTimer::clearPWM() {
TCCR1A= 0;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
for (byte i=0; i<6; i++) {
// take the fist 3 and last 3 of the serial.
// the first 5 of 8 are at 0x0E to 0x013
// the last 3 of 8 are at 0x15 to 0x017
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
}
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
extern char *__brkval;
extern char *__malloc_heap_start;
int DCCTimer::freeMemory() {
char top;
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}
void DCCTimer::reset() {
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
}
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define NUM_ADC_INPUTS 16
#else
#define NUM_ADC_INPUTS 8
#endif
uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
static bool ADCusesHighPort = false;
/*
* Register a new pin to be scanned
* Returns current reading of pin and
* stores that as well
*/
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
if (id >= NUM_ADC_INPUTS)
return -1023;
if (id > 7)
ADCusesHighPort = true;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
if (id > highestPin) highestPin = id;
return value;
}
int16_t ADCee::ADCmax() {
return 1023;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
if (!fromISR) noInterrupts();
int a = analogvals[id];
if (!fromISR) interrupts();
return a;
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
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;
if (waiting) {
// look if we have a result
byte low, high;
if (bit_is_set(ADCSRA, ADSC))
return; // no result, continue to wait
// found value
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
analogvals[id] = (high << 8) | low;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
waiting = false;
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
#if defined(ADCSRB) && defined(MUX5)
if (ADCusesHighPort) { // if we ever have started to use high pins)
if (id > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit
else
bitClear(ADCSRB, MUX5);
}
#endif
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
// Set up ADC for free running mode
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
interrupts();
}
#endif

217
DCCTimerESP.cpp Normal file
View File

@ -0,0 +1,217 @@
/*
* © 2020-2022 Harald Barth
*
* 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/>.
*/
// ATTENTION: this file only compiles on an ESP8266 and ESP32
// On ESP32 we do not even use the functions but they are here for completeness sake
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_ESP8266
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
timer1_disable();
// There seem to be differnt ways to attach interrupt handler
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
// Let us choose the one from the API
timer1_attachInterrupt(interruptHandler);
// not exactly sure of order:
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
timer1_write(CLOCK_CYCLES);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
void IRAM_ATTR DCCTimer::clearPWM() {
}
// Fake this as it should not be used
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
mac[0] = 0xFE;
mac[1] = 0xBE;
mac[2] = 0xEF;
mac[3] = 0xC0;
mac[4] = 0xFF;
mac[5] = 0xEE;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
int DCCTimer::freeMemory() {
return ESP.getFreeHeap();
}
#endif
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#include <driver/adc.h>
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#undef ADC_INPUT_MAX_VALUE
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
int IRAM_ATTR local_adc1_get_raw(int channel) {
uint16_t adc_value;
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
while (SENS.sar_slave_addr1.meas_status != 0);
SENS.sar_meas_start1.meas1_start_sar = 0;
SENS.sar_meas_start1.meas1_start_sar = 1;
while (SENS.sar_meas_start1.meas1_done_sar == 0);
adc_value = SENS.sar_meas_start1.meas1_data_sar;
return adc_value;
}
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
// This should not be called on ESP32 so disable it
return;
interruptHandler = callback;
hw_timer_t *timer = NULL;
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
timerAttachInterrupt(timer, interruptHandler, true);
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
timerAlarmEnable(timer);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
void IRAM_ATTR DCCTimer::clearPWM() {
}
// Fake this as it should not be used
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
mac[0] = 0xFE;
mac[1] = 0xBE;
mac[2] = 0xEF;
mac[3] = 0xC0;
mac[4] = 0xFF;
mac[5] = 0xEE;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
int DCCTimer::freeMemory() {
return ESP.getFreeHeap();
}
void DCCTimer::reset() {
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) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
return adc1_get_raw(pinToADC1Channel(pin));
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
return local_adc1_get_raw(pinToADC1Channel(pin));
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
}
#endif //ESP32

155
DCCTimerMEGAAVR.cpp Normal file
View File

@ -0,0 +1,155 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
/* This timer class is used to manage the single timer required to handle the DCC waveform.
* All timer access comes through this class so that it can be compiled for
* various hardware CPU types.
*
* DCCEX works on a single timer interrupt at a regular 58uS interval.
* The DCCWaveform class generates the signals to the motor shield
* based on this timer.
*
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
* (see isPWMPin() function. )
* then this allows us to use a hardware driven pin switching arrangement which is
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
* the required pin state. (see setPWM())
* This is more accurate than the software interrupt but at the expense of
* limiting the choice of available pins.
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
*
* Because the PWM-based waveform is effectively set half a cycle after the software version,
* it is not acceptable to drive the two tracks on different methiods or it would cause
* problems for <1 JOIN> etc.
*
*/
// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_MEGAAVR
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
extern char *__brkval;
extern char *__malloc_heap_start;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
TCB0.CNT = 0;
TCB0.CTRLA |= TCB_ENABLE_bm; // start
interrupts();
}
// ISR called by timer interrupt every 58uS
ISR(TCB0_INT_vect){
TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag
interruptHandler();
}
bool DCCTimer::isPWMPin(byte pin) {
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
(void) pin;
(void) high;
// TODO what are the relevant pins?
}
void DCCTimer::clearPWM() {
// Do nothing unless we implent HA
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
extern char *__brkval;
extern char *__malloc_heap_start;
int DCCTimer::freeMemory() {
char top;
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}
void DCCTimer::reset() {
CPU_CCP=0xD8;
WDT.CTRLA=0x4;
while(true){}
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

277
DCCTimerSAMD.cpp Normal file
View File

@ -0,0 +1,277 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
// ATTENTION: this file only compiles on a SAMD21 based board
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_SAMD
#include "DCCTimer.h"
#include <wiring_private.h>
INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// Timer setup - setup clock sources first
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
GCLK_GENDIV_ID(4); // Apply to GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1
while (GCLK->STATUS.bit.SYNCBUSY);
// Assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway
// for "normal accuracy" DCC waveform generation. For high accuracy we're going to need
// to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and output
// pins for each TCC are only available on certain pins
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync
// Set the frequency
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
TCC0->PER.reg = CLOCK_CYCLES * 2;
while (TCC0->SYNCBUSY.bit.PER);
// Start the timer
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE);
// Set the interrupt condition, priority and enable it in the NVIC
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority
// NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt
interrupts();
}
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
// copied from rf24 branch
void TCC0_Handler() {
if(TCC0->INTFLAG.bit.OVF) {
TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
void TCC1_Handler() {
if(TCC1->INTFLAG.bit.OVF) {
TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
void TCC2_Handler() {
if(TCC2->INTFLAG.bit.OVF) {
TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
return false;
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO: High Accuracy mode is not supported as yet, and may never need to be
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
return;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
int DCCTimer::freeMemory() {
char top;
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
}
void DCCTimer::reset() {
__disable_irq();
NVIC_SystemReset();
while(true) {};
}
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
int value = 0;
if (id > NUM_ADC_INPUTS)
return -1023;
// Permanently configure SAMD IO MUX for that pin
pinPeripheral(pin, PIO_ANALOG);
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// Wait for the conversion to be ready
while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete
// Read the value
value = ADC->RESULT.reg;
if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
return value;
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
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 bool waiting = false;
if (waiting) {
// look if we have a result
if (ADC->INTFLAG.bit.RESRDY == 0)
return; // no result, continue to wait
// found value
analogvals[id] = ADC->RESULT.reg;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
// and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables
// 12-bit mode
// Reconfigure ADC
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
interrupts();
}
#endif

410
DCCTimerSTM32.cpp Normal file
View File

@ -0,0 +1,410 @@
/*
* © 2023 Neil McKechnie
* © 2022-23 Paul M. Antoine
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
// ATTENTION: this file only compiles on a STM32 based boards
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_STM32
#include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
// 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
// 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.
// 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
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have additional serial ports defined by default
// 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
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// 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
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
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// Experimental code for High Accuracy (HA) DCC Signal mode
// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins,
// which is used by the DC motor types.
///////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT_CALLBACK interruptHandler=0;
// // Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
// // Also, timer #3 will do hardware pulses on pin D12. This gives
// // accurate timing, independent of the latency of interrupt handling.
// // We only need to interrupt on one of these (TIM2), the other will just generate
// // pulses.
// HardwareTimer timer(TIM2);
// HardwareTimer timerAux(TIM3);
// static bool tim2ModeHA = false;
// static bool tim3ModeHA = false;
// // Timer IRQ handler
// void Timer_Handler() {
// interruptHandler();
// }
// void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
// interruptHandler=callback;
// noInterrupts();
// // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
// timer.pause();
// timerAux.pause();
// timer.setPrescaleFactor(1);
// timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// timer.attachInterrupt(Timer_Handler);
// timer.refresh();
// timerAux.setPrescaleFactor(1);
// timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// timerAux.refresh();
// timer.resume();
// timerAux.resume();
// interrupts();
// }
// bool DCCTimer::isPWMPin(byte pin) {
// // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
// // Enable the appropriate timer channel.
// switch (pin) {
// case 12:
// return true;
// case 13:
// return true;
// default:
// return false;
// }
// }
// void DCCTimer::setPWM(byte pin, bool high) {
// // Set the timer so that, at the next counter overflow, the requested
// // pin state is activated automatically before the interrupt code runs.
// // TIM2 is timer, TIM3 is timerAux.
// switch (pin) {
// case 12:
// if (!tim3ModeHA) {
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
// tim3ModeHA = true;
// }
// if (high)
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
// else
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
// break;
// case 13:
// if (!tim2ModeHA) {
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
// tim2ModeHA = true;
// }
// if (high)
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
// else
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
// break;
// }
// }
// void DCCTimer::clearPWM() {
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
// tim2ModeHA = false;
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
// tim3ModeHA = false;
// }
///////////////////////////////////////////////////////////////////////////////////////////////
INTERRUPT_CALLBACK interruptHandler=0;
// Let's use STM32's timer #11 until disabused of this notion
// Timer #11 is used for "servo" library, but as DCC-EX is not using
// this libary, we should be free and clear.
HardwareTimer timer(TIM11);
// Timer IRQ handler
void Timer11_Handler() {
interruptHandler();
}
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
timer.pause();
timer.setPrescaleFactor(1);
// timer.setOverflow(CLOCK_CYCLES * 2);
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timer.attachInterrupt(Timer11_Handler);
timer.refresh();
timer.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
return false;
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO: High Accuracy mode is not supported as yet, and may never need to be
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
return;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
int DCCTimer::freeMemory() {
char top;
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
}
void DCCTimer::reset() {
__disable_irq();
NVIC_SystemReset();
while(true) {};
}
// 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;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
int value = 0;
PinName stmpin = analogInputToPinName(pin);
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!)
GPIO_TypeDef * gpioBase;
// Port config - find which port we're on and power it up
switch(stmgpio) {
case 0x00:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
gpioBase = GPIOA;
break;
case 0x01:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB
gpioBase = GPIOB;
break;
case 0x02:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC;
break;
default:
return -1023; // some silly value as error
}
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
// 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)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
// Read the inital ADC value for this analog input
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
value = ADC1->DR; // Read value from register
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));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
}
analogvals[id] = value; // Store sampled value
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
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;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - PNUM_ANALOG_BASE;
// Was this pin initialised yet?
if ((usedpins & (1<<id) ) == 0)
return -1023;
// We do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
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 bool waiting = false;
if (waiting) {
// look if we have a result
if (!(ADC1->SR & (1 << 1)))
return; // no result, continue to wait
// found value
analogvals[id] = ADC1->DR;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
waiting = false;
id++;
mask = mask << 1;
if (id > highestPin) { // the 1 has been shifted out
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
//ADC1 config sequence
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC1->CR2 &= ~(1 << 1); //Single conversion
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->CR2 |= (1 << 0); // Switch on ADC1
interrupts();
}
#endif

171
DCCTimerTEENSY.cpp Normal file
View File

@ -0,0 +1,171 @@
/*
* © 2022 Paul M Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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/>.
*/
// ATTENTION: this file only compiles on a TEENSY
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef TEENSYDUINO
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
IntervalTimer myDCCTimer;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
}
bool DCCTimer::isPWMPin(byte pin) {
//Teensy: digitalPinHasPWM, todo
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO what are the relevant pins?
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
// Do nothing unless we implent HA
}
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
uint32_t m1 = HW_OCOTP_MAC1;
uint32_t m2 = HW_OCOTP_MAC0;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
#else
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
void teensyRead(uint8_t word, uint8_t *mac, uint8_t offset) {
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
FTFL_FCCOB1 = word; // read the given word of read once area
// launch command and wait until complete
FTFL_FSTAT = FTFL_FSTAT_CCIF;
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
teensyRead(0xe,mac,0);
teensyRead(0xf,mac,3);
}
#endif
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
#if !defined(__IMXRT1062__)
int DCCTimer::freeMemory() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
#else
#if defined(ARDUINO_TEENSY40)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
static const unsigned OCRAM_SIZE = 512;
static const unsigned FLASH_SIZE = 1984;
#elif defined(ARDUINO_TEENSY41)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
static const unsigned OCRAM_SIZE = 512;
static const unsigned FLASH_SIZE = 7936;
#if TEENSYDUINO>151
extern "C" uint8_t external_psram_size;
#endif
#endif
int DCCTimer::freeMemory() {
extern unsigned long _ebss;
extern unsigned long _sdata;
extern unsigned long _estack;
const unsigned DTCM_START = 0x20000000UL;
unsigned dtcm = (unsigned)&_estack - DTCM_START;
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
unsigned freemem = dtcm - (stackinuse + varsinuse);
return freemem;
}
#endif
void DCCTimer::reset() {
// found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically
SCB_AIRCR = 0x05FA0004;
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@ -2,7 +2,7 @@
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2021 Harald Barth * © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* All rights reserved. * All rights reserved.
* *
@ -21,43 +21,52 @@
* 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/>.
*/ */
#ifndef ARDUINO_ARCH_ESP32
// This code is replaced entirely on an ESP32
#include <Arduino.h> #include <Arduino.h>
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "TrackManager.h"
#include "DCCTimer.h" #include "DCCTimer.h"
#include "DCCACK.h"
#include "DIAG.h" #include "DIAG.h"
#include "freeMemory.h"
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
bool DCCWaveform::progTrackSyncMain=false;
bool DCCWaveform::progTrackBoosted=false;
int DCCWaveform::progTripValue=0;
volatile uint8_t DCCWaveform::numAckGaps=0;
volatile uint8_t DCCWaveform::numAckSamples=0;
uint8_t DCCWaveform::trailingEdgeCounter=0;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { // This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
mainTrack.motorDriver=mainDriver; const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
progTrack.motorDriver=progDriver;
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static const byte idlePacket[] = {0xFF, 0x00, 0xFF};
mainTrack.setPowerMode(POWERMODE::OFF); const byte resetPacket[] = {0x00, 0x00, 0x00};
progTrack.setPowerMode(POWERMODE::OFF);
// Fault pin config for odd motor boards (example pololu)
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) // For each state of the wave nextState=stateTransform[currentState]
&& (mainDriver->getFaultPin() != UNUSED_PIN)); const WAVE_STATE stateTransform[]={
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work /* WAVE_START -> */ WAVE_PENDING,
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); /* WAVE_MID_1 -> */ WAVE_START,
DIAG(F("Signal pin config: %S accuracy waveform"), /* WAVE_HIGH_0 -> */ WAVE_MID_0,
MotorDriver::usePWM ? F("high") : F("normal") ); /* WAVE_MID_0 -> */ WAVE_LOW_0,
/* WAVE_LOW_0 -> */ WAVE_START,
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
// For each state of the wave, signal pin is HIGH or LOW
const bool signalTransform[]={
/* WAVE_START -> */ HIGH,
/* WAVE_MID_1 -> */ LOW,
/* WAVE_HIGH_0 -> */ HIGH,
/* WAVE_MID_0 -> */ LOW,
/* WAVE_LOW_0 -> */ LOW,
/* WAVE_PENDING (should not happen) -> */ LOW};
void DCCWaveform::begin() {
DCCTimer::begin(DCCWaveform::interruptHandler); DCCTimer::begin(DCCWaveform::interruptHandler);
} }
void DCCWaveform::loop(bool ackManagerActive) { void DCCWaveform::loop() {
mainTrack.checkPowerOverload(false); // empty placemarker in case ESP32 needs something here
progTrack.checkPowerOverload(ackManagerActive);
} }
#pragma GCC push_options #pragma GCC push_options
@ -66,24 +75,26 @@ void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack // call the timer edge sensitive actions for progtrack and maintrack
// member functions would be cleaner but have more overhead // member functions would be cleaner but have more overhead
byte sigMain=signalTransform[mainTrack.state]; byte sigMain=signalTransform[mainTrack.state];
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
// Set the signal state for both tracks // Set the signal state for both tracks
mainTrack.motorDriver->setSignal(sigMain); TrackManager::setDCCSignal(sigMain);
progTrack.motorDriver->setSignal(sigProg); TrackManager::setPROGSignal(sigProg);
// Refresh the values in the ADCee object buffering the values of the ADC HW
ADCee::scan();
// Move on in the state engine // Move on in the state engine
mainTrack.state=stateTransform[mainTrack.state]; mainTrack.state=stateTransform[mainTrack.state];
progTrack.state=stateTransform[progTrack.state]; progTrack.state=stateTransform[progTrack.state];
// WAVE_PENDING means we dont yet know what the next bit is // WAVE_PENDING means we dont yet know what the next bit is
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
else if (progTrack.ackPending) progTrack.checkAck(); else DCCACK::checkAck(progTrack.getResets());
} }
#pragma GCC push_options #pragma GCC pop_options
// An instance of this class handles the DCC transmissions for one track. (main or prog) // An instance of this class handles the DCC transmissions for one track. (main or prog)
// Interrupts are marshalled via the statics. // Interrupts are marshalled via the statics.
@ -91,9 +102,6 @@ void DCCWaveform::interruptHandler() {
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer. // When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
isMainTrack = isMain; isMainTrack = isMain;
@ -105,104 +113,9 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
requiredPreambles = preambleBits+1; requiredPreambles = preambleBits+1;
bytes_sent = 0; bytes_sent = 0;
bits_sent = 0; bits_sent = 0;
sampleDelay = 0;
lastSampleTaken = millis();
ackPending=false;
}
POWERMODE DCCWaveform::getPowerMode() {
return powerMode;
}
void DCCWaveform::setPowerMode(POWERMODE mode) {
powerMode = mode;
bool ison = (mode == POWERMODE::ON);
motorDriver->setPower( ison);
sentResetsSincePacket=0;
} }
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= motorDriver->getRawCurrentTripValue();
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
tripValue=progTripValue;
// Trackname for diag messages later
const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG");
switch (powerMode) {
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
lastCurrent=motorDriver->getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (MotorDriver::commonFaultPin) {
if (lastCurrent <= tripValue) {
setPowerMode(POWERMODE::ON); // maybe other track
}
// Write this after the fact as we want to turn on as fast as possible
// because we don't know which output actually triggered the fault pin
DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname);
} else {
DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname);
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
}
}
if (lastCurrent < tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
else
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
} else {
setPowerMode(POWERMODE::OVERLOAD);
unsigned int mA=motorDriver->raw2mA(lastCurrent);
unsigned int maxmA=motorDriver->raw2mA(tripValue);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay);
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
}
// For each state of the wave nextState=stateTransform[currentState]
const WAVE_STATE DCCWaveform::stateTransform[]={
/* WAVE_START -> */ WAVE_PENDING,
/* WAVE_MID_1 -> */ WAVE_START,
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
/* WAVE_MID_0 -> */ WAVE_LOW_0,
/* WAVE_LOW_0 -> */ WAVE_START,
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
// For each state of the wave, signal pin is HIGH or LOW
const bool DCCWaveform::signalTransform[]={
/* WAVE_START -> */ HIGH,
/* WAVE_MID_1 -> */ LOW,
/* WAVE_HIGH_0 -> */ HIGH,
/* WAVE_MID_0 -> */ LOW,
/* WAVE_LOW_0 -> */ LOW,
/* WAVE_PENDING (should not happen) -> */ LOW};
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("-O3") #pragma GCC optimize ("-O3")
@ -216,7 +129,7 @@ void DCCWaveform::interrupt2() {
remainingPreambles--; remainingPreambles--;
// Update free memory diagnostic as we don't have anything else to do this time. // Update free memory diagnostic as we don't have anything else to do this time.
// Allow for checkAck and its called functions using 22 bytes more. // Allow for checkAck and its called functions using 22 bytes more.
updateMinimumFreeMemory(22); DCCTimer::updateMinimumFreeMemoryISR(22);
return; return;
} }
@ -249,21 +162,20 @@ void DCCWaveform::interrupt2() {
transmitLength = pendingLength; transmitLength = pendingLength;
transmitRepeats = pendingRepeats; transmitRepeats = pendingRepeats;
packetPending = false; packetPending = false;
sentResetsSincePacket=0; clearResets();
} }
else { else {
// Fortunately reset and idle packets are the same length // Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket)); memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket); transmitLength = sizeof(idlePacket);
transmitRepeats = 0; transmitRepeats = 0;
if (sentResetsSincePacket<250) sentResetsSincePacket++; if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
} }
} }
} }
} }
#pragma GCC pop_options #pragma GCC pop_options
// Wait until there is no packet pending, then make this pending // Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
@ -279,91 +191,93 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
pendingLength = byteCount + 1; pendingLength = byteCount + 1;
pendingRepeats = repeats; pendingRepeats = repeats;
packetPending = true; packetPending = true;
sentResetsSincePacket=0; clearResets();
}
bool DCCWaveform::getPacketPending() {
return packetPending;
}
#endif
#ifdef ARDUINO_ARCH_ESP32
#include "DCCWaveform.h"
#include "DCCACK.h"
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
isMainTrack = isMain;
requiredPreambles = preambleBits;
}
void DCCWaveform::begin() {
for(const auto& md: TrackManager::getMainDrivers()) {
pinpair p = md->getSignalPin();
if(rmtMainChannel) {
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
rmtMainChannel->addPin(p); // add pin to existing main channel
} else {
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
}
}
MotorDriver *md = TrackManager::getProgDriver();
if (md) {
pinpair p = md->getSignalPin();
if (rmtProgChannel) {
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
rmtProgChannel->addPin(p); // add pin to existing prog channel
} else {
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
rmtProgChannel = new RMTChannel(p, false);
}
}
} }
// Operations applicable to PROG track ONLY. void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
// (yes I know I could have subclassed the main track but...) if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
void DCCWaveform::setAckBaseline() { byte checksum = 0;
if (isMainTrack) return; for (byte b = 0; b < byteCount; b++) {
int baseline=motorDriver->getCurrentRaw(); checksum ^= buffer[b];
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); pendingPacket[b] = buffer[b];
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), }
baseline,motorDriver->raw2mA(baseline), // buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
ackThreshold,motorDriver->raw2mA(ackThreshold), pendingPacket[byteCount] = checksum;
minAckPulseDuration, maxAckPulseDuration); pendingLength = byteCount + 1;
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
clearResets(repeats+1);
{
int ret;
do {
if(isMainTrack) {
if (rmtMainChannel != NULL)
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
} else {
if (rmtProgChannel != NULL)
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
}
} while(ret > 0);
}
} }
void DCCWaveform::setAckPending() { bool DCCWaveform::getPacketPending() {
if (isMainTrack) return; if(isMainTrack) {
ackMaxCurrent=0; if (rmtMainChannel == NULL)
ackPulseStart=0; return true;
ackPulseDuration=0; return rmtMainChannel->busy();
ackDetected=false; } else {
ackCheckStart=millis(); if (rmtProgChannel == NULL)
numAckSamples=0; return true;
numAckGaps=0; return rmtProgChannel->busy();
ackPending=true; // interrupt routines will now take note }
} }
void IRAM_ATTR DCCWaveform::loop() {
byte DCCWaveform::getAck() { DCCACK::checkAck(progTrack.getResets());
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.
} }
#endif
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void DCCWaveform::checkAck() {
// This function operates in interrupt() time so must be fast and can't DIAG
if (sentResetsSincePacket > 6) { //ACK timeout
ackCheckDuration=millis()-ackCheckStart;
ackPending = false;
return;
}
int current=motorDriver->getCurrentRaw();
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
if (current>ackThreshold) {
if (trailingEdgeCounter > 0) {
numAckGaps++;
trailingEdgeCounter = 0;
}
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
// not in pulse
if (ackPulseStart==0) return; // keep waiting for leading edge
// if we reach to this point, we have
// detected trailing edge of pulse
if (trailingEdgeCounter == 0) {
ackPulseDuration=micros()-ackPulseStart;
}
// but we do not trust it yet and return (which will force another
// measurement) and first the third time around with low current
// the ack detection will be finalized.
if (trailingEdgeCounter < 2) {
trailingEdgeCounter++;
return;
}
trailingEdgeCounter = 0;
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;
transmitRepeats=0; // shortcut remaining repeat packets
return; // we have a genuine ACK result
}
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
}
#pragma GCC pop_options

View File

@ -25,107 +25,70 @@
#define DCCWaveform_h #define DCCWaveform_h
#include "MotorDriver.h" #include "MotorDriver.h"
#ifdef ARDUINO_ARCH_ESP32
#include "DCCRMT.h"
#include "TrackManager.h"
#endif
// Wait times for power management. Unit: milliseconds
const int POWER_SAMPLE_ON_WAIT = 100;
const int POWER_SAMPLE_OFF_WAIT = 1000;
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
// Number of preamble bits. // Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22; const int PREAMBLE_BITS_PROG = 22;
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic // The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
// to the transform array. // to the transform array.
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5}; enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
// NOTE: static functions are used for the overall controller, then // NOTE: static functions are used for the overall controller, then
// one instance is created for each track. // one instance is created for each track.
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
const byte resetPacket[] = {0x00, 0x00, 0x00};
class DCCWaveform { class DCCWaveform {
public: public:
DCCWaveform( byte preambleBits, bool isMain); DCCWaveform( byte preambleBits, bool isMain);
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver); static void begin();
static void loop(bool ackManagerActive); static void loop();
static DCCWaveform mainTrack; static DCCWaveform mainTrack;
static DCCWaveform progTrack; static DCCWaveform progTrack;
inline void clearRepeats() { transmitRepeats=0; }
void beginTrack(); #ifndef ARDUINO_ARCH_ESP32
void setPowerMode(POWERMODE); inline void clearResets() { sentResetsSincePacket=0; }
POWERMODE getPowerMode(); inline byte getResets() { return sentResetsSincePacket; }
void checkPowerOverload(bool ackManagerActive); #else
inline int get1024Current() { // extrafudge is added when we know that the resets will first come extrafudge packets in the future
if (powerMode == POWERMODE::ON) inline void clearResets(byte extrafudge=0) {
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return;
resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
resetPacketBase += extrafudge;
};
inline byte getResets() {
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0;
uint32_t packetcount = isMainTrack ?
rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic.
if (count > UINT32_MAX/2) // we are in the extrafudge area
return 0; return 0;
} if (count > 255) // cap to 255
inline int getCurrentmA() { return 255;
if (powerMode == POWERMODE::ON) return count; // all special cases handled above
return motorDriver->raw2mA(lastCurrent); };
return 0; #endif
}
inline int getMaxmA() {
if (maxmA == 0) { //only calculate this for first request, it doesn't change
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
}
return maxmA;
}
inline int getTripmA() {
if (tripmA == 0) { //only calculate this for first request, it doesn't change
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
}
return tripmA;
}
void schedulePacket(const byte buffer[], byte byteCount, byte repeats); void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending; bool getPacketPending();
volatile byte sentResetsSincePacket;
volatile bool autoPowerOff=false;
void setAckBaseline(); //prog track only
void setAckPending(); //prog track only
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
inline void doAutoPowerOff() {
if (autoPowerOff) {
setPowerMode(POWERMODE::OFF);
autoPowerOff=false;
}
};
inline bool canMeasureCurrent() {
return motorDriver->canMeasureCurrent();
};
inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
inline void setMinAckPulseDuration(unsigned int i) {
minAckPulseDuration = i;
}
inline void setMaxAckPulseDuration(unsigned int i) {
maxAckPulseDuration = i;
}
private: private:
#ifndef ARDUINO_ARCH_ESP32
// For each state of the wave nextState=stateTransform[currentState] volatile bool packetPending;
static const WAVE_STATE stateTransform[6]; volatile byte sentResetsSincePacket;
#else
// For each state of the wave, signal pin is HIGH or LOW volatile uint32_t resetPacketBase;
static const bool signalTransform[6]; #endif
static void interruptHandler(); static void interruptHandler();
void interrupt2(); void interrupt2();
void checkAck();
bool isMainTrack; bool isMainTrack;
MotorDriver* motorDriver;
// Transmission controller // Transmission controller
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte transmitLength; byte transmitLength;
@ -138,38 +101,9 @@ class DCCWaveform {
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte pendingLength; byte pendingLength;
byte pendingRepeats; byte pendingRepeats;
int lastCurrent; #ifdef ARDUINO_ARCH_ESP32
static int progTripValue; static RMTChannel *rmtMainChannel;
int maxmA; static RMTChannel *rmtProgChannel;
int tripmA; #endif
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
// ACK management (Prog track only)
volatile bool ackPending;
volatile bool ackDetected;
int ackThreshold;
int ackLimitmA = 50;
int ackMaxCurrent;
unsigned long ackCheckStart; // millis
unsigned int ackCheckDuration; // millis
unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros
unsigned int minAckPulseDuration = 2000; // micros
unsigned int maxAckPulseDuration = 20000; // micros
volatile static uint8_t numAckGaps;
volatile static uint8_t numAckSamples;
static uint8_t trailingEdgeCounter;
}; };
#endif #endif

1
DIAG.h
View File

@ -24,4 +24,5 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define DIAG StringFormatter::diag #define DIAG StringFormatter::diag
#define LCD StringFormatter::lcd #define LCD StringFormatter::lcd
#define SCREEN StringFormatter::lcd2
#endif #endif

219
Display.cpp Normal file
View File

@ -0,0 +1,219 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// CAUTION: the device dependent parts of this class are created in the .ini
// using LCD_Implementation.h
/* The strategy for drawing the screen is as follows.
* 1) There are up to eight rows of text to be displayed.
* 2) Blank rows of text are ignored.
* 3) If there are more non-blank rows than screen lines,
* then all of the rows are displayed, with the rest of the
* screen being blank.
* 4) If there are fewer non-blank rows than screen lines,
* then a scrolling strategy is adopted so that, on each screen
* refresh, a different subset of the rows is presented.
* 5) On each entry into loop2(), a single operation is sent to the
* screen; this may be a position command or a character for
* display. This spreads the onerous work of updating the screen
* and ensures that other loop() functions in the application are
* not held up significantly. The exception to this is when
* the loop2() function is called with force=true, where
* a screen update is executed to completion. This is normally
* only done during start-up.
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
* in the config.h.
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
* #define SCROLLMODE 1 is by page (alternate between pages),
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
*/
#include "Display.h"
// Constructor - allocates device driver.
Display::Display(DisplayDevice *deviceDriver) {
_deviceDriver = deviceDriver;
// Get device dimensions in characters (e.g. 16x2).
numScreenColumns = _deviceDriver->getNumCols();
numScreenRows = _deviceDriver->getNumRows();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
addDisplay(0); // Add this display as display number 0
};
void Display::begin() {
_deviceDriver->begin();
_deviceDriver->clearNative();
}
void Display::_clear() {
_deviceDriver->clearNative();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
}
void Display::_setRow(uint8_t line) {
hotRow = line;
hotCol = 0;
rowBuffer[hotRow][0] = '\0'; // Clear existing text
}
size_t Display::_write(uint8_t b) {
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
rowBuffer[hotRow][hotCol] = b;
hotCol++;
rowBuffer[hotRow][hotCol] = '\0';
return 1;
}
// Refresh screen completely (will block until complete). Used
// during start-up.
void Display::_refresh() {
loop2(true);
}
// On normal loop entries, loop will only make one output request on each
// entry, to avoid blocking while waiting for the I2C.
void Display::_displayLoop() {
// If output device is busy, don't do anything on this loop
// This avoids blocking while waiting for the device to complete.
if (!_deviceDriver->isBusy()) loop2(false);
}
Display *Display::loop2(bool force) {
unsigned long currentMillis = millis();
if (!force) {
// See if we're in the time between updates
if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME)
return NULL;
} else {
// force full screen update from the beginning.
rowFirst = 0;
rowCurrent = 0;
bufferPointer = 0;
noMoreRowsToDisplay = false;
slot = 0;
}
do {
if (bufferPointer == 0) {
// Search for non-blank row
while (!noMoreRowsToDisplay) {
if (!isCurrentRowBlank()) break;
moveToNextRow();
if (rowCurrent == rowFirst) noMoreRowsToDisplay = true;
}
if (noMoreRowsToDisplay) {
// No non-blank lines left, so draw blank line
buffer[0] = '\0';
} else {
// Non-blank line found, so copy it (including terminator)
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
buffer[i] = rowBuffer[rowCurrent][i];
}
_deviceDriver->setRowNative(slot); // Set position for display
charIndex = 0;
bufferPointer = &buffer[0];
} else {
// Write next character, or a space to erase current position.
char ch = *bufferPointer;
if (ch) {
_deviceDriver->writeNative(ch);
bufferPointer++;
} else {
_deviceDriver->writeNative(' ');
}
if (++charIndex >= MAX_CHARACTER_COLS) {
// Screen slot completed, move to next nonblank row
bufferPointer = 0;
for (;;) {
moveToNextRow();
if (rowCurrent == rowFirst) {
noMoreRowsToDisplay = true;
break;
}
if (!isCurrentRowBlank()) break;
}
// Move to next screen slot, if available
slot++;
if (slot >= numScreenRows) {
// Last slot on screen written, so get ready for next screen update.
#if SCROLLMODE==0
// Scrollmode 0 scrolls continuously. If the rows fit on the screen,
// then restart at row 0, but otherwise continue with the row
// after the last one displayed.
if (countNonBlankRows() <= numScreenRows)
rowCurrent = 0;
rowFirst = rowCurrent;
#elif SCROLLMODE==1
// Scrollmode 1 scrolls by page, so if the last page has just completed then
// next time restart with row 0.
if (noMoreRowsToDisplay)
rowFirst = rowCurrent = 0;
#else
// Scrollmode 2 scrolls by row. If the rows don't fit on the screen,
// then start one row further on next time. If they do fit, then
// show them in order and start next page at row 0.
if (countNonBlankRows() <= numScreenRows) {
rowFirst = rowCurrent = 0;
} else {
// Find first non-blank row after the previous first row
rowCurrent = rowFirst;
do {
moveToNextRow();
} while (isCurrentRowBlank());
rowFirst = rowCurrent;
}
#endif
noMoreRowsToDisplay = false;
slot = 0;
lastScrollTime = currentMillis;
return NULL;
}
}
}
} while (force);
return NULL;
}
bool Display::isCurrentRowBlank() {
return (rowBuffer[rowCurrent][0] == '\0');
}
void Display::moveToNextRow() {
// Skip blank rows
if (++rowCurrent >= MAX_CHARACTER_ROWS)
rowCurrent = 0;
}
uint8_t Display::countNonBlankRows() {
uint8_t count = 0;
for (uint8_t rowNumber=0; rowNumber<MAX_CHARACTER_ROWS; rowNumber++) {
if (rowBuffer[rowNumber][0] != '\0')
count++;
}
return count;
}

View File

@ -16,8 +16,8 @@
* 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/>.
*/ */
#ifndef LCDDisplay_h #ifndef Display_h
#define LCDDisplay_h #define Display_h
#include <Arduino.h> #include <Arduino.h>
#include "defines.h" #include "defines.h"
#include "DisplayInterface.h" #include "DisplayInterface.h"
@ -32,50 +32,46 @@
#define SCROLLMODE 1 #define SCROLLMODE 1
#endif #endif
// This class is created in LCDisplay_Implementation.h // This class is created in Display_Implementation.h
class LCDDisplay : public DisplayInterface { class Display : public DisplayInterface {
public: public:
LCDDisplay() {}; Display(DisplayDevice *deviceDriver);
static const int MAX_LCD_ROWS = 8; static const int MAX_CHARACTER_ROWS = 8;
static const int MAX_LCD_COLS = MAX_MSG_SIZE; static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
static const long LCD_SCROLL_TIME = 3000; // 3 seconds static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
// Internally handled functions private:
static void loop(); DisplayDevice *_deviceDriver;
LCDDisplay* loop2(bool force) override;
void setRow(byte line) override;
void clear() override;
size_t write(uint8_t b) override;
protected:
uint8_t lcdRows;
uint8_t lcdCols;
private:
void moveToNextRow();
void skipBlankRows();
// Relay functions to the live driver in the subclass
virtual void clearNative() = 0;
virtual void setRowNative(byte line) = 0;
virtual size_t writeNative(uint8_t b) = 0;
virtual bool isBusy() = 0;
unsigned long lastScrollTime = 0; unsigned long lastScrollTime = 0;
int8_t hotRow = 0; uint8_t hotRow = 0;
int8_t hotCol = 0; uint8_t hotCol = 0;
int8_t topRow = 0; uint8_t slot = 0;
int8_t slot = 0; uint8_t rowFirst = 0;
int8_t rowFirst = -1; uint8_t rowCurrent = 0;
int8_t rowNext = 0; uint8_t charIndex = 0;
int8_t charIndex = 0; char buffer[MAX_CHARACTER_COLS + 1];
char buffer[MAX_LCD_COLS + 1];
char* bufferPointer = 0; char* bufferPointer = 0;
bool done = false; bool noMoreRowsToDisplay = false;
uint16_t numScreenRows;
uint16_t numScreenColumns = MAX_CHARACTER_COLS;
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
public:
void begin() override;
void _clear() override;
void _setRow(uint8_t line) override;
size_t _write(uint8_t b) override;
void _refresh() override;
void _displayLoop() override;
Display *loop2(bool force);
bool findNonBlankRow();
bool isCurrentRowBlank();
void moveToNextRow();
uint8_t countNonBlankRows();
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
}; };
#endif #endif

View File

@ -21,4 +21,7 @@
#include "DisplayInterface.h" #include "DisplayInterface.h"
DisplayInterface *DisplayInterface::lcdDisplay = 0; // Install null display driver initially - will be replaced if required.
DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface();
uint8_t DisplayInterface::_selectedDisplayNo = 255;

View File

@ -25,13 +25,75 @@
// Definition of base class for displays. The base class does nothing. // Definition of base class for displays. The base class does nothing.
class DisplayInterface : public Print { class DisplayInterface : public Print {
public: protected:
virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; static DisplayInterface *_displayHandler;
virtual void setRow(byte line) { (void)line; }; static uint8_t _selectedDisplayNo; // Nothing selected.
virtual void clear() {}; DisplayInterface *_nextHandler = NULL;
virtual size_t write(uint8_t c) { (void)c; return 0; }; uint8_t _displayNo = 0;
static DisplayInterface *lcdDisplay; public:
// Add display object to list of displays
void addDisplay(uint8_t displayNo) {
_nextHandler = _displayHandler;
_displayHandler = this;
_displayNo = displayNo;
}
static DisplayInterface *getDisplayHandler() {
return _displayHandler;
}
uint8_t getDisplayNo() {
return _displayNo;
}
// The next functions are to provide compatibility with calls to the LCD function
// which does not specify a display number. These always apply to display '0'.
static void refresh() { refresh(0); };
static void setRow(uint8_t line) { setRow(0, line); };
static void clear() { clear(0); };
// Additional functions to support multiple displays. These perform a
// multicast to all displays that match the selected displayNo.
// Display number zero is the default one.
static void setRow(uint8_t displayNo, uint8_t line) {
_selectedDisplayNo = displayNo;
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) {
if (displayNo == p->_displayNo) p->_setRow(line);
}
}
size_t write (uint8_t c) override {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (_selectedDisplayNo == p->_displayNo) p->_write(c);
return _displayHandler ? 1 : 0;
}
static void clear(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_clear();
}
static void refresh(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_refresh();
}
static void loop() {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
p->_displayLoop();
};
// The following are overridden within the specific device class
virtual void begin() {};
virtual size_t _write(uint8_t c) { (void)c; return 0; };
virtual void _setRow(uint8_t line) { (void)line; }
virtual void _clear() {}
virtual void _refresh() {}
virtual void _displayLoop() {}
}; };
class DisplayDevice {
public:
virtual bool begin() { return true; }
virtual void clearNative() = 0;
virtual void setRowNative(uint8_t line) = 0;
virtual size_t writeNative(uint8_t c) = 0;
virtual bool isBusy() = 0;
virtual uint16_t getNumRows() = 0;
virtual uint16_t getNumCols() = 0;
};
#endif #endif

View File

@ -27,27 +27,34 @@
#ifndef LCD_Implementation_h #ifndef LCD_Implementation_h
#define LCD_Implementation_h #define LCD_Implementation_h
#include "LCDDisplay.h" #include "DisplayInterface.h"
#include "SSD1306Ascii.h" #include "SSD1306Ascii.h"
#include "LiquidCrystal_I2C.h" #include "LiquidCrystal_I2C.h"
// Implement the LCDDisplay shim class as a singleton. // Implement the Display shim class as a singleton.
// The DisplayInterface class implements a displayy handler with no code (null device); // The DisplayInterface class implements a display handler with no code (null device);
// The LCDDisplay class sub-classes DisplayInterface to provide the common display code; // The Display class sub-classes DisplayInterface to provide the common display code;
// Then LCDDisplay class is subclassed to the specific device type classes: // Then Display class talks to the specific device type classes:
// SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers;
// LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'.
#if defined(OLED_DRIVER) #if defined(OLED_DRIVER)
#define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \
t->begin(); \
xxx; \
t->refresh(); \
}
#elif defined(LCD_DRIVER) #elif defined(LCD_DRIVER)
#define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \
t->begin(); \
xxx; \
t->refresh();}
#else #else
// Create null display handler just in case someone calls lcdDisplay->something without checking if lcdDisplay is NULL! #define DISPLAY_START(xxx) {}
#define CONDITIONAL_LCD_START { new DisplayInterface(); }
#endif
#endif
#endif // LCD_Implementation_h #endif // LCD_Implementation_h

View File

@ -1,7 +1,7 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2021 Harald Barth * © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2013-2016 Gregg E. Berman * © 2013-2016 Gregg E. Berman
* All rights reserved. * All rights reserved.
@ -31,12 +31,12 @@
#include "Sensors.h" #include "Sensors.h"
#include "Turnouts.h" #include "Turnouts.h"
#if defined(ARDUINO_ARCH_SAMD) #if defined(ARDUINO_ARCH_SAMC)
ExternalEEPROM EEPROM; ExternalEEPROM EEPROM;
#endif #endif
void EEStore::init() { void EEStore::init() {
#if defined(ARDUINO_ARCH_SAMD) #if defined(ARDUINO_ARCH_SAMC)
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three
// A pins grounded (0b1010000 = 0x50) // A pins grounded (0b1010000 = 0x50)
#endif #endif
@ -49,7 +49,7 @@ void EEStore::init() {
if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) { if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) {
// if not, create blank eeStore structure (no // if not, create blank eeStore structure (no
// turnouts, no sensors) and save it back to EEPROM // turnouts, no sensors) and save it back to EEPROM
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)); strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0);
eeStore->data.nTurnouts = 0; eeStore->data.nTurnouts = 0;
eeStore->data.nSensors = 0; eeStore->data.nSensors = 0;
eeStore->data.nOutputs = 0; eeStore->data.nOutputs = 0;
@ -98,7 +98,7 @@ int EEStore::pointer() { return (eeAddress); }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void EEStore::dump(int num) { void EEStore::dump(int num) {
byte b; byte b = 0;
DIAG(F("Addr 0x char")); DIAG(F("Addr 0x char"));
for (int n = 0; n < num; n++) { for (int n = 0; n < num; n++) {
EEPROM.get(n, b); EEPROM.get(n, b);

View File

@ -26,7 +26,7 @@
#include <Arduino.h> #include <Arduino.h>
#if defined(ARDUINO_ARCH_SAMD) #if defined(ARDUINO_ARCH_SAMC)
#include <SparkFun_External_EEPROM.h> #include <SparkFun_External_EEPROM.h>
extern ExternalEEPROM EEPROM; extern ExternalEEPROM EEPROM;
#else #else

Binary file not shown.

View File

@ -1,7 +1,8 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021-2022 Harald Barth * © 2021-2023 Harald Barth
* © 2020-2022 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
@ -24,8 +25,8 @@
F1. [DONE] DCC accessory packet opcodes (short and long form) F1. [DONE] DCC accessory packet opcodes (short and long form)
F2. [DONE] ONAccessory catchers F2. [DONE] ONAccessory catchers
F3. [DONE] Turnout descriptions for Withrottle F3. [DONE] Turnout descriptions for Withrottle
F4. Oled announcements (depends on HAL) F4. [DONE] Oled announcements (depends on HAL)
F5. Withrottle roster info F5. [DONE] Withrottle roster info
F6. Multi-occupancy semaphore F6. Multi-occupancy semaphore
F7. [DONE see AUTOSTART] Self starting sequences F7. [DONE see AUTOSTART] Self starting sequences
F8. Park/unpark F8. Park/unpark
@ -41,6 +42,7 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
#include "DCC.h" #include "DCC.h"
#include "DCCWaveform.h" #include "DCCWaveform.h"
@ -49,7 +51,7 @@
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "Turnouts.h" #include "Turnouts.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "TrackManager.h"
// Command parsing keywords // Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435; const int16_t HASH_KEYWORD_EXRAIL=15435;
@ -87,11 +89,27 @@ LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL; LookList * RMFT2::onCloseLookup=NULL;
LookList * RMFT2::onActivateLookup=NULL; LookList * RMFT2::onActivateLookup=NULL;
LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
LookList * RMFT2::onChangeLookup=NULL;
LookList * RMFT2::onClockLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter) #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#define SKIPOP progCounter+=3 #define SKIPOP progCounter+=3
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
return getOperand(progCounter,n);
}
// getOperand static version, must be provided prog counter from loop etc.
uint16_t RMFT2::getOperand(int progCounter,byte n) {
int offset=progCounter+1+(n*3);
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
LookList::LookList(int16_t size) { LookList::LookList(int16_t size) {
m_size=size; m_size=size;
@ -116,130 +134,115 @@ int16_t LookList::find(int16_t value) {
return -1; return -1;
} }
/* static */ void RMFT2::begin() { LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
int progCounter; int progCounter;
int16_t count=0;
// counters to create lookup arrays // find size for list
int sequenceCount=0; // to allow for seq 0 at start
int onThrowCount=0;
int onCloseCount=0;
int onActivateCount=0;
int onDeactivateCount=0;
// first pass count sizes for fast lookup arrays
for (progCounter=0;; SKIPOP) { for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE; byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break; if (opcode==OPCODE_ENDEXRAIL) break;
switch (opcode) { if (opcode==op1 || opcode==op2 || opcode==op3) count++;
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
sequenceCount++;
break;
case OPCODE_ONTHROW:
onThrowCount++;
break;
case OPCODE_ONCLOSE:
onCloseCount++;
break;
case OPCODE_ONACTIVATE:
onActivateCount++;
break;
case OPCODE_ONDEACTIVATE:
onDeactivateCount++;
break;
default: // Ignore
break;
} }
// create list
LookList* list=new LookList(count);
if (count==0) return list;
for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
} }
return list;
}
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
// create lookups // create lookups
sequenceLookup=new LookList(sequenceCount); sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
onThrowLookup=new LookList(onThrowCount); onThrowLookup=LookListLoader(OPCODE_ONTHROW);
onCloseLookup=new LookList(onCloseCount); onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=new LookList(onActivateCount); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
onDeactivateLookup=new LookList(onDeactivateCount); onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
// Second pass startup, define any turnouts or servos, set signals red // Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups // add sequences onRoutines to the lookups
for (int sigpos=0;;sigpos+=4) { for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED); doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
} }
int progCounter;
for (progCounter=0;; SKIPOP){ for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE; byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break; if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0); VPIN operand=getOperand(progCounter,0);
switch (opcode) { switch (opcode) {
case OPCODE_AT: case OPCODE_AT:
case OPCODE_ATTIMEOUT2:
case OPCODE_AFTER: case OPCODE_AFTER:
case OPCODE_IF: case OPCODE_IF:
case OPCODE_IFNOT: { case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand; int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin; if (pin<0) pin = -pin;
DIAG(F("EXRAIL input VPIN %u"),pin);
IODevice::configureInput((VPIN)pin,true); IODevice::configureInput((VPIN)pin,true);
break; break;
} }
case OPCODE_ATGTE:
case OPCODE_ATLT:
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: { case OPCODE_TURNOUT: {
VPIN id=operand; VPIN id=operand;
int addr=GET_OPERAND(1); int addr=getOperand(progCounter,1);
byte subAddr=GET_OPERAND(2); byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr)); setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break; break;
} }
case OPCODE_SERVOTURNOUT: { case OPCODE_SERVOTURNOUT: {
VPIN id=operand; VPIN id=operand;
VPIN pin=GET_OPERAND(1); VPIN pin=getOperand(progCounter,1);
int activeAngle=GET_OPERAND(2); int activeAngle=getOperand(progCounter,2);
int inactiveAngle=GET_OPERAND(3); int inactiveAngle=getOperand(progCounter,3);
int profile=GET_OPERAND(4); int profile=getOperand(progCounter,4);
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile)); setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
break; break;
} }
case OPCODE_PINTURNOUT: { case OPCODE_PINTURNOUT: {
VPIN id=operand; VPIN id=operand;
VPIN pin=GET_OPERAND(1); VPIN pin=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin)); setTurnoutHiddenState(VpinTurnout::create(id,pin));
break; break;
} }
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
sequenceLookup->add(operand,progCounter);
break;
case OPCODE_ONTHROW:
onThrowLookup->add(operand,progCounter);
break;
case OPCODE_ONCLOSE:
onCloseLookup->add(operand,progCounter);
break;
case OPCODE_ONACTIVATE:
onActivateLookup->add(operand,progCounter);
break;
case OPCODE_ONDEACTIVATE:
onDeactivateLookup->add(operand,progCounter);
break;
case OPCODE_AUTOSTART: case OPCODE_AUTOSTART:
// automatically create a task from here at startup. // automatically create a task from here at startup.
// Removed if (progCounter>0) check 4.2.31 because
// default start it top of file is now removed. .
new RMFT2(progCounter); new RMFT2(progCounter);
break; break;
@ -249,30 +252,31 @@ int16_t LookList::find(int16_t value) {
} }
SKIPOP; // include ENDROUTES opcode SKIPOP; // include ENDROUTES opcode
DIAG(F("EXRAIL %db, fl=%d seq=%d, onT=%d, onC=%d"), DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
progCounter,MAX_FLAGS,
sequenceCount, onThrowCount, onCloseCount);
new RMFT2(0); // add the startup route // Removed for 4.2.31 new RMFT2(0); // add the startup route
diag=saved_diag;
} }
void RMFT2::setTurnoutHiddenState(Turnout * t) { void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01); t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
} }
char RMFT2::getRouteType(int16_t id) { char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i++) { for (int16_t i=0;;i+=2) {
int16_t rid= GETFLASHW(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++) { for (int16_t i=0;;i+=2) {
int16_t rid= GETFLASHW(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
@ -329,7 +333,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals // do the signals
// flags[n] represents the state of the nth signal in the table // flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) { for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"), StringFormatter::send(stream,F("\n%S[%d]"),
@ -383,12 +387,13 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
return true; return true;
} }
// all other / commands take 1 parameter 0 to MAX_FLAGS-1 // all other / commands take 1 parameter
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false; if (paramCount!=2 ) return false;
switch (p[0]) { switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid|ALL case HASH_KEYWORD_KILL: // Kill taskid|ALL
{ {
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
RMFT2 * task=loopTask; RMFT2 * task=loopTask;
while(task) { while(task) {
if (task->taskId==p[1]) { if (task->taskId==p[1]) {
@ -402,20 +407,16 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
return false; return false;
case HASH_KEYWORD_RESERVE: // force reserve a section case HASH_KEYWORD_RESERVE: // force reserve a section
setFlag(p[1],SECTION_FLAG); return setFlag(p[1],SECTION_FLAG);
return true;
case HASH_KEYWORD_FREE: // force free a section case HASH_KEYWORD_FREE: // force free a section
setFlag(p[1],0,SECTION_FLAG); return setFlag(p[1],0,SECTION_FLAG);
return true;
case HASH_KEYWORD_LATCH: case HASH_KEYWORD_LATCH:
setFlag(p[1], LATCH_FLAG); return setFlag(p[1], LATCH_FLAG);
return true;
case HASH_KEYWORD_UNLATCH: case HASH_KEYWORD_UNLATCH:
setFlag(p[1], 0, LATCH_FLAG); return setFlag(p[1], 0, LATCH_FLAG);
return true;
case HASH_KEYWORD_RED: case HASH_KEYWORD_RED:
doSignal(p[1],SIGNAL_RED); doSignal(p[1],SIGNAL_RED);
@ -461,7 +462,7 @@ RMFT2::RMFT2(int progCtr) {
invert=false; invert=false;
timeoutFlag=false; timeoutFlag=false;
stackDepth=0; stackDepth=0;
onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE onEventStartPosition=-1; // Not handling an ONxxx
// chain into ring of RMFTs // chain into ring of RMFTs
if (loopTask==NULL) { if (loopTask==NULL) {
@ -498,10 +499,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
void RMFT2::driveLoco(byte speed) { void RMFT2::driveLoco(byte speed) {
if (loco<=0) return; // Prevent broadcast! if (loco<=0) return; // Prevent broadcast!
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { /* TODO.....
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); power on appropriate track if DC or main if dcc
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
TrackManager::setMainPower(POWERMODE::ON);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
} }
**********/
DCC::setThrottle(loco,speed, forward^invert); DCC::setThrottle(loco,speed, forward^invert);
speedo=speed; speedo=speed;
} }
@ -575,7 +580,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return; if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = GET_OPCODE; byte opcode = GET_OPCODE;
int16_t operand = GET_OPERAND(0); int16_t operand = getOperand(0);
// skipIf will get set to indicate a failing IF condition // skipIf will get set to indicate a failing IF condition
bool skipIf=false; bool skipIf=false;
@ -605,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;
@ -641,13 +647,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false; timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break; if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50); delayMe(50);
return; return;
case OPCODE_ATLT: // wait for analog sensor < value case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false; timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break; if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50); delayMe(50);
return; return;
@ -658,7 +664,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2: case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) { if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true; timeoutFlag=true;
break; // and drop through break; // and drop through
} }
@ -701,16 +707,25 @@ void RMFT2::loop2() {
break; break;
case OPCODE_POM: case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break; break;
case OPCODE_POWEROFF: case OPCODE_POWEROFF:
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); TrackManager::setPower(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); TrackManager::setJoin(false);
DCC::setProgTrackSyncMain(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
break; break;
case OPCODE_SET_TRACK:
// operand is trackmode<<8 | track id
// If DC/DCX use my loco for DC address
{
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
}
break;
case OPCODE_RESUME: case OPCODE_RESUME:
pausingTask=NULL; pausingTask=NULL;
driveLoco(speedo); driveLoco(speedo);
@ -726,19 +741,27 @@ void RMFT2::loop2() {
break; break;
case OPCODE_IFGTE: // do next operand if sensor>= value case OPCODE_IFGTE: // do next operand if sensor>= value
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1)); skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
break; break;
case OPCODE_IFLT: // do next operand if sensor< value case OPCODE_IFLT: // do next operand if sensor< value
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1)); skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
break;
case OPCODE_IFLOCO: // do if the loco is the active one
skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL
break; break;
case OPCODE_IFNOT: // do next operand if sensor not set case OPCODE_IFNOT: // do next operand if sensor not set
skipIf=readSensor(operand); skipIf=readSensor(operand);
break; break;
case OPCODE_IFRE: // do next operand if rotary encoder != position
skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1));
break;
case OPCODE_IFRANDOM: // do block on random percentage case OPCODE_IFRANDOM: // do block on random percentage
skipIf=(int16_t)random(100)>=operand; skipIf=(uint8_t)micros() >= operand * 255/100;
break; break;
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
@ -782,7 +805,7 @@ void RMFT2::loop2() {
break; break;
case OPCODE_RANDWAIT: case OPCODE_RANDWAIT:
delayMe(random(operand)*100L); delayMe(operand==0 ? 0 : (micros()%operand) *100L);
break; break;
case OPCODE_RED: case OPCODE_RED:
@ -813,11 +836,11 @@ void RMFT2::loop2() {
} }
case OPCODE_XFON: case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true); DCC::setFn(operand,getOperand(1),true);
break; break;
case OPCODE_XFOFF: case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false); DCC::setFn(operand,getOperand(1),false);
break; break;
case OPCODE_DCCACTIVATE: { case OPCODE_DCCACTIVATE: {
@ -861,21 +884,15 @@ 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:
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); TrackManager::setPower(POWERMODE::ON);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); TrackManager::setJoin(true);
DCC::setProgTrackSyncMain(true);
CommandDistributor::broadcastPower();
break;
case OPCODE_POWERON:
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
break; break;
case OPCODE_UNJOIN: case OPCODE_UNJOIN:
DCC::setProgTrackSyncMain(false); TrackManager::setJoin(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
break; break;
@ -899,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:
{ {
@ -910,7 +934,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route case OPCODE_SENDLOCO: // cab, route
{ {
int newPc=sequenceLookup->find(GET_OPERAND(1)); int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break; if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand; newtask->loco=operand;
@ -928,7 +952,7 @@ void RMFT2::loop2() {
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
break; break;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@ -957,6 +981,12 @@ void RMFT2::loop2() {
case OPCODE_ONTHROW: case OPCODE_ONTHROW:
case OPCODE_ONACTIVATE: // Activate event catchers ignored here case OPCODE_ONACTIVATE: // Activate event catchers ignored here
case OPCODE_ONDEACTIVATE: case OPCODE_ONDEACTIVATE:
case OPCODE_ONRED:
case OPCODE_ONAMBER:
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
break; break;
default: default:
@ -973,12 +1003,13 @@ void RMFT2::delayMe(long delay) {
delayStart=millis(); delayStart=millis();
} }
void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
if (FLAGOVERFLOW(id)) return; // Outside range limit if (FLAGOVERFLOW(id)) return false; // Outside range limit
byte f=flags[id]; byte f=flags[id];
f &= ~offMask; f &= ~offMask;
f |= onMask; f |= onMask;
flags[id]=f; flags[id]=f;
return true;
} }
bool RMFT2::getFlag(VPIN id,byte mask) { bool RMFT2::getFlag(VPIN id,byte mask) {
@ -992,9 +1023,9 @@ void RMFT2::kill(const FSH * reason, int operand) {
delete this; delete this;
} }
int16_t RMFT2::getSignalSlot(VPIN id) { int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) { for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id); DIAG(F("EXRAIL Signal %d not defined"), id);
return -1; return -1;
@ -1004,11 +1035,19 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set. // but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
return sigpos/4; // relative slot in signals table return sigslot; // relative slot in signals table
} }
} }
/* static */ void RMFT2::doSignal(VPIN id,char rag) {
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag); if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
// Thjis will work even without a signal definition.
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
else handleEvent(F("AMBER"), onAmberLookup,id);
int16_t sigslot=getSignalSlot(id); int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return; if (sigslot<0) return;
@ -1016,14 +1055,16 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
setFlag(sigslot,rag,SIGNAL_MASK); setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values // Correct signal definition found, get the rag values
int16_t sigpos=sigslot*4; int16_t sigpos=sigslot*8;
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d"),sigid,redpin,amberpin,greenpin); if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
if (sigid & SERVO_SIGNAL_FLAG) { VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
if (sigtype == SERVO_SIGNAL_FLAG) {
// A servo signal, the pin numbers are actually servo positions // A servo signal, the pin numbers are actually servo positions
// Note, setting a signal to a zero position has no effect. // Note, setting a signal to a zero position has no effect.
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin); int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
@ -1032,7 +1073,14 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
return; return;
} }
// LED or similar 3 pin signal
if (sigtype== DCC_SIGNAL_FLAG) {
// redpin,amberpin are the DCC addr,subaddr
DCC::setAccessory(redpin,amberpin, rag!=SIGNAL_RED);
return;
}
// LED or similar 3 pin signal, (all pins zero would be a virtual signal)
// If amberpin is zero, synthesise amber from red+green // If amberpin is zero, synthesise amber from red+green
const byte SIMAMBER=0x00; const byte SIMAMBER=0x00;
if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only
@ -1041,13 +1089,24 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG; bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
// set the three pins // set the three pins
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); if (redpin) {
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); if (!aHigh) redval=!redval;
return; IODevice::write(redpin,redval);
}
if (amberpin) {
bool amberval=(rag==SIGNAL_AMBER);
if (!aHigh) amberval=!amberval;
IODevice::write(amberpin,amberval);
}
if (greenpin) {
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
if (!aHigh) greenval=!greenval;
IODevice::write(greenpin,greenval);
}
} }
/* static */ bool RMFT2::isSignal(VPIN id,char rag) { /* static */ bool RMFT2::isSignal(int16_t id,char rag) {
int16_t sigslot=getSignalSlot(id); int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return false; if (sigslot<0) return false;
return (flags[sigslot] & SIGNAL_MASK) == rag; return (flags[sigslot] & SIGNAL_MASK) == rag;
@ -1055,46 +1114,148 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
// Hunt for an ONTHROW/ONCLOSE for this turnout // Hunt for an ONTHROW/ONCLOSE for this turnout
int pc= (closed?onCloseLookup:onThrowLookup)->find(turnoutId); if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
if (pc<0) return; else handleEvent(F("THROW"),onThrowLookup,turnoutId);
// Check we dont already have a task running this turnout
RMFT2 * task=loopTask;
while(task) {
if (task->onTurnoutId==turnoutId) {
DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId);
return;
}
task=task->next;
if (task==loopTask) break;
}
task=new RMFT2(pc); // new task starts at this instruction
task->onTurnoutId=turnoutId; // flag for recursion detector
} }
void RMFT2::activateEvent(int16_t addr, bool activate) { void RMFT2::activateEvent(int16_t addr, bool activate) {
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
int pc= (activate?onActivateLookup:onDeactivateLookup)->find(addr); if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
}
void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
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) {
int pc= handlers->find(id);
if (pc<0) return; if (pc<0) return;
// Check we dont already have a task running this address // Check we dont already have a task running this handler
RMFT2 * task=loopTask; RMFT2 * task=loopTask;
while(task) { while(task) {
if (task->onActivateAddr==addr) { if (task->onEventStartPosition==pc) {
DIAG(F("Recursive ON(DE)ACTIVATE for %d"),addr); DIAG(F("Recursive ON%S(%d)"),reason, id);
return; return;
} }
task=task->next; task=task->next;
if (task==loopTask) break; if (task==loopTask) break;
} }
task->onActivateAddr=addr; // flag for recursion detector
task=new RMFT2(pc); // new task starts at this instruction task=new RMFT2(pc); // new task starts at this instruction
task->onEventStartPosition=pc; // flag for recursion detector
} }
void RMFT2::printMessage2(const FSH * msg) { void RMFT2::printMessage2(const FSH * msg) {
DIAG(F("EXRAIL(%d) %S"),loco,msg); DIAG(F("EXRAIL(%d) %S"),loco,msg);
} }
static StringBuffer * buffer=NULL;
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
and handle the oddities like LCD, BROADCAST and PARSE */
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
Print * stream=NULL;
// Find out where the string is going
switch (mode) {
case thrunge_print:
StringFormatter::send(&USB_SERIAL,F("<* EXRAIL(%d) "),loco);
stream=&USB_SERIAL;
break;
case thrunge_serial: stream=&USB_SERIAL; break;
case thrunge_serial1:
#ifdef SERIAL1_COMMANDS
stream=&Serial1;
#endif
break;
case thrunge_serial2:
#ifdef SERIAL2_COMMANDS
stream=&Serial2;
#endif
break;
case thrunge_serial3:
#ifdef SERIAL3_COMMANDS
stream=&Serial3;
#endif
break;
case thrunge_serial4:
#ifdef SERIAL4_COMMANDS
stream=&Serial4;
#endif
break;
case thrunge_serial5:
#ifdef SERIAL5_COMMANDS
stream=&Serial5;
#endif
break;
case thrunge_serial6:
#ifdef SERIAL6_COMMANDS
stream=&Serial6;
#endif
break;
case thrunge_lcn:
#if defined(LCN_SERIAL)
stream=&LCN_SERIAL;
#endif
break;
case thrunge_parse:
case thrunge_broadcast:
case thrunge_lcd:
default: // thrunge_lcd+1, ...
if (!buffer) buffer=new StringBuffer();
buffer->flush();
stream=buffer;
break;
}
if (!stream) return;
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// if mega stream it out
for (;;strfar++) {
char c=pgm_read_byte_far(strfar);
if (c=='\0') break;
stream->write(c);
}
#else
// UNO/NANO CPUs dont have high memory
// 32 bit cpus dont care anyway
stream->print((FSH *)strfar);
#endif
// and decide what to do next
switch (mode) {
case thrunge_print:
StringFormatter::send(&USB_SERIAL,F(" *>\n"));
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_parse:
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString());
break;
case thrunge_withrottle:
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
break;
default: // thrunge_lcd+1, ...
if (mode > thrunge_lcd)
SCREEN(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display
break;
}
}

View File

@ -1,6 +1,8 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow * © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -43,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,
@ -52,6 +57,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ROSTER,OPCODE_KILLALL, OPCODE_ROSTER,OPCODE_KILLALL,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE, OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL, OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
// OPcodes below this point are skip-nesting IF operations // OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group // placed here so that they may be skipped as a group
@ -62,7 +72,20 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFTIMEOUT, OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT, OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE, OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED, OPCODE_IFTHROWN OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
};
// Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd.
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_withrottle,
thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcn,
thrunge_lcd, // Must be last!!
}; };
@ -105,15 +128,17 @@ class LookList {
static void createNewTask(int route, uint16_t cab); static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed); static void turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active); static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change);
static void clockEvent(int16_t clocktime, bool change);
static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF; static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros // Throttle Info Access functions built by exrail macros
static const byte rosterNameCount; static const byte rosterNameCount;
static const int16_t FLASH routeIdList[]; static const int16_t HIGHFLASH routeIdList[];
static const int16_t FLASH automationIdList[]; static const int16_t HIGHFLASH automationIdList[];
static const int16_t FLASH rosterIdList[]; static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id); static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id); static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id); static const FSH * getTurnoutDescription(int16_t id);
@ -124,13 +149,17 @@ private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
static void streamFlags(Print* stream); static void streamFlags(Print* stream);
static void setFlag(VPIN id,byte onMask, byte OffMask=0); static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
static bool getFlag(VPIN id,byte mask); static bool getFlag(VPIN id,byte mask);
static int16_t progtrackLocoId; static int16_t progtrackLocoId;
static void doSignal(VPIN id,char rag); static void doSignal(int16_t id,char rag);
static bool isSignal(VPIN id,char rag); static bool isSignal(int16_t id,char rag);
static int16_t getSignalSlot(VPIN id); static int16_t getSignalSlot(int16_t id);
static void setTurnoutHiddenState(Turnout * t); static void setTurnoutHiddenState(Turnout * t);
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask; static RMFT2 * loopTask;
static RMFT2 * pausingTask; static RMFT2 * pausingTask;
void delayMe(long millisecs); void delayMe(long millisecs);
@ -142,18 +171,23 @@ private:
void kill(const FSH * reason=NULL,int operand=0); void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg); void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag; static bool diag;
static const FLASH byte RouteCode[]; static const HIGHFLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[]; static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS]; static byte flags[MAX_FLAGS];
static LookList * sequenceLookup; static LookList * sequenceLookup;
static LookList * onThrowLookup; static LookList * onThrowLookup;
static LookList * onCloseLookup; static LookList * onCloseLookup;
static LookList * onActivateLookup; static LookList * onActivateLookup;
static LookList * onDeactivateLookup; static LookList * onDeactivateLookup;
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
// Local variables - exist for each instance/task // Local variables - exist for each instance/task
RMFT2 *next; // loop chain RMFT2 *next; // loop chain
@ -171,8 +205,7 @@ private:
bool forward; bool forward;
bool invert; bool invert;
byte speedo; byte speedo;
int16_t onTurnoutId; int onEventStartPosition;
int16_t onActivateAddr;
byte stackDepth; byte stackDepth;
int callStack[MAX_STACK_DEPTH]; int callStack[MAX_STACK_DEPTH];
}; };

View File

@ -1,6 +1,7 @@
/* /*
* © 2021-2022 Chris Harlow * © 2020-2022 Chris Harlow. All rights reserved.
* © 2020,2021 Chris Harlow. All rights reserved. * © 2022 Colin Murdoch
* © 2023 Harald Barth
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -28,6 +29,7 @@
#undef AFTER #undef AFTER
#undef ALIAS #undef ALIAS
#undef AMBER #undef AMBER
#undef ANOUT
#undef AT #undef AT
#undef ATGTE #undef ATGTE
#undef ATLT #undef ATLT
@ -37,6 +39,7 @@
#undef BROADCAST #undef BROADCAST
#undef CALL #undef CALL
#undef CLOSE #undef CLOSE
#undef DCC_SIGNAL
#undef DEACTIVATE #undef DEACTIVATE
#undef DEACTIVATEL #undef DEACTIVATEL
#undef DELAY #undef DELAY
@ -58,11 +61,13 @@
#undef FREE #undef FREE
#undef FWD #undef FWD
#undef GREEN #undef GREEN
#undef HAL
#undef IF #undef IF
#undef IFAMBER #undef IFAMBER
#undef IFCLOSED #undef IFCLOSED
#undef IFGREEN #undef IFGREEN
#undef IFGTE #undef IFGTE
#undef IFLOCO
#undef IFLT #undef IFLT
#undef IFNOT #undef IFNOT
#undef IFRANDOM #undef IFRANDOM
@ -70,23 +75,35 @@
#undef IFRESERVE #undef IFRESERVE
#undef IFTHROWN #undef IFTHROWN
#undef IFTIMEOUT #undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION #undef INVERT_DIRECTION
#undef JOIN #undef JOIN
#undef KILLALL #undef KILLALL
#undef LATCH #undef LATCH
#undef LCD #undef LCD
#undef SCREEN
#undef LCN #undef LCN
#undef MOVETT
#undef ONACTIVATE #undef ONACTIVATE
#undef ONACTIVATEL #undef ONACTIVATEL
#undef ONAMBER
#undef ONDEACTIVATE #undef ONDEACTIVATE
#undef ONDEACTIVATEL #undef ONDEACTIVATEL
#undef ONCLOSE #undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
#undef ONGREEN
#undef ONRED
#undef ONTHROW #undef ONTHROW
#undef ONCHANGE
#undef PARSE #undef PARSE
#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
@ -104,11 +121,15 @@
#undef SERIAL1 #undef SERIAL1
#undef SERIAL2 #undef SERIAL2
#undef SERIAL3 #undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO #undef SERVO
#undef SERVO2 #undef SERVO2
#undef SERVO_TURNOUT #undef SERVO_TURNOUT
#undef SERVO_SIGNAL #undef SERVO_SIGNAL
#undef SET #undef SET
#undef SET_TRACK
#undef SETLOCO #undef SETLOCO
#undef SIGNAL #undef SIGNAL
#undef SIGNALH #undef SIGNALH
@ -117,10 +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_TURNOUT #undef VIRTUAL_TURNOUT
#undef WAITFOR #undef WAITFOR
#undef WITHROTTLE
#undef XFOFF #undef XFOFF
#undef XFON #undef XFON
@ -130,6 +154,7 @@
#define AFTER(sensor_id) #define AFTER(sensor_id)
#define ALIAS(name,value...) #define ALIAS(name,value...)
#define AMBER(signal_id) #define AMBER(signal_id)
#define ANOUT(vpin,value,param1,param2)
#define AT(sensor_id) #define AT(sensor_id)
#define ATGTE(sensor_id,value) #define ATGTE(sensor_id,value)
#define ATLT(sensor_id,value) #define ATLT(sensor_id,value)
@ -139,6 +164,7 @@
#define BROADCAST(msg) #define BROADCAST(msg)
#define CALL(route) #define CALL(route)
#define CLOSE(id) #define CLOSE(id)
#define DCC_SIGNAL(id,add,subaddr)
#define DEACTIVATE(addr,subaddr) #define DEACTIVATE(addr,subaddr)
#define DEACTIVATEL(addr) #define DEACTIVATEL(addr)
#define DELAY(mindelay) #define DELAY(mindelay)
@ -160,11 +186,13 @@
#define FREE(blockid) #define FREE(blockid)
#define FWD(speed) #define FWD(speed)
#define GREEN(signal_id) #define GREEN(signal_id)
#define HAL(haltype,params...)
#define IF(sensor_id) #define IF(sensor_id)
#define IFAMBER(signal_id) #define IFAMBER(signal_id)
#define IFCLOSED(turnout_id) #define IFCLOSED(turnout_id)
#define IFGREEN(signal_id) #define IFGREEN(signal_id)
#define IFGTE(sensor_id,value) #define IFGTE(sensor_id,value)
#define IFLOCO(loco_id)
#define IFLT(sensor_id,value) #define IFLT(sensor_id,value)
#define IFNOT(sensor_id) #define IFNOT(sensor_id)
#define IFRANDOM(percent) #define IFRANDOM(percent)
@ -172,23 +200,35 @@
#define IFTHROWN(turnout_id) #define IFTHROWN(turnout_id)
#define IFRESERVE(block) #define IFRESERVE(block)
#define IFTIMEOUT #define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION #define INVERT_DIRECTION
#define JOIN #define JOIN
#define KILLALL #define KILLALL
#define LATCH(sensor_id) #define LATCH(sensor_id)
#define LCD(row,msg) #define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg) #define LCN(msg)
#define MOVETT(id,steps,activity)
#define ONACTIVATE(addr,subaddr) #define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear) #define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
#define ONTIME(value)
#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)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id) #define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE #define PAUSE
#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
@ -206,11 +246,15 @@
#define SERIAL1(msg) #define SERIAL1(msg)
#define SERIAL2(msg) #define SERIAL2(msg)
#define SERIAL3(msg) #define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile) #define SERVO(id,position,profile)
#define SERVO2(id,position,duration) #define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin) #define SET(pin)
#define SET_TRACK(track,mode)
#define SETLOCO(loco) #define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin) #define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin)
@ -219,10 +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_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,8 @@
/* /*
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow * © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@ -44,7 +46,7 @@
// Descriptive texts for routes and animations are created in a sepaerate function which // Descriptive texts for routes and animations are created in a sepaerate function which
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle. // can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create // PRINT(msg), LCD(row,msg) and SCREEN(display,row,msg) are implemented in a separate pass to create
// a getMessageText(id) function. // a getMessageText(id) function.
// CAUTION: The macros below are multiple passed over myAutomation.h // CAUTION: The macros below are multiple passed over myAutomation.h
@ -55,26 +57,38 @@
// helper macro for turnout description as HIDDEN // helper macro for turnout description as HIDDEN
#define HIDDEN "\x01" #define HIDDEN "\x01"
// helper macro to strip leading zeros off time inputs
// (10#mins)%100)
#define STRIP_ZERO(value) 10##value%100
// Pass 1 Implements aliases // Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ALIAS #undef ALIAS
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10; #define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h" #include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
void exrailHalSetup() {
#include "myAutomation.h"
}
// Pass 2 create throttle route list // Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROUTE #undef ROUTE
#define ROUTE(id, description) id, #define ROUTE(id, description) id,
const int16_t FLASH 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 FLASH 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
@ -92,30 +106,65 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions // Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__; const int StringMacroTracker1=__COUNTER__;
#define THRUNGE(msg,mode) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=mode;\
break;\
}
#undef BROADCAST #undef BROADCAST
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break; #define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE #undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break; #define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT #undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break; #define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN #undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break; #define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL #undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break; #define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1 #undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break; #define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2 #undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break; #define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#undef SERIAL3 #undef SERIAL3
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break; #define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
#undef SERIAL4
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
#undef SERIAL5
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
#undef SERIAL6
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
#undef LCD #undef LCD
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break; #define LCD(id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=thrunge_lcd; \
lcdid=id;\
break;\
}
#undef SCREEN
#define SCREEN(display,id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=(thrunger)(thrunge_lcd+display); \
lcdid=id;\
break;\
}
#undef WITHROTTLE
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
void RMFT2::printMessage(uint16_t id) { void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) { switch(id) {
#include "myAutomation.h" #include "myAutomation.h"
default: break ; default: break ;
} }
if (strfar) thrungeString(strfar,tmode,lcdid);
} }
@ -141,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"
; ;
@ -150,9 +199,9 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROSTER #undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid, #define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH 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"
@ -174,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
@ -185,7 +234,12 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin, #define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
#undef SERVO_SIGNAL #undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval, #define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
const FLASH int16_t RMFT2::SignalDefinitions[] = { #undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h" #include "myAutomation.h"
0,0,0,0 }; 0,0,0,0 };
@ -204,6 +258,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), #define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define ALIAS(name,value...) #define ALIAS(name,value...)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id), #define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
#define AT(sensor_id) OPCODE_AT,V(sensor_id), #define AT(sensor_id) OPCODE_AT,V(sensor_id),
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value), #define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value), #define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
@ -218,6 +273,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)), #define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay), #define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L), #define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
#define DCC_SIGNAL(id,add,subaddr)
#define DONE OPCODE_ENDTASK,0,0, #define DONE OPCODE_ENDTASK,0,0,
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin), #define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
#define ELSE OPCODE_ELSE,0,0, #define ELSE OPCODE_ELSE,0,0,
@ -234,11 +290,13 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define FREE(blockid) OPCODE_FREE,V(blockid), #define FREE(blockid) OPCODE_FREE,V(blockid),
#define FWD(speed) OPCODE_FWD,V(speed), #define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id), #define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
#define IF(sensor_id) OPCODE_IF,V(sensor_id), #define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id), #define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id), #define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id), #define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value), #define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value), #define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id), #define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent), #define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
@ -246,21 +304,33 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block), #define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id), #define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0, #define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0, #define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0, #define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0, #define KILLALL OPCODE_KILLALL,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg) #define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg) #define LCN(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
#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 ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_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),
@ -280,11 +350,15 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg) #define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg) #define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg) #define SERIAL3(msg) PRINT(msg)
#define SERIAL4(msg) PRINT(msg)
#define SERIAL5(msg) PRINT(msg)
#define SERIAL6(msg) PRINT(msg)
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0), #define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L), #define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile), #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define SET(pin) OPCODE_SET,V(pin), #define SET(pin) OPCODE_SET,V(pin),
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco), #define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SIGNAL(redpin,amberpin,greenpin) #define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin)
@ -293,22 +367,27 @@ const FLASH 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_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),
// Build RouteCode // Build RouteCode
const int StringMacroTracker2=__COUNTER__; const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = { const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h" #include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
// Restore normal code LCD & SERIAL macro // Restore normal code LCD & SERIAL macro
#undef LCD #undef LCD
#define LCD StringFormatter::lcd #define LCD StringFormatter::lcd
#undef SCREEN
#define SCREEN StringFormatter::lcd2
#undef SERIAL #undef SERIAL
#define SERIAL 0x0 #define SERIAL 0x0
#endif #endif

View File

@ -27,6 +27,7 @@
#include "EthernetInterface.h" #include "EthernetInterface.h"
#include "DIAG.h" #include "DIAG.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "WiThrottle.h"
#include "DCCTimer.h" #include "DCCTimer.h"
EthernetInterface * EthernetInterface::singleton=NULL; EthernetInterface * EthernetInterface::singleton=NULL;
@ -67,8 +68,9 @@ EthernetInterface::EthernetInterface()
return; return;
} }
#endif #endif
if (Ethernet.hardwareStatus() == EthernetNoHardware) if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not detected or is a W5100")); DIAG(F("Ethernet shield not found or W5100"));
}
unsigned long startmilli = millis(); unsigned long startmilli = millis();
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
@ -77,6 +79,12 @@ EthernetInterface::EthernetInterface()
DIAG(F("Ethernet waiting for link (1sec) ")); DIAG(F("Ethernet waiting for link (1sec) "));
delay(1000); delay(1000);
} }
// now we either do have link of we have a W5100
// where we do not know if we have link. That's
// the reason to now run checkLink.
// CheckLinks sets up outboundRing if it does
// not exist yet as well.
checkLink();
} }
/** /**
@ -93,8 +101,9 @@ EthernetInterface::~EthernetInterface() {
* @brief Main loop for the EthernetInterface * @brief Main loop for the EthernetInterface
* *
*/ */
void EthernetInterface::loop() { void EthernetInterface::loop()
if(!singleton || (!singleton->checkLink())) {
if (!singleton || (!singleton->checkLink()))
return; return;
switch (Ethernet.maintain()) { switch (Ethernet.maintain()) {
@ -126,7 +135,10 @@ bool EthernetInterface::checkLink() {
if(!connected) { if(!connected) {
DIAG(F("Ethernet cable connected")); DIAG(F("Ethernet cable connected"));
connected=true; connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address #ifdef IP_ADDRESS
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
#endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin(); server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
@ -154,6 +166,10 @@ bool EthernetInterface::checkLink() {
} }
void EthernetInterface::loop2() { void EthernetInterface::loop2() {
if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it
if (Diag::ETHERNET) DIAG(F("No outboundRing"));
return;
}
// get client from the server // get client from the server
EthernetClient client = server->accept(); EthernetClient client = server->accept();
@ -189,9 +205,7 @@ void EthernetInterface::loop2() {
buffer[count] = '\0'; // terminate the string properly buffer[count] = '\0'; // terminate the string properly
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer); if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
// execute with data going directly back // execute with data going directly back
outboundRing->mark(socket);
CommandDistributor::parse(socket,buffer,outboundRing); CommandDistributor::parse(socket,buffer,outboundRing);
outboundRing->commit();
return; // limit the amount of processing that takes place within 1 loop() cycle. return; // limit the amount of processing that takes place within 1 loop() cycle.
} }
} }
@ -206,9 +220,13 @@ void EthernetInterface::loop2() {
} }
} }
WiThrottle::loop(outboundRing);
// handle at most 1 outbound transmission // handle at most 1 outbound transmission
int socketOut=outboundRing->read(); int socketOut=outboundRing->read();
if (socketOut>=0) { if (socketOut >= MAX_SOCK_NUM) {
DIAG(F("Ethernet outboundRing socket=%d error"), socketOut);
} else if (socketOut >= 0) {
int count=outboundRing->count(); int count=outboundRing->count();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count); if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read()); for(;count>0;count--) clients[socketOut].write(outboundRing->read());

View File

@ -31,7 +31,7 @@
#include "defines.h" #include "defines.h"
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include <Arduino.h> #include <Arduino.h>
#include <avr/pgmspace.h> //#include <avr/pgmspace.h>
#if defined (ARDUINO_TEENSY41) #if defined (ARDUINO_TEENSY41)
#include <NativeEthernet.h> //TEENSY Ethernet Treiber #include <NativeEthernet.h> //TEENSY Ethernet Treiber
#include <NativeEthernetUdp.h> #include <NativeEthernetUdp.h>
@ -50,23 +50,22 @@
class EthernetInterface { class EthernetInterface {
public: public:
static void setup(); static void setup();
static void loop(); static void loop();
private: private:
static EthernetInterface * singleton; static EthernetInterface * singleton;
bool connected; bool connected;
EthernetInterface(); EthernetInterface();
~EthernetInterface(); ~EthernetInterface();
void loop2(); void loop2();
bool checkLink(); bool checkLink();
EthernetServer * server = NULL;
EthernetServer *server = nullptr; EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time
// This depends on the chipset used on the Shield
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing = nullptr; RingStream * outboundRing = NULL;
}; };
#endif #endif

57
FSH.h
View File

@ -1,4 +1,5 @@
/* /*
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie * © 2021 Neil McKechnie
* © 2021 Harald Barth * © 2021 Harald Barth
* © 2021 Fred Decker * © 2021 Fred Decker
@ -34,23 +35,59 @@
* pgm_read_byte_near use GETFLASH instead. * pgm_read_byte_near use GETFLASH instead.
* pgm_read_word_near use GETFLASHW instead. * pgm_read_word_near use GETFLASHW instead.
* *
* Also:
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
* GETHIGHFLASH,GETHIGHFLASHW to access them
*
*/ */
#include <Arduino.h> #include <Arduino.h>
#if defined(ARDUINO_ARCH_MEGAAVR) #ifdef ARDUINO_ARCH_AVR
// AVR devices have flash memory mapped differently
// progmem can be accessed by _near functions or _far
typedef __FlashStringHelper FSH;
#define FLASH PROGMEM
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define STRCPY_P strcpy_P
#define STRCMP_P strcmp_P
#define STRNCPY_P strncpy_P
#define STRNCMP_P strncmp_P
#define STRLEN_P strlen_P
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2")))
#define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM
#define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
#endif
#else
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
#ifdef F #ifdef F
#undef F #undef F
#endif #endif
#ifdef FLASH
#undef FLASH
#endif
#define F(str) (str) #define F(str) (str)
typedef char FSH; typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define FLASH #define FLASH
#define strlen_P strlen #define HIGHFLASH
#define strcpy_P strcpy #define GETFARPTR(data) ((uint32_t)(data))
#else #define GETFLASH(addr) (*(const byte *)(addr))
typedef __FlashStringHelper FSH; #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETFLASH(addr) pgm_read_byte_near(addr) #define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#define GETFLASHW(addr) pgm_read_word_near(addr) #define STRCPY_P strcpy
#define FLASH PROGMEM #define STRCMP_P strcmp
#define STRNCPY_P strncpy
#define STRNCMP_P strncmp
#define STRLEN_P strlen
#endif #endif
#endif #endif

View File

@ -1 +1 @@
#define GITHUB_SHA "a26d988" #define GITHUB_SHA "3bddf4d"

View File

@ -1,5 +1,7 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie
* © 2022 Paul M Antoine
* All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -30,28 +32,128 @@
#elif defined(ARDUINO_ARCH_MEGAAVR) #elif defined(ARDUINO_ARCH_MEGAAVR)
#include "I2CManager_NonBlocking.h" #include "I2CManager_NonBlocking.h"
#include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi #include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi
#elif defined(ARDUINO_ARCH_SAMD)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later
#elif defined(ARDUINO_ARCH_STM32)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_STM32.h" // STM32F411RE for now... more later
#else #else
#define I2C_USE_WIRE #define I2C_USE_WIRE
#include "I2CManager_Wire.h" // Other platforms #include "I2CManager_Wire.h" // Other platforms
#endif #endif
// Helper function for listing device types
static const FSH * guessI2CDeviceType(uint8_t address) {
if (address >= 0x20 && address <= 0x26)
return F("GPIO Expander");
else if (address == 0x27)
return F("GPIO Expander or LCD Display");
else if (address == 0x29)
return F("Time-of-flight sensor");
else if (address >= 0x3c && address <= 0x3d)
return F("OLED Display");
else if (address >= 0x48 && address <= 0x4f)
return F("Analogue Inputs or PWM");
else if (address >= 0x40 && address <= 0x4f)
return F("PWM");
else if (address >= 0x50 && address <= 0x5f)
return F("EEPROM");
else if (address == 0x68)
return F("Real-time clock");
else if (address >= 0x70 && address <= 0x77)
return F("I2C Mux");
else
return F("?");
}
// If not already initialised, initialise I2C // If not already initialised, initialise I2C
void I2CManagerClass::begin(void) { void I2CManagerClass::begin(void) {
//setTimeout(25000); // 25 millisecond timeout
if (!_beginCompleted) { if (!_beginCompleted) {
_beginCompleted = true; _beginCompleted = true;
// Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C
const FSH *message = F("WARNING: Check I2C %S line for short/pullup");
pinMode(SDA, INPUT);
if (!digitalRead(SDA))
DIAG(message, F("SDA"));
pinMode(SCL, INPUT);
if (!digitalRead(SCL))
DIAG(message, F("SCL"));
// Now initialise I2C
_initialise(); _initialise();
// Probe and list devices. #if defined(I2C_USE_WIRE)
DIAG(F("I2CManager: Using Wire library"));
#endif
// Probe and list devices. Use standard mode
// (clock speed 100kHz) for best device compatibility.
_setClock(100000);
unsigned long originalTimeout = _timeout;
setTimeout(1000); // use 1ms timeout for probes
#if defined(I2C_EXTENDED_ADDRESS)
// First count the multiplexers and switch off all subbuses
_muxCount = 0;
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK)
_muxCount++;
}
#endif
// Enumerate devices that are visible
bool found = false; bool found = false;
for (byte addr=1; addr<127; addr++) { for (uint8_t addr=0x08; addr<0x78; addr++) {
if (exists(addr)) { if (exists(addr)) {
found = true; found = true;
DIAG(F("I2C Device found at x%x"), addr); DIAG(F("I2C Device found at 0x%x, %S?"), addr, guessI2CDeviceType(addr));
} }
} }
#if defined(I2C_EXTENDED_ADDRESS)
// Enumerate all I2C devices that are connected via multiplexer,
// i.e. that respond when only one multiplexer has one subBus enabled
// and the device doesn't respond when the mux subBus is disabled.
// If any probes time out, then assume that the subbus is dead and
// don't do any more on that subbus.
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo;
if (exists(muxAddr)) {
// Select Mux Subbus
for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) {
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
for (uint8_t addr=0x08; addr<0x78; addr++) {
uint8_t status = checkAddress(addr);
if (status == I2C_STATUS_OK) {
// De-select subbus
muxSelectSubBus({(I2CMux)muxNo, SubBus_None});
if (!exists(addr)) {
// Device responds when subbus selected but not when
// subbus disabled - ergo it must be on subbus!
found = true;
DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,0x%x}, %S?"),
muxNo, subBus, addr, guessI2CDeviceType(addr));
}
// Re-select subbus
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
} else if (status == I2C_STATUS_TIMEOUT) {
// Bus stuck, skip to next one.
break;
}
}
}
// Deselect all subBuses for this mux. Otherwise its devices will continue to
// respond when other muxes are being probed.
I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux
}
}
#endif
if (!found) DIAG(F("No I2C Devices found")); if (!found) DIAG(F("No I2C Devices found"));
_setClock(_clockSpeed);
setTimeout(originalTimeout); // set timeout back to original
} }
} }
@ -60,31 +162,35 @@ void I2CManagerClass::begin(void) {
void I2CManagerClass::setClock(uint32_t speed) { void I2CManagerClass::setClock(uint32_t speed) {
if (speed < _clockSpeed && !_clockSpeedFixed) { if (speed < _clockSpeed && !_clockSpeedFixed) {
_clockSpeed = speed; _clockSpeed = speed;
DIAG(F("I2C clock speed set to %l Hz"), _clockSpeed);
} }
_setClock(_clockSpeed); _setClock(_clockSpeed);
} }
// Force clock speed to that specified. It can then only // Force clock speed to that specified.
// be overridden by calling Wire.setClock directly.
void I2CManagerClass::forceClock(uint32_t speed) { void I2CManagerClass::forceClock(uint32_t speed) {
if (!_clockSpeedFixed) {
_clockSpeed = speed; _clockSpeed = speed;
_clockSpeedFixed = true; _clockSpeedFixed = true;
_setClock(_clockSpeed); _setClock(_clockSpeed);
} DIAG(F("I2C clock speed forced to %l Hz"), _clockSpeed);
} }
// Check if specified I2C address is responding (blocking operation) // Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code. // Returns I2C_STATUS_OK (0) if OK, or error code.
uint8_t I2CManagerClass::checkAddress(uint8_t address) { // Suppress retries. If it doesn't respond first time it's out of the running.
return write(address, NULL, 0); uint8_t I2CManagerClass::checkAddress(I2CAddress address) {
I2CRB rb;
rb.setWriteParams(address, NULL, 0);
rb.suppressRetries(true);
queueRequest(&rb);
return rb.wait();
} }
/*************************************************************************** /***************************************************************************
* Write a transmission to I2C using a list of data (blocking operation) * Write a transmission to I2C using a list of data (blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) { uint8_t I2CManagerClass::write(I2CAddress address, uint8_t nBytes, ...) {
uint8_t buffer[nBytes]; uint8_t buffer[nBytes];
va_list args; va_list args;
va_start(args, nBytes); va_start(args, nBytes);
@ -97,7 +203,7 @@ uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) {
/*************************************************************************** /***************************************************************************
* Initiate a write to an I2C device (blocking operation) * Initiate a write to an I2C device (blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) { uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) {
I2CRB req; I2CRB req;
uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req); uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req);
return finishRB(&req, status); return finishRB(&req, status);
@ -106,7 +212,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[],
/*************************************************************************** /***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation) * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8_t dataLen) { uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * data, uint8_t dataLen) {
I2CRB req; I2CRB req;
uint8_t status = write_P(i2cAddress, data, dataLen, &req); uint8_t status = write_P(i2cAddress, data, dataLen, &req);
return finishRB(&req, status); return finishRB(&req, status);
@ -115,7 +221,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8
/*************************************************************************** /***************************************************************************
* Initiate a write (optional) followed by a read from the I2C device (blocking operation) * Initiate a write (optional) followed by a read from the I2C device (blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen) const uint8_t *writeBuffer, uint8_t writeLen)
{ {
I2CRB req; I2CRB req;
@ -126,7 +232,7 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r
/*************************************************************************** /***************************************************************************
* Overload of read() to allow command to be specified as a series of bytes (blocking operation) * Overload of read() to allow command to be specified as a series of bytes (blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...) { uint8_t writeSize, ...) {
va_list args; va_list args;
// Copy the series of bytes into an array. // Copy the series of bytes into an array.
@ -157,7 +263,7 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) {
case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)"); case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)");
case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)"); case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)");
case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error"); case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error");
case I2C_STATUS_TIMEOUT: return F("Timeout"); case I2C_STATUS_TIMEOUT: return F("I2C bus timeout");
case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost"); case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost");
case I2C_STATUS_BUS_ERROR: return F("I2C bus error"); case I2C_STATUS_BUS_ERROR: return F("I2C bus error");
case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error"); case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error");
@ -171,46 +277,40 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) {
***************************************************************************/ ***************************************************************************/
I2CManagerClass I2CManager = I2CManagerClass(); I2CManagerClass I2CManager = I2CManagerClass();
// Buffer for conversion of I2CAddress to char*.
/* static */ char I2CAddress::addressBuffer[30];
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Helper functions associated with I2C Request Block // Helper functions associated with I2C Request Block
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
/*************************************************************************** /***************************************************************************
* Block waiting for request block to complete, and return completion status. * Block waiting for request to complete, and return completion status.
* Since such a loop could potentially last for ever if the RB status doesn't * Timeout monitoring is performed in the I2CManager.loop() function.
* change, we set a high limit (1sec, 1000ms) on the wait time and, if it
* hasn't changed by that time we assume it's not going to, and just return
* a timeout status. This means that CS will not lock up.
***************************************************************************/ ***************************************************************************/
uint8_t I2CRB::wait() { uint8_t I2CRB::wait() {
unsigned long waitStart = millis(); while (status==I2C_STATUS_PENDING) {
do {
I2CManager.loop(); I2CManager.loop();
// Rather than looping indefinitely, let's set a very high timeout (1s). };
if ((millis() - waitStart) > 1000UL) {
DIAG(F("I2C TIMEOUT I2C:x%x I2CRB:x%x"), i2cAddress, this);
status = I2C_STATUS_TIMEOUT;
// Note that, although the timeout is posted, the request may yet complete.
// TODO: Ideally we would like to cancel the request.
return status;
}
} while (status==I2C_STATUS_PENDING);
return status; return status;
} }
/*************************************************************************** /***************************************************************************
* Check whether request is still in progress. * Check whether request is still in progress.
* Timeout monitoring is performed in the I2CManager.loop() function.
***************************************************************************/ ***************************************************************************/
bool I2CRB::isBusy() { bool I2CRB::isBusy() {
if (status==I2C_STATUS_PENDING) {
I2CManager.loop(); I2CManager.loop();
return (status==I2C_STATUS_PENDING); return true;
} else
return false;
} }
/*************************************************************************** /***************************************************************************
* Helper functions to fill the I2CRequest structure with parameters. * Helper functions to fill the I2CRequest structure with parameters.
***************************************************************************/ ***************************************************************************/
void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen) { void I2CRB::setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen) {
this->i2cAddress = i2cAddress; this->i2cAddress = i2cAddress;
this->writeLen = 0; this->writeLen = 0;
this->readBuffer = readBuffer; this->readBuffer = readBuffer;
@ -219,7 +319,7 @@ void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readL
this->status = I2C_STATUS_OK; this->status = I2C_STATUS_OK;
} }
void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, void I2CRB::setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen) { const uint8_t *writeBuffer, uint8_t writeLen) {
this->i2cAddress = i2cAddress; this->i2cAddress = i2cAddress;
this->writeBuffer = writeBuffer; this->writeBuffer = writeBuffer;
@ -230,7 +330,7 @@ void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t re
this->status = I2C_STATUS_OK; this->status = I2C_STATUS_OK;
} }
void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) { void I2CRB::setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) {
this->i2cAddress = i2cAddress; this->i2cAddress = i2cAddress;
this->writeBuffer = writeBuffer; this->writeBuffer = writeBuffer;
this->writeLen = writeLen; this->writeLen = writeLen;
@ -239,3 +339,28 @@ void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8
this->status = I2C_STATUS_OK; this->status = I2C_STATUS_OK;
} }
void I2CRB::suppressRetries(bool suppress) {
if (suppress)
this->operation |= OPERATION_NORETRY;
else
this->operation &= ~OPERATION_NORETRY;
}
// Helper function for converting a uint8_t to four characters (e.g. 0x23).
void I2CAddress::toHex(const uint8_t value, char *buffer) {
char *ptr = buffer;
// Just display hex value, two digits.
*ptr++ = '0';
*ptr++ = 'x';
uint8_t bits = (value >> 4) & 0xf;
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
bits = value & 0xf;
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
}
#if !defined(I2C_EXTENDED_ADDRESS)
/* static */ bool I2CAddress::_addressWarningDone = false;
#endif

View File

@ -1,5 +1,6 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie. All rights reserved.
* © 2022 Paul M Antoine
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -22,13 +23,15 @@
#include <inttypes.h> #include <inttypes.h>
#include "FSH.h" #include "FSH.h"
#include "defines.h"
#include "DIAG.h"
/* /*
* Manager for I2C communications. For portability, it allows use * Manager for I2C communications. For portability, it allows use
* of the Wire class, but also has a native implementation for AVR * of the Wire class, but also has a native implementation for AVR
* which supports non-blocking queued I/O requests. * which supports non-blocking queued I/O requests.
* *
* Helps to avoid calling Wire.begin() multiple times (which is not) * Helps to avoid calling Wire.begin() multiple times (which is not
* entirely benign as it reinitialises). * entirely benign as it reinitialises).
* *
* Also helps to avoid the Wire clock from being set, by another device * Also helps to avoid the Wire clock from being set, by another device
@ -75,12 +78,12 @@
* Timeout monitoring is possible, but requires that the following call is made * Timeout monitoring is possible, but requires that the following call is made
* reasonably frequently in the program's loop() function: * reasonably frequently in the program's loop() function:
* I2CManager.loop(); * I2CManager.loop();
* So that the application doesn't need to do this explicitly, this call is performed
* from the I2CRB::isBusy() or I2CRB::wait() functions.
* *
*/ */
/* /*
* Future enhancement possibility:
*
* I2C Multiplexer (e.g. TCA9547, TCA9548) * I2C Multiplexer (e.g. TCA9547, TCA9548)
* *
* A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27 * A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27
@ -93,15 +96,10 @@
* Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be * Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be
* put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions. * put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions.
* *
* Changes required: Increase the size of the I2CAddress field in the IODevice class from uint8_t to uint16_t.
* The most significant byte would contain a '1' bit flag, the multiplexer number (0-7) and bus number (0-7). Then, when performing
* an I2C operation, the I2CManager would check this byte and, if zero, do what it currently does. If the byte is non-zero, then
* that means the device is connected via a multiplexer so the I2C transaction should be preceded by a select command issued to the
* relevant multiplexer.
* *
* Non-interrupting I2C: * Non-interrupting I2C:
* *
* I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state * Non-blocking I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state
* machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function. * machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function.
* The speed at which I2C operations can be performed then becomes highly dependent on the frequency that * The speed at which I2C operations can be performed then becomes highly dependent on the frequency that
* the loop() function is called, and may be adequate under some circumstances. * the loop() function is called, and may be adequate under some circumstances.
@ -110,10 +108,17 @@
* *
*/ */
// Uncomment following line to enable Wire library instead of native I2C drivers // Maximum number of retries on an I2C operation.
// A value of zero will disable retries.
// Maximum value is 254 (unsigned byte counter)
// Note that timeout failures are not retried, but any timeout
// configured applies to each try separately.
#define MAX_I2C_RETRIES 2
// Add following line to config.h to enable Wire library instead of native I2C drivers
//#define I2C_USE_WIRE //#define I2C_USE_WIRE
// Uncomment following line to disable the use of interrupts by the native I2C drivers. // Add following line to config.h to disable the use of interrupts by the native I2C drivers.
//#define I2C_NO_INTERRUPTS //#define I2C_NO_INTERRUPTS
// Default to use interrupts within the native I2C drivers. // Default to use interrupts within the native I2C drivers.
@ -121,6 +126,233 @@
#define I2C_USE_INTERRUPTS #define I2C_USE_INTERRUPTS
#endif #endif
// I2C Extended Address support I2C Multiplexers and allows various properties to be
// associated with an I2C address such as the MUX and SubBus. In the future, this
// may be extended to include multiple buses, and other features.
// Uncomment to enable extended address.
//
//#define I2C_EXTENDED_ADDRESS
/////////////////////////////////////////////////////////////////////////////////////
// Extended I2C Address type to facilitate extended I2C addresses including
// I2C multiplexer support.
/////////////////////////////////////////////////////////////////////////////////////
// Currently only one bus supported, and one instance of I2CManager to handle it.
enum I2CBus : uint8_t {
I2CBus_0 = 0,
};
// Currently I2CAddress supports one I2C bus, with up to eight
// multipexers (MUX) attached. Each MUX can have up to eight sub-buses.
enum I2CMux : uint8_t {
I2CMux_0 = 0,
I2CMux_1 = 1,
I2CMux_2 = 2,
I2CMux_3 = 3,
I2CMux_4 = 4,
I2CMux_5 = 5,
I2CMux_6 = 6,
I2CMux_7 = 7,
I2CMux_None = 255, // Address doesn't need mux switching
};
enum I2CSubBus : uint8_t {
SubBus_0 = 0, // Enable individual sub-buses...
SubBus_1 = 1,
#if !defined(I2CMUX_PCA9542)
SubBus_2 = 2,
SubBus_3 = 3,
#if !defined(I2CMUX_PCA9544)
SubBus_4 = 4,
SubBus_5 = 5,
SubBus_6 = 6,
SubBus_7 = 7,
#endif
#endif
SubBus_No, // Number of subbuses (highest + 1)
SubBus_None = 254, // Disable all sub-buses on selected mux
SubBus_All = 255, // Enable all sub-buses (not supported by some multiplexers)
};
// Type to hold I2C address
#if defined(I2C_EXTENDED_ADDRESS)
// First MUX address (they range between 0x70-0x77).
#define I2C_MUX_BASE_ADDRESS 0x70
// Currently I2C address supports one I2C bus, with up to eight
// multiplexers (MUX) attached. Each MUX can have up to eight sub-buses.
// This structure could be extended in the future (if there is a need)
// to support 10-bit I2C addresses, different I2C clock speed for each
// sub-bus, multiple I2C buses, and other features not yet thought of.
struct I2CAddress {
private:
// Fields
I2CBus _busNumber;
I2CMux _muxNumber;
I2CSubBus _subBus;
uint8_t _deviceAddress;
static char addressBuffer[];
public:
// Constructors
// For I2CAddress "{I2CBus_0, Mux_0, SubBus_0, 0x23}" syntax.
I2CAddress(const I2CBus busNumber, const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) {
_busNumber = busNumber;
_muxNumber = muxNumber;
_subBus = subBus;
_deviceAddress = deviceAddress;
}
// Basic constructor
I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {}
// For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax.
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) :
I2CAddress(I2CBus_0, muxNumber, subBus, deviceAddress) {}
// For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70)
I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) :
I2CAddress(I2CMux_0, subBus, deviceAddress) {}
// Conversion from uint8_t to I2CAddress
// For I2CAddress in form "0x23"
// (device assumed to be on the main I2C bus).
I2CAddress(const uint8_t deviceAddress) :
I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {}
// Conversion from uint8_t to I2CAddress
// For I2CAddress in form "{I2CBus_1, 0x23}"
// (device not connected via multiplexer).
I2CAddress(const I2CBus bus, const uint8_t deviceAddress) :
I2CAddress(bus, I2CMux_None, SubBus_None, deviceAddress) {}
// For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector)
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) :
I2CAddress(muxNumber, subBus, 0x00) {}
// For I2CAddress in form "{i2cAddress, deviceAddress}"
// where deviceAddress is to be on the same subbus as i2cAddress.
I2CAddress(I2CAddress firstAddress, uint8_t newDeviceAddress) :
I2CAddress(firstAddress._muxNumber, firstAddress._subBus, newDeviceAddress) {}
// Conversion operator from I2CAddress to uint8_t
// For "uint8_t address = i2cAddress;" syntax
// (device assumed to be on the main I2C bus or on a currently selected subbus.
operator uint8_t () const { return _deviceAddress; }
// Conversion from I2CAddress to char* (uses static storage so only
// one conversion can be done at a time). So don't call it twice in a
// single DIAG statement for example.
const char* toString() {
char *ptr = addressBuffer;
if (_muxNumber != I2CMux_None) {
strcpy_P(ptr, (const char*)F("{I2CMux_"));
ptr += 8;
*ptr++ = '0' + _muxNumber;
strcpy_P(ptr, (const char*)F(",Subbus_"));
ptr += 8;
if (_subBus == SubBus_None) {
strcpy_P(ptr, (const char*)F("None"));
ptr += 4;
} else if (_subBus == SubBus_All) {
strcpy_P(ptr, (const char*)F("All"));
ptr += 3;
} else
*ptr++ = '0' + _subBus;
*ptr++ = ',';
}
toHex(_deviceAddress, ptr);
ptr += 4;
if (_muxNumber != I2CMux_None)
*ptr++ = '}';
*ptr = 0; // terminate string
return addressBuffer;
}
// Comparison operator
int operator == (I2CAddress &a) const {
if (_deviceAddress != a._deviceAddress)
return false; // Different device address so no match
if (_muxNumber == I2CMux_None || a._muxNumber == I2CMux_None)
return true; // Same device address, one or other on main bus
if (_subBus == SubBus_None || a._subBus == SubBus_None)
return true; // Same device address, one or other on main bus
if (_muxNumber != a._muxNumber)
return false; // Connected to a subbus on a different mux
if (_subBus != a._subBus)
return false; // different subbus
return true; // Same address on same mux and same subbus
}
// Field accessors
I2CMux muxNumber() { return _muxNumber; }
I2CSubBus subBus() { return _subBus; }
uint8_t deviceAddress() { return _deviceAddress; }
private:
// Helper function for converting byte to four-character hex string (e.g. 0x23).
void toHex(const uint8_t value, char *buffer);
};
#else
struct I2CAddress {
private:
uint8_t _deviceAddress;
static char addressBuffer[];
public:
// Constructors
I2CAddress(const uint8_t deviceAddress) {
_deviceAddress = deviceAddress;
}
I2CAddress(I2CMux, I2CSubBus, const uint8_t deviceAddress) {
addressWarning();
_deviceAddress = deviceAddress;
}
I2CAddress(I2CSubBus, const uint8_t deviceAddress) {
addressWarning();
_deviceAddress = deviceAddress;
}
// Basic constructor
I2CAddress() : I2CAddress(0) {}
// Conversion operator from I2CAddress to uint8_t
// For "uint8_t address = i2cAddress;" syntax
operator uint8_t () const { return _deviceAddress; }
// Conversion from I2CAddress to char* (uses static storage so only
// one conversion can be done at a time). So don't call it twice in a
// single DIAG statement for example.
const char* toString () {
char *ptr = addressBuffer;
// Just display hex value, two digits.
toHex(_deviceAddress, ptr);
ptr += 4;
*ptr = 0; // terminate string
return addressBuffer;
}
// Comparison operator
int operator == (I2CAddress &a) const {
if (_deviceAddress != a._deviceAddress)
return false; // Different device address so no match
return true; // Same address on same mux and same subbus
}
private:
// Helper function for converting byte to four-character hex string (e.g. 0x23).
void toHex(const uint8_t value, char *buffer);
void addressWarning() {
if (!_addressWarningDone) {
DIAG(F("WARNIING: Extended I2C address used but not supported in this configuration"));
_addressWarningDone = true;
}
}
static bool _addressWarningDone;
};
#endif // I2C_EXTENDED_ADDRESS
// Status codes for I2CRB structures. // Status codes for I2CRB structures.
enum : uint8_t { enum : uint8_t {
// Codes used by Wire and by native drivers // Codes used by Wire and by native drivers
@ -143,6 +375,7 @@ enum : uint8_t {
I2C_STATE_ACTIVE=253, I2C_STATE_ACTIVE=253,
I2C_STATE_FREE=254, I2C_STATE_FREE=254,
I2C_STATE_CLOSING=255, I2C_STATE_CLOSING=255,
I2C_STATE_COMPLETED=252,
}; };
typedef enum : uint8_t typedef enum : uint8_t
@ -151,6 +384,8 @@ typedef enum : uint8_t
OPERATION_REQUEST = 2, OPERATION_REQUEST = 2,
OPERATION_SEND = 3, OPERATION_SEND = 3,
OPERATION_SEND_P = 4, OPERATION_SEND_P = 4,
OPERATION_NORETRY = 0x80, // OR with operation to suppress retries.
OPERATION_MASK = 0x7f, // mask for extracting the operation code
} OperationEnum; } OperationEnum;
@ -169,18 +404,19 @@ public:
uint8_t wait(); uint8_t wait();
bool isBusy(); bool isBusy();
void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen); void setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen);
void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); void setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen);
void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); void setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen);
void suppressRetries(bool suppress);
uint8_t writeLen; uint8_t writeLen;
uint8_t readLen; uint8_t readLen;
uint8_t operation; uint8_t operation;
uint8_t i2cAddress; I2CAddress i2cAddress;
uint8_t *readBuffer; uint8_t *readBuffer;
const uint8_t *writeBuffer; const uint8_t *writeBuffer;
#if !defined(I2C_USE_WIRE) #if !defined(I2C_USE_WIRE)
I2CRB *nextRequest; I2CRB *nextRequest; // Used by non-blocking devices for I2CRB queue management.
#endif #endif
}; };
@ -194,26 +430,33 @@ public:
void setClock(uint32_t speed); void setClock(uint32_t speed);
// Force clock speed // Force clock speed
void forceClock(uint32_t speed); void forceClock(uint32_t speed);
// setTimeout sets the timout value for I2C transactions (milliseconds).
void setTimeout(unsigned long);
// Check if specified I2C address is responding. // Check if specified I2C address is responding.
uint8_t checkAddress(uint8_t address); uint8_t checkAddress(I2CAddress address);
inline bool exists(uint8_t address) { inline bool exists(I2CAddress address) {
return checkAddress(address)==I2C_STATUS_OK; return checkAddress(address)==I2C_STATUS_OK;
} }
// Select/deselect Mux Sub-Bus (if using legacy addresses, just checks address)
// E.g. muxSelectSubBus({I2CMux_0, SubBus_3});
uint8_t muxSelectSubBus(I2CAddress address) {
return checkAddress(address);
}
// Write a complete transmission to I2C from an array in RAM // Write a complete transmission to I2C from an array in RAM
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size); uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size);
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
// Write a complete transmission to I2C from an array in Flash // Write a complete transmission to I2C from an array in Flash
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size); uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size);
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
// Write a transmission to I2C from a list of bytes. // Write a transmission to I2C from a list of bytes.
uint8_t write(uint8_t address, uint8_t nBytes, ...); uint8_t write(I2CAddress address, uint8_t nBytes, ...);
// Write a command from an array in RAM and read response // Write a command from an array in RAM and read response
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0); const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0);
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb); const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb);
// Write a command from an arbitrary list of bytes and read response // Write a command from an arbitrary list of bytes and read response
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...); uint8_t writeSize, ...);
void queueRequest(I2CRB *req); void queueRequest(I2CRB *req);
@ -230,7 +473,19 @@ public:
private: private:
bool _beginCompleted = false; bool _beginCompleted = false;
bool _clockSpeedFixed = false; bool _clockSpeedFixed = false;
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. uint8_t retryCounter; // Count of retries
// Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD
// and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds
// will not be useful. The speed can be overridden by I2CManager::forceClock().
uint32_t _clockSpeed = I2C_FREQ;
// Default timeout 100ms on I2C request block completion.
// A full 32-byte transmission takes about 8ms at 100kHz,
// so this value allows lots of headroom.
// It can be modified by calling I2CManager.setTimeout() function.
// When retries are enabled, the timeout applies to each
// try, and failure from timeout does not get retried.
// A value of 0 means disable timeout monitoring.
unsigned long _timeout = 100000UL;
// Finish off request block by waiting for completion and posting status. // Finish off request block by waiting for completion and posting status.
uint8_t finishRB(I2CRB *rb, uint8_t status); uint8_t finishRB(I2CRB *rb, uint8_t status);
@ -238,6 +493,17 @@ private:
void _initialise(); void _initialise();
void _setClock(unsigned long); void _setClock(unsigned long);
#if defined(I2C_EXTENDED_ADDRESS)
// Count of I2C multiplexers found when initialising. If there is only one
// MUX then the subbus does not need de-selecting after use; however, if there
// are two or more, then the subbus must be deselected to avoid multiple
// sub-bus legs on different multiplexers being accessible simultaneously.
private:
uint8_t _muxCount = 0;
public:
uint8_t getMuxCount() { return _muxCount; }
#endif
#if !defined(I2C_USE_WIRE) #if !defined(I2C_USE_WIRE)
// I2CRB structs are queued on the following two links. // I2CRB structs are queued on the following two links.
// If there are no requests, both are NULL. // If there are no requests, both are NULL.
@ -247,42 +513,57 @@ private:
// Within the queue, each request's nextRequest field points to the // Within the queue, each request's nextRequest field points to the
// next request, or NULL. // next request, or NULL.
// Mark volatile as they are updated by IRC and read/written elsewhere. // Mark volatile as they are updated by IRC and read/written elsewhere.
static I2CRB * volatile queueHead; private:
static I2CRB * volatile queueTail; I2CRB * volatile queueHead = NULL;
static volatile uint8_t state; I2CRB * volatile queueTail = NULL;
static I2CRB * volatile currentRequest; // State is set to I2C_STATE_FREE when the interrupt handler has finished
static volatile uint8_t txCount; // the current request and is ready to complete.
static volatile uint8_t rxCount; uint8_t state = I2C_STATE_FREE;
static volatile uint8_t bytesToSend;
static volatile uint8_t bytesToReceive;
static volatile uint8_t operation;
static volatile unsigned long startTime;
static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. // CompletionStatus may be set by the interrupt handler at any time but is
// not written to the I2CRB until the state is I2C_STATE_FREE.
uint8_t completionStatus = I2C_STATUS_OK;
uint8_t overallStatus = I2C_STATUS_OK;
I2CRB * currentRequest = NULL;
uint8_t txCount = 0;
uint8_t rxCount = 0;
uint8_t bytesToSend = 0;
uint8_t bytesToReceive = 0;
uint8_t operation = 0;
unsigned long startTime = 0;
uint8_t muxPhase = 0;
uint8_t muxAddress = 0;
uint8_t muxData[1];
uint8_t deviceAddress;
const uint8_t *sendBuffer;
uint8_t *receiveBuffer;
volatile uint32_t pendingClockSpeed = 0;
void startTransaction(); void startTransaction();
// Low-level hardware manipulation functions. // Low-level hardware manipulation functions.
static void I2C_init(); void I2C_init();
static void I2C_setClock(unsigned long i2cClockSpeed); void I2C_setClock(unsigned long i2cClockSpeed);
static void I2C_handleInterrupt(); void I2C_handleInterrupt();
static void I2C_sendStart(); void I2C_sendStart();
static void I2C_sendStop(); void I2C_sendStop();
static void I2C_close(); void I2C_close();
public: public:
// setTimeout sets the timout value for I2C transactions.
// TODO: Get I2C timeout working before uncommenting the code below.
void setTimeout(unsigned long value) { (void)value; /* timeout = value; */ };
// handleInterrupt needs to be public to be called from the ISR function! // handleInterrupt needs to be public to be called from the ISR function!
static void handleInterrupt(); void handleInterrupt();
#endif #endif
}; };
// Pointer to class instance (Note: if there is more than one bus, each will have
// its own instance of I2CManager, selected by the queueRequest function from
// the I2CBus field within the request block's I2CAddress).
extern I2CManagerClass I2CManager; extern I2CManagerClass I2CManager;
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie. All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -22,6 +22,7 @@
#include <Arduino.h> #include <Arduino.h>
#include "I2CManager.h" #include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
#include <avr/io.h> #include <avr/io.h>
#include <avr/interrupt.h> #include <avr/interrupt.h>
@ -94,12 +95,13 @@ void I2CManagerClass::I2C_init()
* Initiate a start bit for transmission. * Initiate a start bit for transmission.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_sendStart() { void I2CManagerClass::I2C_sendStart() {
bytesToSend = currentRequest->writeLen; rxCount = 0;
bytesToReceive = currentRequest->readLen; txCount = 0;
// We may have initiated a stop bit before this without waiting for it. // We may have already triggered a stop bit in the same run as this. To avoid
// Wait for stop bit to be sent before sending start. // clearing that bit before the stop bit has been sent, we can either wait for
while (TWCR & (1<<TWSTO)) {} // it to complete or we can OR the bit onto the existing bits.
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start TWCR |= (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
} }
/*************************************************************************** /***************************************************************************
@ -107,7 +109,7 @@ void I2CManagerClass::I2C_sendStart() {
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_sendStop() { void I2CManagerClass::I2C_sendStop() {
TWDR = 0xff; // Default condition = SDA released TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO); // Send Stop
} }
/*************************************************************************** /***************************************************************************
@ -115,9 +117,8 @@ void I2CManagerClass::I2C_sendStop() {
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_close() { void I2CManagerClass::I2C_close() {
// disable TWI // disable TWI
I2C_sendStop();
while (TWCR & (1<<TWSTO)) {}
TWCR = (1<<TWINT); // clear any interrupt and stop twi. TWCR = (1<<TWINT); // clear any interrupt and stop twi.
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
} }
/*************************************************************************** /***************************************************************************
@ -125,37 +126,51 @@ void I2CManagerClass::I2C_close() {
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function * if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()). * (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() { void I2CManagerClass::I2C_handleInterrupt() {
if (!(TWCR & (1<<TWINT))) return; // Nothing to do. if (!(TWCR & (1<<TWINT))) return; // Nothing to do.
uint8_t twsr = TWSR & 0xF8; uint8_t twsr = TWSR & 0xF8;
// Main I2C interrupt handler, used for the device communications.
// The following variables are used:
// bytesToSend, bytesToReceive (R/W)
// txCount, rxCount (W)
// deviceAddress (R)
// sendBuffer, receiveBuffer (R)
// operation (R)
// state, completionStatus (W)
//
// Cases are ordered so that the most frequently used ones are tested first. // Cases are ordered so that the most frequently used ones are tested first.
switch (twsr) { switch (twsr) {
case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received
case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received
if (bytesToSend) { // Send first. if (bytesToSend) { // Send first.
if (operation == OPERATION_SEND_P) if (operation == OPERATION_SEND_P)
TWDR = GETFLASH(currentRequest->writeBuffer + (txCount++)); TWDR = GETFLASH(sendBuffer + (txCount++));
else else
TWDR = currentRequest->writeBuffer[txCount++]; TWDR = sendBuffer[txCount++];
bytesToSend--; bytesToSend--;
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA); TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
} else if (bytesToReceive) { // All sent, anything to receive? } else if (bytesToReceive) { // All sent, anything to receive?
while (TWCR & (1<<TWSTO)) {} // Wait for stop to be sent // Don't need to wait for stop, as the interface won't send the start until
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start // any in-progress stop condition from previous interrupts has been sent.
} else { // Nothing left to send or receive TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTA); // Send Start
TWDR = 0xff; // Default condition = SDA released } else {
// Nothing left to send or receive
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
} }
break; break;
case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted
if (bytesToReceive > 0) { if (bytesToReceive > 0) {
currentRequest->readBuffer[rxCount++] = TWDR; receiveBuffer[rxCount++] = TWDR;
bytesToReceive--; bytesToReceive--;
} }
/* fallthrough */ /* fallthrough */
case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received
if (bytesToReceive <= 1) { if (bytesToReceive <= 1) {
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception
@ -164,45 +179,51 @@ void I2CManagerClass::I2C_handleInterrupt() {
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA); TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
} }
break; break;
case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted
if (bytesToReceive > 0) { if (bytesToReceive > 0) {
currentRequest->readBuffer[rxCount++] = TWDR; receiveBuffer[rxCount++] = TWDR;
bytesToReceive--; bytesToReceive--;
} }
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
break; break;
case TWI_START: // START has been transmitted case TWI_START: // START has been transmitted
case TWI_REP_START: // Repeated START has been transmitted case TWI_REP_START: // Repeated START has been transmitted
// Set up address and R/W // Set up address and R/W
if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend)) if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend))
TWDR = (currentRequest->i2cAddress << 1) | 1; // SLA+R TWDR = (deviceAddress << 1) | 1; // SLA+R
else else
TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W TWDR = (deviceAddress << 1) | 0; // SLA+W
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA); TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
break; break;
case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received
case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received
case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received
TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
break; break;
case TWI_ARB_LOST: // Arbitration lost case TWI_ARB_LOST: // Arbitration lost
// Restart transaction from start. // Restart transaction from start.
I2C_sendStart(); I2C_sendStart();
break; break;
case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition
default: default:
TWDR = 0xff; // Default condition = SDA released TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_TRANSMIT_ERROR; completionStatus = I2C_STATUS_TRANSMIT_ERROR;
state = I2C_STATE_COMPLETED;
} }
} }
#if defined(I2C_USE_INTERRUPTS) #if defined(I2C_USE_INTERRUPTS)
ISR(TWI_vect) { ISR(TWI_vect) {
I2CManagerClass::handleInterrupt(); I2CManager.handleInterrupt();
} }
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie. All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -28,21 +28,21 @@
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) {
uint16_t t_rise; uint16_t t_rise;
if (i2cClockSpeed < 200000) { if (i2cClockSpeed < 200000)
i2cClockSpeed = 100000;
t_rise = 1000; t_rise = 1000;
} else if (i2cClockSpeed < 800000) { else if (i2cClockSpeed < 800000)
i2cClockSpeed = 400000;
t_rise = 300; t_rise = 300;
} else if (i2cClockSpeed < 1200000) { else
i2cClockSpeed = 1000000;
t_rise = 120; t_rise = 120;
} else {
i2cClockSpeed = 100000; if (t_rise == 120)
t_rise = 1000; TWI0.CTRLA |= TWI_FMPEN_bm;
} else
TWI0.CTRLA &= ~TWI_FMPEN_bm;
uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000
* t_rise / 1000 - 10) / 2; * t_rise / 1000 - 10) / 2;
if (baud > 255) baud = 255; // ~30kHz
TWI0.MBAUD = (uint8_t)baud; TWI0.MBAUD = (uint8_t)baud;
} }
@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init()
pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SDA, INPUT_PULLUP);
pinMode(PIN_WIRE_SCL, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP);
PORTMUX.TWISPIROUTEA |= TWI_MUX; PORTMUX.TWISPIROUTEA |= TWI_MUX;
I2C_setClock(I2C_FREQ);
#if defined(I2C_USE_INTERRUPTS) #if defined(I2C_USE_INTERRUPTS)
TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm;
#else #else
TWI0.MCTRLA = TWI_ENABLE_bm; TWI0.MCTRLA = TWI_ENABLE_bm;
#endif #endif
I2C_setClock(I2C_FREQ);
TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
} }
@ -68,8 +68,8 @@ void I2CManagerClass::I2C_init()
* Initiate a start bit for transmission, followed by address and R/W * Initiate a start bit for transmission, followed by address and R/W
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_sendStart() { void I2CManagerClass::I2C_sendStart() {
bytesToSend = currentRequest->writeLen; txCount = 0;
bytesToReceive = currentRequest->readLen; rxCount = 0;
// If anything to send, initiate write. Otherwise initiate read. // If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
@ -89,7 +89,10 @@ void I2CManagerClass::I2C_sendStop() {
* Close I2C down * Close I2C down
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_close() { void I2CManagerClass::I2C_close() {
I2C_sendStop();
TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C
TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc;
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
} }
/*************************************************************************** /***************************************************************************
@ -105,38 +108,34 @@ void I2CManagerClass::I2C_handleInterrupt() {
I2C_sendStart(); // Reinitiate request I2C_sendStart(); // Reinitiate request
} else if (currentStatus & TWI_BUSERR_bm) { } else if (currentStatus & TWI_BUSERR_bm) {
// Bus error // Bus error
state = I2C_STATUS_BUS_ERROR; completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
TWI0.MSTATUS = currentStatus; // clear all flags TWI0.MSTATUS = currentStatus; // clear all flags
} else if (currentStatus & TWI_WIF_bm) { } else if (currentStatus & TWI_WIF_bm) {
// Master write completed // Master write completed
if (currentStatus & TWI_RXACK_bm) { if (currentStatus & TWI_RXACK_bm) {
// Nacked, send stop. // Nacked, send stop.
TWI0.MCTRLB = TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_MCMD_STOP_gc;
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) { } else if (bytesToSend) {
// Acked, so send next byte // Acked, so send next byte (don't need to use GETFLASH)
if (currentRequest->operation == OPERATION_SEND_P) TWI0.MDATA = sendBuffer[txCount++];
TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++));
else
TWI0.MDATA = currentRequest->writeBuffer[txCount++];
bytesToSend--; bytesToSend--;
} else if (bytesToReceive) { } else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit. // Last sent byte acked and no more to send. Send repeated start, address and read bit.
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; TWI0.MADDR = (deviceAddress << 1) | 1;
} else { } else {
// No more data to send/receive. Initiate a STOP condition. // No more data to send/receive. Initiate a STOP condition.
TWI0.MCTRLB = TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_MCMD_STOP_gc;
state = I2C_STATUS_OK; // Done state = I2C_STATE_COMPLETED;
} }
} else if (currentStatus & TWI_RIF_bm) { } else if (currentStatus & TWI_RIF_bm) {
// Master read completed without errors // Master read completed without errors
if (bytesToReceive) { if (bytesToReceive) {
currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte receiveBuffer[rxCount++] = TWI0.MDATA; // Store received byte
bytesToReceive--; bytesToReceive--;
} else {
// Buffer full, issue nack/stop
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
state = I2C_STATUS_OK;
} }
if (bytesToReceive) { if (bytesToReceive) {
// More bytes to receive, issue ack and start another read // More bytes to receive, issue ack and start another read
@ -144,7 +143,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
} else { } else {
// Transaction finished, issue NACK and STOP. // Transaction finished, issue NACK and STOP.
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
} }
} }
} }
@ -154,7 +153,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
* Interrupt handler. * Interrupt handler.
***************************************************************************/ ***************************************************************************/
ISR(TWI0_TWIM_vect) { ISR(TWI0_TWIM_vect) {
I2CManagerClass::handleInterrupt(); I2CManager.handleInterrupt();
} }
#endif #endif

View File

@ -1,5 +1,7 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie
* © 2022 Paul M Antoine
* All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -22,19 +24,62 @@
#include <Arduino.h> #include <Arduino.h>
#include "I2CManager.h" #include "I2CManager.h"
#if defined(I2C_USE_INTERRUPTS)
#include <util/atomic.h> // Support for atomic isolation (i.e. a block with interrupts disabled).
// E.g.
// ATOMIC_BLOCK() {
// doSomethingWithInterruptsDisabled();
// }
// This has the advantage over simple noInterrupts/Interrupts that the
// original interrupt state is restored when the block finishes.
//
// (This should really be defined in an include file somewhere more global, so
// it can replace use of noInterrupts/interrupts in other parts of DCC-EX.
//
static inline uint8_t _deferInterrupts(void) {
noInterrupts();
return 1;
}
static inline void _conditionalEnableInterrupts(bool *wasEnabled) {
if (*wasEnabled) interrupts();
}
#define ATOMIC_BLOCK(x) \
for (bool _int_saved __attribute__((__cleanup__(_conditionalEnableInterrupts))) \
=_getInterruptState(),_ToDo=_deferInterrupts(); _ToDo; _ToDo=0)
#if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc.
static inline bool _getInterruptState(void) {
return bitRead(SREG, SREG_I); // true if enabled, false if disabled
}
#elif defined(__arm__) // STM32, SAMD, Teensy
static inline bool _getInterruptState( void ) {
uint32_t reg;
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
return !(reg & 1); // true if interrupts enabled, false otherwise
}
#else #else
#define ATOMIC_BLOCK(x) #warning "ATOMIC_BLOCK() not defined for this target type, I2C interrupts disabled"
#define ATOMIC_RESTORESTATE #define ATOMIC_BLOCK(x) // expand to nothing.
#ifdef I2C_USE_INTERRUPTS
#undef I2C_USE_INTERRUPTS
#endif
#endif #endif
// This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here // This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here
// to get intellisense to work correctly. // to get intellisense to work correctly.
#if defined(I2C_USE_WIRE) #if defined(I2C_USE_WIRE)
#undef I2C_USE_WIRE #undef I2C_USE_WIRE
#endif #endif
enum MuxPhase: uint8_t {
MuxPhase_OFF = 0,
MuxPhase_PROLOG,
MuxPhase_PAYLOAD,
MuxPhase_EPILOG,
} ;
/*************************************************************************** /***************************************************************************
* Initialise the I2CManagerAsync class. * Initialise the I2CManagerAsync class.
***************************************************************************/ ***************************************************************************/
@ -43,31 +88,82 @@ void I2CManagerClass::_initialise()
queueHead = queueTail = NULL; queueHead = queueTail = NULL;
state = I2C_STATE_FREE; state = I2C_STATE_FREE;
I2C_init(); I2C_init();
_setClock(_clockSpeed);
} }
/*************************************************************************** /***************************************************************************
* Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast) * Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast)
* on Arduino. Mega4809 supports 1000000 (Fast+) too. * on Arduino. Mega4809 supports 1000000 (Fast+) too.
* This function saves the desired clock speed and the startTransaction
* function acts on it before a new transaction, to avoid speed changes
* during an I2C transaction.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
I2C_setClock(i2cClockSpeed); pendingClockSpeed = i2cClockSpeed;
} }
/*************************************************************************** /***************************************************************************
* Helper function to start operations, if the I2C interface is free and * Start an I2C transaction, if the I2C interface is free and
* there is a queued request to be processed. * there is a queued request to be processed.
* If there's an I2C clock speed change pending, then implement it before
* starting the operation.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::startTransaction() { void I2CManagerClass::startTransaction() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK() {
if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) {
state = I2C_STATE_ACTIVE; state = I2C_STATE_ACTIVE;
completionStatus = I2C_STATUS_OK;
// Check for pending clock speed change
if (pendingClockSpeed) {
// We're about to start a new I2C transaction, so set clock now.
I2C_setClock(pendingClockSpeed);
pendingClockSpeed = 0;
}
startTime = micros();
currentRequest = queueHead; currentRequest = queueHead;
rxCount = txCount = 0; rxCount = txCount = 0;
// Copy key fields to static data for speed.
operation = currentRequest->operation;
// Start the I2C process going. // Start the I2C process going.
#if defined(I2C_EXTENDED_ADDRESS)
I2CMux muxNumber = currentRequest->i2cAddress.muxNumber();
if (muxNumber != I2CMux_None) {
muxPhase = MuxPhase_PROLOG;
uint8_t subBus = currentRequest->i2cAddress.subBus();
muxData[0] = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
#if defined(I2CMUX_PCA9547)
0x08 | subBus;
#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544)
0x04 | subBus; // NB Only 2 or 4 subbuses respectively
#else
// Default behaviour for most MUXs is to use a mask
// with a bit set for the subBus to be enabled
1 << subBus;
#endif
deviceAddress = I2C_MUX_BASE_ADDRESS + muxNumber;
sendBuffer = &muxData[0];
bytesToSend = 1;
bytesToReceive = 0;
operation = OPERATION_SEND;
} else {
// Send/receive payload for device only.
muxPhase = MuxPhase_OFF;
deviceAddress = currentRequest->i2cAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
}
#else
deviceAddress = currentRequest->i2cAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
#endif
I2C_sendStart(); I2C_sendStart();
startTime = micros();
} }
} }
} }
@ -78,8 +174,7 @@ void I2CManagerClass::startTransaction() {
void I2CManagerClass::queueRequest(I2CRB *req) { void I2CManagerClass::queueRequest(I2CRB *req) {
req->status = I2C_STATUS_PENDING; req->status = I2C_STATUS_PENDING;
req->nextRequest = NULL; req->nextRequest = NULL;
ATOMIC_BLOCK() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (!queueTail) if (!queueTail)
queueHead = queueTail = req; // Only item on queue queueHead = queueTail = req; // Only item on queue
else else
@ -92,7 +187,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
/*************************************************************************** /***************************************************************************
* Initiate a write to an I2C device (non-blocking operation) * Initiate a write to an I2C device (non-blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) { uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) {
// Make sure previous request has completed. // Make sure previous request has completed.
req->wait(); req->wait();
req->setWriteParams(i2cAddress, writeBuffer, writeLen); req->setWriteParams(i2cAddress, writeBuffer, writeLen);
@ -103,7 +198,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, u
/*************************************************************************** /***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation) * Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) { uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) {
// Make sure previous request has completed. // Make sure previous request has completed.
req->wait(); req->wait();
req->setWriteParams(i2cAddress, writeBuffer, writeLen); req->setWriteParams(i2cAddress, writeBuffer, writeLen);
@ -116,7 +211,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer
* Initiate a read from the I2C device, optionally preceded by a write * Initiate a read from the I2C device, optionally preceded by a write
* (non-blocking operation) * (non-blocking operation)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req)
{ {
// Make sure previous request has completed. // Make sure previous request has completed.
@ -126,31 +221,54 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r
return I2C_STATUS_OK; return I2C_STATUS_OK;
} }
/***************************************************************************
* Set I2C timeout value in microseconds. The timeout applies to the entire
* I2CRB request, e.g. where a write+read is performed, the timer is not
* reset before the read.
***************************************************************************/
void I2CManagerClass::setTimeout(unsigned long value) {
_timeout = value;
};
/*************************************************************************** /***************************************************************************
* checkForTimeout() function, called from isBusy() and wait() to cancel * checkForTimeout() function, called from isBusy() and wait() to cancel
* requests that are taking too long to complete. * requests that are taking too long to complete. Such faults
* This function doesn't fully work as intended so is not currently called.
* Instead we check for an I2C hang-up and report an error from
* I2CRB::wait(), but we aren't able to recover from the hang-up. Such faults
* may be caused by an I2C wire short for example. * may be caused by an I2C wire short for example.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::checkForTimeout() { void I2CManagerClass::checkForTimeout() {
unsigned long currentMicros = micros(); ATOMIC_BLOCK() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
I2CRB *t = queueHead; I2CRB *t = queueHead;
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) { if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
// Check for timeout // Check for timeout
if (currentMicros - startTime > timeout) { unsigned long elapsed = micros() - startTime;
if (elapsed > _timeout) {
#ifdef DIAG_IO
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
#endif
// Excessive time. Dequeue request // Excessive time. Dequeue request
queueHead = t->nextRequest; queueHead = t->nextRequest;
if (!queueHead) queueTail = NULL; if (!queueHead) queueTail = NULL;
currentRequest = NULL; currentRequest = NULL;
bytesToReceive = bytesToSend = 0;
// Post request as timed out. // Post request as timed out.
t->status = I2C_STATUS_TIMEOUT; t->status = I2C_STATUS_TIMEOUT;
// Reset TWI interface so it is able to continue // Reset TWI interface so it is able to continue
// Try close and init, not entirely satisfactory but sort of works... // Try close and init, not entirely satisfactory but sort of works...
I2C_close(); // Shutdown and restart twi interface I2C_close(); // Shutdown and restart twi interface
// If SDA is stuck low, issue up to 9 clock pulses to attempt to free it.
pinMode(SCL, INPUT_PULLUP);
pinMode(SDA, INPUT_PULLUP);
for (int i=0; !digitalRead(SDA) && i<9; i++) {
digitalWrite(SCL, 0);
pinMode(SCL, OUTPUT); // Force clock low
delayMicroseconds(10); // ... for 5us
pinMode(SCL, INPUT_PULLUP); // ... then high
delayMicroseconds(10); // ... for 5us (100kHz Clock)
}
// Whether that's succeeded or not, now try reinitialising.
I2C_init(); I2C_init();
_setClock(_clockSpeed);
state = I2C_STATE_FREE; state = I2C_STATE_FREE;
// Initiate next queued request if any. // Initiate next queued request if any.
@ -167,10 +285,8 @@ void I2CManagerClass::loop() {
#if !defined(I2C_USE_INTERRUPTS) #if !defined(I2C_USE_INTERRUPTS)
handleInterrupt(); handleInterrupt();
#endif #endif
// Timeout is now reported in I2CRB::wait(), not here. // Call function to monitor for stuck I2C operations.
// I've left the code, commented out, as a reminder to look at this again checkForTimeout();
// in the future.
//checkForTimeout();
} }
/*************************************************************************** /***************************************************************************
@ -182,43 +298,85 @@ void I2CManagerClass::handleInterrupt() {
// Update hardware state machine // Update hardware state machine
I2C_handleInterrupt(); I2C_handleInterrupt();
// Enable interrupts to minimise effect on other interrupt code
interrupts();
// Check if current request has completed. If there's a current request // Check if current request has completed. If there's a current request
// and state isn't active then state contains the completion status of the request. // and state isn't active then state contains the completion status of the request.
if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
// Operation has completed.
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|| currentRequest->operation & OPERATION_NORETRY)
{
// Status is OK, or has failed and retry count exceeded, or retries disabled.
#if defined(I2C_EXTENDED_ADDRESS)
if (muxPhase == MuxPhase_PROLOG ) {
overallStatus = completionStatus;
uint8_t rbAddress = currentRequest->i2cAddress.deviceAddress();
if (completionStatus == I2C_STATUS_OK && rbAddress != 0) {
// Mux request OK, start handling application request.
muxPhase = MuxPhase_PAYLOAD;
deviceAddress = rbAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
state = I2C_STATE_ACTIVE;
I2C_sendStart();
return;
}
} else if (muxPhase == MuxPhase_PAYLOAD) {
// Application request completed, now send epilogue to mux
overallStatus = completionStatus;
currentRequest->nBytes = rxCount; // Save number of bytes read into rb
if (_muxCount == 1) {
// Only one MUX, don't need to deselect subbus
muxPhase = MuxPhase_OFF;
} else {
muxPhase = MuxPhase_EPILOG;
deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber();
muxData[0] = 0x00;
sendBuffer = &muxData[0];
bytesToSend = 1;
bytesToReceive = 0;
operation = OPERATION_SEND;
state = I2C_STATE_ACTIVE;
I2C_sendStart();
return;
}
} else if (muxPhase == MuxPhase_EPILOG) {
// Epilog finished, ignore completionStatus
muxPhase = MuxPhase_OFF;
} else
overallStatus = completionStatus;
#else
overallStatus = completionStatus;
currentRequest->nBytes = rxCount;
#endif
// Remove completed request from head of queue // Remove completed request from head of queue
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
I2CRB * t = queueHead; I2CRB * t = queueHead;
if (t == queueHead) { if (t == currentRequest) {
queueHead = t->nextRequest; queueHead = t->nextRequest;
if (!queueHead) queueTail = queueHead; if (!queueHead) queueTail = queueHead;
t->nBytes = rxCount; t->status = overallStatus;
t->status = state;
// I2C state machine is now free for next request // I2C state machine is now free for next request
currentRequest = NULL; currentRequest = NULL;
state = I2C_STATE_FREE; state = I2C_STATE_FREE;
}
retryCounter = 0;
} else {
// Status is failed and retry permitted.
// Retry previous request.
state = I2C_STATE_FREE;
}
}
// Start next request (if any) if (state == I2C_STATE_FREE && queueHead != NULL) {
// Allow any pending interrupts before starting the next request.
//interrupts();
// Start next request
I2CManager.startTransaction(); I2CManager.startTransaction();
} }
}
}
} }
// Fields in I2CManager class specific to Non-blocking implementation.
I2CRB * volatile I2CManagerClass::queueHead = NULL;
I2CRB * volatile I2CManagerClass::queueTail = NULL;
I2CRB * volatile I2CManagerClass::currentRequest = NULL;
volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE;
volatile uint8_t I2CManagerClass::txCount;
volatile uint8_t I2CManagerClass::rxCount;
volatile uint8_t I2CManagerClass::operation;
volatile uint8_t I2CManagerClass::bytesToSend;
volatile uint8_t I2CManagerClass::bytesToReceive;
volatile unsigned long I2CManagerClass::startTime;
unsigned long I2CManagerClass::timeout = 0;
#endif #endif

247
I2CManager_SAMD.h Normal file
View File

@ -0,0 +1,247 @@
/*
* © 2022 Paul M Antoine
* © 2023, Neil McKechnie
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef I2CMANAGER_SAMD_H
#define I2CMANAGER_SAMD_H
#include <Arduino.h>
#include "I2CManager.h"
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO)
void SERCOM3_Handler() {
I2CManager.handleInterrupt();
}
#endif
// Assume SERCOM3 for now - default I2C bus on Arduino Zero and variants of same
Sercom *s = SERCOM3;
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
int t_rise;
if (i2cClockSpeed < 200000L) {
i2cClockSpeed = 100000L; // NB: this overrides a "force clock" of lower than 100KHz!
t_rise = 1000;
} else if (i2cClockSpeed < 800000L) {
i2cClockSpeed = 400000L;
t_rise = 300;
} else if (i2cClockSpeed < 1200000L) {
i2cClockSpeed = 1000000L;
t_rise = 120;
} else {
i2cClockSpeed = 100000L;
t_rise = 1000;
}
// Wait while the bus is busy
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
// Disable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 0 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Calculate baudrate - using a rise time appropriate for the speed
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000));
// Enable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 1 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Setting bus idle mode and wait for sync
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
//Setting clock
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM3_CORE) | // Generic Clock 0 (SERCOM3)
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
GCLK_CLKCTRL_CLKEN ;
/* Wait for peripheral clock synchronization */
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
// Software reset the SERCOM
s->I2CM.CTRLA.bit.SWRST = 1;
//Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0
while(s->I2CM.CTRLA.bit.SWRST || s->I2CM.SYNCBUSY.bit.SWRST);
// Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit)
s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* |
SERCOM_I2CM_CTRLA_SCLSM*/ ;
// Enable Smart mode (but not Quick Command)
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_EnableIRQ(SERCOM3_IRQn);
NVIC_SetPriority (SERCOM3_IRQn, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities
// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest priority
// Enable all interrupts
s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR;
#endif
// Calculate baudrate and set default rate for now
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * I2C_FREQ) - 7 / (2 * 1000);
// Enable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 1 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Setting bus idle mode and wait for sync
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
// Set SDA/SCL pins as outputs and enable pullups, at present we assume these are
// the default ones for SERCOM3 (see assumption above)
pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType);
pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType);
// Enable the SCL and SDA pins on the sercom: includes increased driver strength,
// pull-up resistors and pin multiplexer
PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg =
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg =
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
}
/***************************************************************************
* Initiate a start bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
txCount = 0;
rxCount = 0;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start and address with read flag (1) or'd in
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
}
else {
// Send start and address with write flag (0) or'd in
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1ul) | 0;
}
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->I2CM.CTRLB.bit.CMD = 3; // Stop condition
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
I2C_sendStop();
// Disable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 0 ;
// Wait for up to 500us only.
unsigned long startTime = micros();
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) {
if (micros() - startTime >= 500UL) break;
}
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler or,
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
if (s->I2CM.STATUS.bit.ARBLOST) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->I2CM.STATUS.bit.BUSERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (s->I2CM.INTFLAG.bit.MB) {
// Master write completed
if (s->I2CM.STATUS.bit.RXNACK) {
// Nacked, send stop.
I2C_sendStop();
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (bytesToSend) {
// Acked, so send next byte
s->I2CM.DATA.bit.DATA = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// No more data to send/receive. Initiate a STOP condition
I2C_sendStop();
state = I2C_STATE_COMPLETED; // Completed OK
}
} else if (s->I2CM.INTFLAG.bit.SB) {
// Master read completed without errors
if (bytesToReceive == 1) {
s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED; // Completed OK
} else if (bytesToReceive) {
s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
bytesToReceive--;
}
}
}
#endif /* I2CMANAGER_SAMD_H */

312
I2CManager_STM32.h Normal file
View File

@ -0,0 +1,312 @@
/*
* © 2022-23 Paul M Antoine
* © 2023, Neil McKechnie
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef I2CMANAGER_STM32_H
#define I2CMANAGER_STM32_H
#include <Arduino.h>
#include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
void I2C1_IRQHandler() {
I2CManager.handleInterrupt();
}
#endif
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
I2C_TypeDef *s = I2C1;
#define I2C_IRQn I2C1_EV_IRQn
#define I2C_BUSFREQ 16
// I2C SR1 Status Register #1 bit definitions for convenience
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
// #define I2C_SR1_TIMEOUT (1<<14) // Timeout of Tlow error
// #define I2C_SR1_PECERR (1<<12) // PEC error in reception
// #define I2C_SR1_OVR (1<<11) // Overrun/Underrun error
// #define I2C_SR1_AF (1<<10) // Acknowledge failure
// #define I2C_SR1_ARLO (1<<9) // Arbitration lost (master mode)
// #define I2C_SR1_BERR (1<<8) // Bus error (misplaced start or stop condition)
// #define I2C_SR1_TxE (1<<7) // Data register empty on transmit
// #define I2C_SR1_RxNE (1<<6) // Data register not empty on receive
// #define I2C_SR1_STOPF (1<<4) // Stop detection (slave mode)
// #define I2C_SR1_ADD10 (1<<3) // 10 bit header sent
// #define I2C_SR1_BTF (1<<2) // Byte transfer finished - data transfer done
// #define I2C_SR1_ADDR (1<<1) // Address sent (master) or matched (slave)
// #define I2C_SR1_SB (1<<0) // Start bit (master mode) 1=start condition generated
// I2C CR1 Control Register #1 bit definitions for convenience
// #define I2C_CR1_SWRST (1<<15) // Software reset - places peripheral under reset
// #define I2C_CR1_ALERT (1<<13) // SMBus alert assertion
// #define I2C_CR1_PEC (1<<12) // Packet Error Checking transfer in progress
// #define I2C_CR1_POS (1<<11) // Acknowledge/PEC Postion (for data reception in PEC mode)
// #define I2C_CR1_ACK (1<<10) // Acknowledge enable - ACK returned after byte is received (address or data)
// #define I2C_CR1_STOP (1<<9) // STOP generated
// #define I2C_CR1_START (1<<8) // START generated
// #define I2C_CR1_NOSTRETCH (1<<7) // Clock stretching disable (slave mode)
// #define I2C_CR1_ENGC (1<<6) // General call (broadcast) enable (address 00h is ACKed)
// #define I2C_CR1_ENPEC (1<<5) // PEC Enable
// #define I2C_CR1_ENARP (1<<4) // ARP enable (SMBus)
// #define I2C_CR1_SMBTYPE (1<<3) // SMBus type, 1=host, 0=device
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
uint16_t t_rise;
uint32_t ccr_freq;
if (i2cClockSpeed < 200000L) {
// i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
else if (i2cClockSpeed < 800000L)
{
i2cClockSpeed = 400000L;
t_rise = 0x06; // (300ns / 62.5ns) + 1;
// } else if (i2cClockSpeed < 1200000L) {
// i2cClockSpeed = 1000000L;
// t_rise = 120;
}
else
{
i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
// Enable the I2C master mode
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
// Software reset the I2C peripheral
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
// Release reset
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Calculate baudrate - using a rise time appropriate for the speed
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = (uint16_t)ccr_freq;
// Configure the rise time register
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
//Setting up the clocks
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
// Standard I2C pins are SCL on PB8 and SDA on PB9
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
// Alt Function High register routing pins PB8 and PB9 for I2C1:
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
// Software reset the I2C peripheral
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Program the peripheral input clock in CR2 Register in order to generate correct timings
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C_IRQn);
// CR2 Interrupt Settings
// Bit 15-13: reserved
// Bit 12: LAST - DMA last transfer
// Bit 11: DMAEN - DMA enable
// Bit 10: ITBUFEN - Buffer interrupt enable
// Bit 9: ITEVTEN - Event interrupt enable
// Bit 8: ITERREN - Error interrupt enable
// Bit 7-6: reserved
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
s->CR2 |= 0x0300; // Enable Event and Error interrupts
#endif
// Calculate baudrate and set default rate for now
// Configure the Clock Control Register for 100KHz SCL frequency
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = 0x0050;
// Configure the rise time register - max allowed in 1000ns
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
// Setting bus idle mode and wait for sync
}
/***************************************************************************
* Initiate a start bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
rxCount = txCount = 0;
uint8_t temp;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start for read operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
// Special case for 1 byte reads!
if (bytesToReceive == 1)
{
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
else
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
else {
// Send start for write operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
I2C_sendStop();
// Disable the I2C master mode and wait for sync
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
// Should never happen, but wait for up to 500us only.
unsigned long startTime = micros();
while ((s->CR1 && I2C_CR1_PE) != 0) {
if (micros() - startTime >= 500UL) break;
}
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler or,
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
if (s->SR1 && I2C_SR1_ARLO) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->SR1 && I2C_SR1_BERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
} else if (s->SR1 && I2C_SR1_TXE) {
// Master write completed
if (s->SR1 && (1<<10)) {
// Nacked, send stop.
I2C_sendStop();
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) {
// Acked, so send next byte
s->DR = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// Check both TxE/BTF == 1 before generating stop
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
// No more data to send/receive. Initiate a STOP condition and finish
I2C_sendStop();
state = I2C_STATE_COMPLETED;
}
} else if (s->SR1 && I2C_SR1_RXNE) {
// Master read completed without errors
if (bytesToReceive == 1) {
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED;
} else if (bytesToReceive) {
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
}
}
#endif /* I2CMANAGER_STM32_H */

View File

@ -1,5 +1,5 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie. All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
@ -30,11 +30,19 @@
#define I2C_USE_WIRE #define I2C_USE_WIRE
#endif #endif
// Older versions of Wire don't have setWireTimeout function. AVR does.
#ifdef ARDUINO_ARCH_AVR
#define WIRE_HAS_TIMEOUT
#endif
/*************************************************************************** /***************************************************************************
* Initialise I2C interface software * Initialise I2C interface software
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::_initialise() { void I2CManagerClass::_initialise() {
Wire.begin(); Wire.begin();
#if defined(WIRE_HAS_TIMEOUT)
Wire.setWireTimeout(_timeout, true);
#endif
} }
/*************************************************************************** /***************************************************************************
@ -45,20 +53,85 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
Wire.setClock(i2cClockSpeed); Wire.setClock(i2cClockSpeed);
} }
/***************************************************************************
* Set I2C timeout value in microseconds. The timeout applies to each
* Wire call separately, i.e. in a write+read, the timer is reset before the
* read is started.
***************************************************************************/
void I2CManagerClass::setTimeout(unsigned long value) {
_timeout = value;
#if defined(WIRE_HAS_TIMEOUT)
Wire.setWireTimeout(value, true);
#endif
}
/********************************************************
* Helper function for I2C Multiplexer operations
********************************************************/
#ifdef I2C_EXTENDED_ADDRESS
static uint8_t muxSelect(I2CAddress address) {
// Select MUX sub bus.
I2CMux muxNo = address.muxNumber();
I2CSubBus subBus = address.subBus();
if (muxNo != I2CMux_None) {
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
uint8_t data = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
#if defined(I2CMUX_PCA9547)
0x08 | subBus;
#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544)
0x04 | subBus; // NB Only 2 or 4 subbuses respectively
#else
// Default behaviour for most MUXs is to use a mask
// with a bit set for the subBus to be enabled
1 << subBus;
#endif
Wire.write(&data, 1);
return Wire.endTransmission(true); // have to release I2C bus for it to work
}
return I2C_STATUS_OK;
}
#endif
/*************************************************************************** /***************************************************************************
* Initiate a write to an I2C device (blocking operation on Wire) * Initiate a write to an I2C device (blocking operation on Wire)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
uint8_t status, muxStatus;
uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block.
do {
status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None)
muxStatus = muxSelect(address);
#endif
// Only send new transaction if address is non-zero.
if (muxStatus == I2C_STATUS_OK && address != 0) {
Wire.beginTransmission(address); Wire.beginTransmission(address);
if (size > 0) Wire.write(buffer, size); if (size > 0) Wire.write(buffer, size);
rb->status = Wire.endTransmission(); status = Wire.endTransmission();
}
#ifdef I2C_EXTENDED_ADDRESS
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK
&& address.deviceAddress() != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!(status == I2C_STATUS_OK
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->status = status;
return I2C_STATUS_OK; return I2C_STATUS_OK;
} }
/*************************************************************************** /***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire) * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
uint8_t ramBuffer[size]; uint8_t ramBuffer[size];
const uint8_t *p1 = buffer; const uint8_t *p1 = buffer;
for (uint8_t i=0; i<size; i++) for (uint8_t i=0; i<size; i++)
@ -70,27 +143,64 @@ uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_
* Initiate a write (optional) followed by a read from the I2C device (blocking operation on Wire) * Initiate a write (optional) followed by a read from the I2C device (blocking operation on Wire)
* If fewer than the number of requested bytes are received, status is I2C_STATUS_TRUNCATED. * If fewer than the number of requested bytes are received, status is I2C_STATUS_TRUNCATED.
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb) const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb)
{ {
uint8_t status = I2C_STATUS_OK; uint8_t status, muxStatus;
uint8_t nBytes = 0; uint8_t nBytes = 0;
uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block.
do {
status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None) {
muxStatus = muxSelect(address);
}
#endif
// Only start new transaction if address is non-zero.
if (muxStatus == I2C_STATUS_OK && address != 0) {
if (writeSize > 0) { if (writeSize > 0) {
Wire.beginTransmission(address); Wire.beginTransmission(address);
Wire.write(writeBuffer, writeSize); Wire.write(writeBuffer, writeSize);
status = Wire.endTransmission(false); // Don't free bus yet status = Wire.endTransmission(false); // Don't free bus yet
} }
if (status == I2C_STATUS_OK) { if (status == I2C_STATUS_OK) {
#ifdef WIRE_HAS_TIMEOUT
Wire.clearWireTimeoutFlag();
Wire.requestFrom(address, (size_t)readSize);
if (!Wire.getWireTimeoutFlag()) {
while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read();
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
} else {
status = I2C_STATUS_TIMEOUT;
}
#else
Wire.requestFrom(address, (size_t)readSize); Wire.requestFrom(address, (size_t)readSize);
while (Wire.available() && nBytes < readSize) while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read(); readBuffer[nBytes++] = Wire.read();
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
#endif
} }
}
#ifdef I2C_EXTENDED_ADDRESS
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK && address != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!((status == I2C_STATUS_OK)
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->nBytes = nBytes; rb->nBytes = nBytes;
rb->status = status; rb->status = status;
return I2C_STATUS_OK; return I2C_STATUS_OK;
} }
/*************************************************************************** /***************************************************************************
* Function to queue a request block and initiate operations. * Function to queue a request block and initiate operations.
* *
@ -100,7 +210,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
* the non-blocking version. * the non-blocking version.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) { void I2CManagerClass::queueRequest(I2CRB *req) {
switch (req->operation) { switch (req->operation & OPERATION_MASK) {
case OPERATION_READ: case OPERATION_READ:
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
break; break;
@ -121,8 +231,4 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::loop() {} void I2CManagerClass::loop() {}
// Loop function
void I2CManagerClass::checkForTimeout() {}
#endif #endif

View File

@ -25,6 +25,7 @@
#include "DIAG.h" #include "DIAG.h"
#include "FSH.h" #include "FSH.h"
#include "IO_MCP23017.h" #include "IO_MCP23017.h"
#include "DCCTimer.h"
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR) #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
#define USE_FAST_IO #define USE_FAST_IO
@ -32,7 +33,7 @@
// Link to halSetup function. If not defined, the function reference will be NULL. // Link to halSetup function. If not defined, the function reference will be NULL.
extern __attribute__((weak)) void halSetup(); extern __attribute__((weak)) void halSetup();
extern __attribute__((weak)) void mySetup(); // Deprecated function name, output warning if it's declared extern __attribute__((weak)) void exrailHalSetup();
//================================================================================================================== //==================================================================================================================
// Static methods // Static methods
@ -47,32 +48,58 @@ extern __attribute__((weak)) void mySetup(); // Deprecated function name, outpu
// Create any standard device instances that may be required, such as the Arduino pins // Create any standard device instances that may be required, such as the Arduino pins
// and PCA9685. // and PCA9685.
void IODevice::begin() { void IODevice::begin() {
// Initialise the IO subsystem // Initialise the IO subsystem defaults
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
// Predefine two PCA9685 modules 0x40-0x41
// Allocates 32 pins 100-131
PCA9685::create(100, 16, 0x40);
PCA9685::create(116, 16, 0x41);
// Predefine two MCP23017 module 0x20/0x21
// Allocates 32 pins 164-195
MCP23017::create(164, 16, 0x20);
MCP23017::create(180, 16, 0x21);
// Call the begin() methods of each configured device in turn
for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) {
dev->_begin();
}
_initPhase = false;
// Check for presence of deprecated mySetup() function, and output warning.
if (mySetup)
DIAG(F("WARNING: mySetup() function should be renamed to halSetup()"));
// Call user's halSetup() function (if defined in the build in myHal.cpp). // Call user's halSetup() function (if defined in the build in myHal.cpp).
// The contents will depend on the user's system hardware configuration. // The contents will depend on the user's system hardware configuration.
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs. // The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
// This is done early so that the subsequent defaults will detect an overlap and not
// create something that conflicts with the user's vpin definitions.
if (halSetup) if (halSetup)
halSetup(); halSetup();
// include any HAL devices defined in exrail.
if (exrailHalSetup)
exrailHalSetup();
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
// Allocates 32 pins 100-131
if (checkNoOverlap(100, 16, 0x40)) {
PCA9685::create(100, 16, 0x40);
} else {
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
}
if (checkNoOverlap(116, 16, 0x41)) {
PCA9685::create(116, 16, 0x41);
} else {
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
}
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
// Allocates 32 pins 164-195
if (checkNoOverlap(164, 16, 0x20)) {
MCP23017::create(164, 16, 0x20);
} else {
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
}
if (checkNoOverlap(180, 16, 0x21)) {
MCP23017::create(180, 16, 0x21);
} else {
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
}
}
// reset() function to reinitialise all devices
void IODevice::reset() {
unsigned long currentMicros = micros();
for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) {
dev->_deviceState = DEVSTATE_DORMANT;
// First ensure that _loop isn't delaying
dev->delayUntil(currentMicros);
// Then invoke _begin to restart driver
dev->_begin();
}
} }
// Overarching static loop() method for the IODevice subsystem. Works through the // Overarching static loop() method for the IODevice subsystem. Works through the
@ -104,18 +131,19 @@ void IODevice::loop() {
// Report loop time if diags enabled // Report loop time if diags enabled
#if defined(DIAG_LOOPTIMES) #if defined(DIAG_LOOPTIMES)
unsigned long diagMicros = micros();
static unsigned long lastMicros = 0; static unsigned long lastMicros = 0;
// Measure time since loop() method started. // Measure time since HAL's loop() method started.
unsigned long halElapsed = micros() - currentMicros; unsigned long halElapsed = diagMicros - currentMicros;
// Measure time between loop() method entries. // Measure time between loop() method entries (excluding this diagnostic).
unsigned long elapsed = currentMicros - lastMicros; unsigned long elapsed = diagMicros - lastMicros;
static unsigned long maxElapsed = 0, maxHalElapsed = 0; static unsigned long maxElapsed = 0, maxHalElapsed = 0;
static unsigned long lastOutputTime = 0; static unsigned long lastOutputTime = 0;
static unsigned long halTotal = 0, total = 0; static unsigned long halTotal = 0, total = 0;
static unsigned long count = 0; static unsigned long count = 0;
const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec
// Ignore long loop counts while message is still outputting // Ignore long loop counts while message is still outputting (~3 milliseconds)
if (currentMicros - lastOutputTime > 3000UL) { if (currentMicros - lastOutputTime > 3000UL) {
if (elapsed > maxElapsed) maxElapsed = elapsed; if (elapsed > maxElapsed) maxElapsed = elapsed;
if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed; if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed;
@ -123,14 +151,16 @@ void IODevice::loop() {
total += elapsed; total += elapsed;
count++; count++;
} }
if (currentMicros - lastOutputTime > interval) { if (diagMicros - lastOutputTime > interval) {
if (lastOutputTime > 0) if (lastOutputTime > 0)
DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"), DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"),
total/count, maxElapsed, halTotal/count, maxHalElapsed); total/count, maxElapsed, halTotal/count, maxHalElapsed);
maxElapsed = maxHalElapsed = total = halTotal = count = 0; maxElapsed = maxHalElapsed = total = halTotal = count = 0;
lastOutputTime = currentMicros; lastOutputTime = diagMicros;
} }
lastMicros = currentMicros; // Read microsecond count after calculations, so they aren't
// included in the overall timings.
lastMicros = micros();
#endif #endif
} }
@ -155,7 +185,7 @@ bool IODevice::hasCallback(VPIN vpin) {
// Display (to diagnostics) details of the device. // Display (to diagnostics) details of the device.
void IODevice::_display() { void IODevice::_display() {
DIAG(F("Unknown device Vpins:%d-%d %S"), DIAG(F("Unknown device Vpins:%u-%u %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F("")); (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
} }
@ -165,7 +195,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
IODevice *dev = findDevice(vpin); IODevice *dev = findDevice(vpin);
if (dev) return dev->_configure(vpin, configType, paramCount, params); if (dev) return dev->_configure(vpin, configType, paramCount, params);
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin); DIAG(F("IODevice::configure(): VPIN %u not found!"), (int)vpin);
#endif #endif
return false; return false;
} }
@ -177,7 +207,7 @@ int IODevice::read(VPIN vpin) {
return dev->_read(vpin); return dev->_read(vpin);
} }
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin); DIAG(F("IODevice::read(): VPIN %u not found!"), (int)vpin);
#endif #endif
return false; return false;
} }
@ -189,9 +219,19 @@ int IODevice::readAnalogue(VPIN vpin) {
return dev->_readAnalogue(vpin); return dev->_readAnalogue(vpin);
} }
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); DIAG(F("IODevice::readAnalogue(): VPIN %u not found!"), (int)vpin);
#endif #endif
return false; return -1023;
}
int IODevice::configureAnalogIn(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_configureAnalogIn(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin);
#endif
return -1023;
} }
// Write value to virtual pin(s). If multiple devices are allocated the same pin // Write value to virtual pin(s). If multiple devices are allocated the same pin
@ -203,7 +243,7 @@ void IODevice::write(VPIN vpin, int value) {
return; return;
} }
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin); DIAG(F("IODevice::write(): VPIN %u not found!"), (int)vpin);
#endif #endif
} }
@ -222,7 +262,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
return; return;
} }
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin); DIAG(F("IODevice::writeAnalogue(): VPIN %u not found!"), (int)vpin);
#endif #endif
} }
@ -243,24 +283,26 @@ void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
_gpioInterruptPin = pinNumber; _gpioInterruptPin = pinNumber;
} }
// Private helper function to add a device to the chain of devices. // Helper function to add a new device to the device chain. If
void IODevice::addDevice(IODevice *newDevice) { // slaveDevice is NULL then the device is added to the end of the chain.
// Link new object to the end of the chain. Thereby, the first devices to be declared/created // Otherwise, the chain is searched for slaveDevice and the new device linked
// will be located faster by findDevice than those which are created later. // in front of it (to support filter devices that share the same VPIN range
// Ideally declare/create the digital IO pins first, then servos, then more esoteric devices. // as the devices they control). If slaveDevice isn't found, then the
IODevice *lastDevice; // device is linked to the end of the chain.
if (_firstDevice == 0) void IODevice::addDevice(IODevice *newDevice, IODevice *slaveDevice /* = NULL */) {
if (slaveDevice == _firstDevice) {
newDevice->_nextDevice = _firstDevice;
_firstDevice = newDevice; _firstDevice = newDevice;
else { } else {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
lastDevice = dev; if (dev->_nextDevice == slaveDevice || dev->_nextDevice == NULL) {
lastDevice->_nextDevice = newDevice; // Link new device between dev and slaveDevice (or at end of chain)
newDevice->_nextDevice = dev->_nextDevice;
dev->_nextDevice = newDevice;
break;
}
}
} }
newDevice->_nextDevice = 0;
// If the IODevice::begin() method has already been called, initialise device here. If not,
// the device's _begin() method will be called by IODevice::begin().
if (!_initPhase)
newDevice->_begin(); newDevice->_begin();
} }
@ -275,6 +317,49 @@ IODevice *IODevice::findDevice(VPIN vpin) {
return NULL; return NULL;
} }
// Instance helper function for filter devices (layered over others). Looks for
// a device that is further down the chain than the current device.
IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) {
VPIN firstVpin = dev->_firstVpin;
if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins)
return dev;
}
return NULL;
}
// Private helper function to check for vpin overlap. Run during setup only.
// returns true if pins DONT overlap with existing device
// TODO: Move the I2C address reservation and checks into the I2CManager code.
// That will enable non-HAL devices to reserve I2C addresses too.
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
#ifdef DIAG_IO
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
#endif
VPIN lastPin=firstPin+nPins-1;
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (nPins > 0 && dev->_nPins > 0) {
// check for pin range overlaps (verbose but compiler will fix that)
VPIN firstDevPin=dev->_firstVpin;
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
if (!noOverlap) {
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
firstPin, lastPin);
return false;
}
}
// Check for overlapping I2C address
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
return false;
}
}
return true; // no overlaps... OK to go on with constructor
}
//================================================================================================================== //==================================================================================================================
// Static data // Static data
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
@ -282,15 +367,12 @@ IODevice *IODevice::findDevice(VPIN vpin) {
// Chain of callback blocks (identifying registered callback functions for state changes) // Chain of callback blocks (identifying registered callback functions for state changes)
IONotifyCallback *IONotifyCallback::first = 0; IONotifyCallback *IONotifyCallback::first = 0;
// Start of chain of devices. // Start and end of chain of devices.
IODevice *IODevice::_firstDevice = 0; IODevice *IODevice::_firstDevice = 0;
// Reference to next device to be called on _loop() method. // Reference to next device to be called on _loop() method.
IODevice *IODevice::_nextLoopDevice = 0; IODevice *IODevice::_nextLoopDevice = 0;
// Flag which is reset when IODevice::begin has been called.
bool IODevice::_initPhase = true;
//================================================================================================================== //==================================================================================================================
// Instance members // Instance members
@ -310,7 +392,7 @@ void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) { bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false; if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]); DIAG(F("Arduino _configurePullup pin:%d Val:%d"), pin, p[0]);
#endif #endif
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT); pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
return true; return true;
@ -328,11 +410,10 @@ int IODevice::read(VPIN vpin) {
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1) return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
} }
int IODevice::readAnalogue(VPIN vpin) { int IODevice::readAnalogue(VPIN vpin) {
pinMode(vpin, INPUT); return ADCee::read(vpin);
noInterrupts(); }
int value = analogRead(vpin); int IODevice::configureAnalogIn(VPIN vpin) {
interrupts(); return ADCee::init(vpin);
return value;
} }
void IODevice::loop() {} void IODevice::loop() {}
void IODevice::DumpAll() { void IODevice::DumpAll() {
@ -434,7 +515,18 @@ int ArduinoPins::_read(VPIN vpin) {
// Device-specific readAnalogue function (analogue input) // Device-specific readAnalogue function (analogue input)
int ArduinoPins::_readAnalogue(VPIN vpin) { int ArduinoPins::_readAnalogue(VPIN vpin) {
int pin = vpin; if (vpin > 255) return -1023;
uint8_t pin = vpin;
int value = ADCee::read(pin);
#ifdef DIAG_IO
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
#endif
return value;
}
int ArduinoPins::_configureAnalogIn(VPIN vpin) {
if (vpin > 255) return -1023;
uint8_t pin = vpin;
uint8_t mask = 1 << ((pin-_firstVpin) % 8); uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8; uint8_t index = (pin-_firstVpin) / 8;
if (_pinModes[index] & mask) { if (_pinModes[index] & mask) {
@ -446,28 +538,15 @@ int ArduinoPins::_readAnalogue(VPIN vpin) {
else else
pinMode(pin, INPUT); pinMode(pin, INPUT);
} }
int value = ADCee::init(pin);
// Since AnalogRead is also called from interrupt code, disable interrupts
// while we're using it. There's only one ADC shared by all analogue inputs
// on the Arduino, so we don't want interruptions.
//******************************************************************************
// NOTE: If the HAL is running on a computer without the DCC signal generator,
// then interrupts needn't be disabled. Also, the DCC signal generator puts
// the ADC into fast mode, so if it isn't present, analogueRead calls will be much
// slower!!
//******************************************************************************
noInterrupts();
int value = analogRead(pin);
interrupts();
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value);
#endif #endif
return value; return value;
} }
void ArduinoPins::_display() { void ArduinoPins::_display() {
DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1); DIAG(F("Arduino Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
} }
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,5 @@
/* /*
* © 2023, Paul Antoine, Discord user @ADUBOURG
* © 2021, Neil McKechnie. All rights reserved. * © 2021, Neil McKechnie. All rights reserved.
* *
* This file is part of DCC++EX API * This file is part of DCC++EX API
@ -93,6 +94,8 @@ public:
CONFIGURE_INPUT = 1, CONFIGURE_INPUT = 1,
CONFIGURE_SERVO = 2, CONFIGURE_SERVO = 2,
CONFIGURE_OUTPUT = 3, CONFIGURE_OUTPUT = 3,
CONFIGURE_ANALOGOUTPUT = 4,
CONFIGURE_ANALOGINPUT = 5,
} ConfigTypeEnum; } ConfigTypeEnum;
typedef enum : uint8_t { typedef enum : uint8_t {
@ -110,6 +113,10 @@ public:
// Also, the _begin method of any existing instances is called from here. // Also, the _begin method of any existing instances is called from here.
static void begin(); static void begin();
// reset function to invoke all driver's _begin() methods again, to
// reset the state of the devices and reinitialise.
static void reset();
// configure is used invoke an IODevice instance's _configure method // configure is used invoke an IODevice instance's _configure method
static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]); static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]);
@ -143,6 +150,7 @@ public:
// read invokes the IODevice instance's _readAnalogue method. // read invokes the IODevice instance's _readAnalogue method.
static int readAnalogue(VPIN vpin); static int readAnalogue(VPIN vpin);
static int configureAnalogIn(VPIN vpin);
// loop invokes the IODevice instance's _loop method. // loop invokes the IODevice instance's _loop method.
static void loop(); static void loop();
@ -160,24 +168,12 @@ public:
// once the GPIO port concerned has been read. // once the GPIO port concerned has been read.
void setGPIOInterruptPin(int16_t pinNumber); void setGPIOInterruptPin(int16_t pinNumber);
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
protected: // Method used by IODevice filters to locate slave pins that may be overlayed by their own
// pin range.
// Constructor IODevice *findDeviceFollowing(VPIN vpin);
IODevice(VPIN firstVpin=0, int nPins=0) {
_firstVpin = firstVpin;
_nPins = nPins;
_nextEntryTime = 0;
}
// Method to perform initialisation of the device (optionally implemented within device class)
virtual void _begin() {}
// Method to configure device (optionally implemented within device class)
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
return false;
};
// Method to write new state (optionally implemented within device class) // Method to write new state (optionally implemented within device class)
virtual void _write(VPIN vpin, int value) { virtual void _write(VPIN vpin, int value) {
@ -185,7 +181,7 @@ protected:
}; };
// Method to write an 'analogue' value (optionally implemented within device class) // Method to write an 'analogue' value (optionally implemented within device class)
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) {
(void)vpin; (void)value; (void) param1; (void)param2; (void)vpin; (void)value; (void) param1; (void)param2;
}; };
@ -201,6 +197,33 @@ protected:
return 0; return 0;
}; };
protected:
// Constructor
IODevice(VPIN firstVpin=0, int nPins=0) {
_firstVpin = firstVpin;
_nPins = nPins;
_nextEntryTime = 0;
_I2CAddress=0;
}
// Method to perform initialisation of the device (optionally implemented within device class)
virtual void _begin() {}
// Method to check whether the vpin corresponds to this device
bool owns(VPIN vpin);
// Method to configure device (optionally implemented within device class)
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
return false;
};
virtual int _configureAnalogIn(VPIN vpin) {
(void)vpin;
return 0;
};
// Method to perform updates on an ongoing basis (optionally implemented within device class) // Method to perform updates on an ongoing basis (optionally implemented within device class)
virtual void _loop(unsigned long currentMicros) { virtual void _loop(unsigned long currentMicros) {
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls. delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
@ -220,7 +243,7 @@ protected:
// Common object fields. // Common object fields.
VPIN _firstVpin; VPIN _firstVpin;
int _nPins; int _nPins;
I2CAddress _I2CAddress;
// Flag whether the device supports callbacks. // Flag whether the device supports callbacks.
bool _hasCallback = false; bool _hasCallback = false;
@ -229,23 +252,20 @@ protected:
int16_t _gpioInterruptPin = -1; int16_t _gpioInterruptPin = -1;
// Static support function for subclass creation // Static support function for subclass creation
static void addDevice(IODevice *newDevice); static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL);
// Method to find device handling Vpin
static IODevice *findDevice(VPIN vpin);
// Current state of device // Current state of device
DeviceStateEnum _deviceState = DEVSTATE_DORMANT; DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
private: private:
// Method to check whether the vpin corresponds to this device
bool owns(VPIN vpin);
// Method to find device handling Vpin
static IODevice *findDevice(VPIN vpin);
IODevice *_nextDevice = 0; IODevice *_nextDevice = 0;
unsigned long _nextEntryTime; unsigned long _nextEntryTime;
static IODevice *_firstDevice; static IODevice *_firstDevice;
static IODevice *_nextLoopDevice; static IODevice *_nextLoopDevice;
static bool _initPhase;
}; };
@ -256,9 +276,7 @@ private:
class PCA9685 : public IODevice { class PCA9685 : public IODevice {
public: public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress); static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50);
// Constructor
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
enum ProfileType : uint8_t { enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified) Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration UseDuration = 0, // Use specified duration
@ -270,6 +288,8 @@ public:
}; };
private: private:
// Constructor
PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency);
// Device-specific initialisation // Device-specific initialisation
void _begin() override; void _begin() override;
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
@ -282,7 +302,6 @@ private:
void writeDevice(uint8_t pin, int value); void writeDevice(uint8_t pin, int value);
void _display() override; void _display() override;
uint8_t _I2CAddress; // 0x40-0x43 possible
struct ServoData { struct ServoData {
uint16_t activePosition : 12; // Config parameter uint16_t activePosition : 12; // Config parameter
@ -300,13 +319,14 @@ private:
struct ServoData *_servoData [16]; struct ServoData *_servoData [16];
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
static const byte FLASH _bounceProfile[30]; static const uint8_t FLASH _bounceProfile[30];
const unsigned int refreshInterval = 50; // refresh every 50ms const unsigned int refreshInterval = 50; // refresh every 50ms
// structures for setting up non-blocking writes to servo controller // structures for setting up non-blocking writes to servo controller
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[5]; uint8_t outputBuffer[5];
uint8_t prescaler; // clock prescaler for setting PWM frequency
}; };
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
@ -317,10 +337,10 @@ private:
class DCCAccessoryDecoder: public IODevice { class DCCAccessoryDecoder: public IODevice {
public: public:
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress); static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
// Constructor
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
private: private:
// Constructor
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
// Device-specific write function. // Device-specific write function.
void _begin() override; void _begin() override;
void _write(VPIN vpin, int value) override; void _write(VPIN vpin, int value) override;
@ -340,13 +360,13 @@ public:
addDevice(new ArduinoPins(firstVpin, nPins)); addDevice(new ArduinoPins(firstVpin, nPins));
} }
// Constructor
ArduinoPins(VPIN firstVpin, int nPins);
static void fastWriteDigital(uint8_t pin, uint8_t value); static void fastWriteDigital(uint8_t pin, uint8_t value);
static bool fastReadDigital(uint8_t pin); static bool fastReadDigital(uint8_t pin);
private: private:
// Constructor
ArduinoPins(VPIN firstVpin, int nPins);
// Device-specific pin configuration // Device-specific pin configuration
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
// Device-specific write function. // Device-specific write function.
@ -354,6 +374,7 @@ private:
// Device-specific read functions. // Device-specific read functions.
int _read(VPIN vpin) override; int _read(VPIN vpin) override;
int _readAnalogue(VPIN vpin) override; int _readAnalogue(VPIN vpin) override;
int _configureAnalogIn(VPIN vpin) override;
void _display() override; void _display() override;
@ -363,9 +384,164 @@ private:
}; };
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-Turntable.
*/
class EXTurntable : public IODevice {
public:
static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
// Constructor
EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
enum ActivityNumber : uint8_t {
Turn = 0, // Rotate turntable, maintain phase
Turn_PInvert = 1, // Rotate turntable, invert phase
Home = 2, // Initiate homing
Calibrate = 3, // Initiate calibration sequence
LED_On = 4, // Turn LED on
LED_Slow = 5, // Set LED to a slow blink
LED_Fast = 6, // Set LED to a fast blink
LED_Off = 7, // Turn LED off
Acc_On = 8, // Turn accessory pin on
Acc_Off = 9, // Turn accessory pin off
};
private:
// Device-specific write function.
void _begin() override;
void _loop(unsigned long currentMicros) override;
int _read(VPIN vpin) override;
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
void _display() override;
uint8_t _stepperStatus;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
// IODevice framework for invoking user-written functions.
// To use, define a function that you want to be regularly
// invoked, and then create an instance of UserAddin.
// For example, you can show the status, on screen 3, of the first eight
// locos in the speed table:
//
// void updateLocoScreen() {
// for (int i=0; i<8; i++) {
// if (DCC::speedTable[i].loco > 0) {
// int speed = DCC::speedTable[i].speedCode;
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
// speed & 0x7f, speed & 0x80 ? 'R' : 'F');
// }
// }
// }
//
// void halSetup() {
// ...
// UserAddin(updateLocoScreen, 1000); // Update every 1000ms
// ...
// }
//
class UserAddin : public IODevice {
private:
void (*_invokeUserFunction)();
int _delay; // milliseconds
public:
UserAddin(void (*func)(), int delay) {
_invokeUserFunction = func;
_delay = delay;
addDevice(this);
}
// userFunction has no return value, no parameter. delay is in milliseconds.
static void create(void (*userFunction)(), int delay) {
new UserAddin(userFunction, delay);
}
protected:
void _begin() { _display(); }
void _loop(unsigned long currentMicros) override {
_invokeUserFunction();
// _loop won't be called again until _delay ms have elapsed.
delayUntil(currentMicros + _delay * 1000UL);
}
void _display() override {
DIAG(F("UserAddin run every %dms"), _delay);
}
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// This HAL device driver is intended for communication in automation
// sequences. A VPIN can be SET or RESET within a sequence, and its
// current state checked elsewhere using IF, IFNOT, AT etc. or monitored
// from JMRI using a Sensor object (DCC-EX <S ...> command).
// Alternatively, the flag can be set from JMRI and other interfaces
// using the <Z ...> command, to enable or disable actions within a sequence.
//
// Example of configuration in halSetup.h:
//
// FLAGS::create(32000, 128);
//
// or in myAutomation.h:
//
// HAL(FLAGS, 32000, 128);
//
// Both create 128 flags numbered with VPINs 32000-32127.
//
//
class FLAGS : IODevice {
private:
uint8_t *_states = NULL;
public:
static void create(VPIN firstVpin, unsigned int nPins) {
if (checkNoOverlap(firstVpin, nPins))
new FLAGS(firstVpin, nPins);
}
protected:
// Constructor performs static initialisation of the device object
FLAGS (VPIN firstVpin, int nPins) {
_firstVpin = firstVpin;
_nPins = nPins;
_states = (uint8_t *)calloc(1, (_nPins+7)/8);
if (!_states) {
DIAG(F("FLAGS: ERROR Memory Allocation Failure"));
return;
}
addDevice(this);
}
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
if (pin >= _nPins || pin < 0) return 0;
uint8_t mask = 1 << (pin & 7);
return (_states[pin>>3] & mask) ? 1 : 0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
if (pin >= _nPins || pin < 0) return;
uint8_t mask = 1 << (pin & 7);
if (value)
_states[pin>>3] |= mask;
else
_states[pin>>3] &= ~mask;
}
void _display() override {
DIAG(F("FLAGS configured on VPINs %u-%u"),
_firstVpin, _firstVpin+_nPins-1);
}
};
#include "IO_MCP23008.h" #include "IO_MCP23008.h"
#include "IO_MCP23017.h" #include "IO_MCP23017.h"
#include "IO_PCF8574.h" #include "IO_PCF8574.h"
#include "IO_PCF8575.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#endif // iodevice_h #endif // iodevice_h

View File

@ -59,28 +59,33 @@
**********************************************************************************************/ **********************************************************************************************/
class ADS111x: public IODevice { class ADS111x: public IODevice {
public: public:
ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress);
}
private:
ADS111x(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = min(nPins,4); _nPins = (nPins > 4) ? 4 : nPins;
_i2cAddress = i2cAddress; _I2CAddress = i2cAddress;
_currentPin = 0; _currentPin = 0;
for (int8_t i=0; i<_nPins; i++) for (int8_t i=0; i<_nPins; i++)
_value[i] = -1; _value[i] = -1;
addDevice(this); addDevice(this);
} }
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
new ADS111x(firstVpin, nPins, i2cAddress);
}
private:
void _begin() { void _begin() {
// Initialise I2C
I2CManager.begin();
// ADS111x support high-speed I2C (4.3MHz) but that requires special
// processing. So stick to fast mode (400kHz maximum).
I2CManager.setClock(400000);
// Initialise ADS device // Initialise ADS device
if (I2CManager.exists(_i2cAddress)) { if (I2CManager.exists(_I2CAddress)) {
_nextState = STATE_STARTSCAN; _nextState = STATE_STARTSCAN;
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
} else { } else {
DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); DIAG(F("ADS111x device not found, I2C:%s"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
} }
} }
@ -98,7 +103,7 @@ private:
_outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n
_outBuffer[2] = 0xA3; // 250 samples/sec, comparator off _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off
// Write command, without waiting for completion. // Write command, without waiting for completion.
I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); I2CManager.write(_I2CAddress, _outBuffer, 3, &_i2crb);
delayUntil(currentMicros + scanInterval); delayUntil(currentMicros + scanInterval);
_nextState = STATE_STARTREAD; _nextState = STATE_STARTREAD;
@ -107,14 +112,14 @@ private:
case STATE_STARTREAD: case STATE_STARTREAD:
// Reading the pin value // Reading the pin value
_outBuffer[0] = 0x00; // Conversion register address _outBuffer[0] = 0x00; // Conversion register address
I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register I2CManager.read(_I2CAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register
_nextState = STATE_GETVALUE; _nextState = STATE_GETVALUE;
break; break;
case STATE_GETVALUE: case STATE_GETVALUE:
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
#ifdef IO_ANALOGUE_SLOW #ifdef IO_ANALOGUE_SLOW
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); DIAG(F("ADS111x VPIN:%u value:%d"), _currentPin, _value[_currentPin]);
#endif #endif
// Move to next pin // Move to next pin
@ -126,7 +131,7 @@ private:
break; break;
} }
} else { // error status } else { // error status
DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); DIAG(F("ADS111x I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
} }
} }
@ -137,7 +142,7 @@ private:
} }
void _display() override { void _display() override {
DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, DIAG(F("ADS111x I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
} }
@ -154,7 +159,6 @@ private:
STATE_GETVALUE, STATE_GETVALUE,
}; };
uint16_t _value[4]; uint16_t _value[4];
uint8_t _i2cAddress;
uint8_t _outBuffer[3]; uint8_t _outBuffer[3];
uint8_t _inBuffer[2]; uint8_t _inBuffer[2];
uint8_t _currentPin; // ADC pin currently being scanned uint8_t _currentPin; // ADC pin currently being scanned

View File

@ -26,8 +26,8 @@
#define ADDRESS(packedaddr) ((packedaddr) >> 2) #define ADDRESS(packedaddr) ((packedaddr) >> 2)
#define SUBADDRESS(packedaddr) ((packedaddr) % 4) #define SUBADDRESS(packedaddr) ((packedaddr) % 4)
void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) { void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) {
new DCCAccessoryDecoder(vpin, nPins, DCCAddress, DCCSubaddress); if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress);
} }
// Constructors // Constructors
@ -62,7 +62,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
void DCCAccessoryDecoder::_display() { void DCCAccessoryDecoder::_display() {
int endAddress = _packedAddress + _nPins - 1; int endAddress = _packedAddress + _nPins - 1;
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1, DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress)); ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
} }

View File

@ -1,5 +1,5 @@
/* /*
* © 2021, Neil McKechnie. All rights reserved. * © 2023, Neil McKechnie. All rights reserved.
* *
* This file is part of DCC++EX API * This file is part of DCC++EX API
* *
@ -33,27 +33,41 @@
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1). * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
* *
* Example: * Example:
* In mySetup function within mySetup.cpp: * In halSetup function within myHal.cpp:
* DFPlayer::create(3500, 5, Serial1); * DFPlayer::create(3500, 5, Serial1);
* or in myAutomation.h:
* HAL(DFPlayer, 3500, 5, Serial1)
* *
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card; * Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the
* Writing an analogue value 0-30 to the second pin will set the volume of the output; * SD card; e.g. a value of 1 will play the first file, 2 for the second file etc.
* Writing a digital value to the first pin will play or stop the file; * Writing an analogue value 0 to the first pin (3500) will stop the file playing;
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
* Writing a digital value of 0 to any pin will stop the player;
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise. * Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
* *
* From EX-RAIL, the following commands may be used: * From EX-RAIL, the following commands may be used:
* SET(3500) -- starts playing the first file on the SD card * SET(3500) -- starts playing the first file (file 1) on the SD card
* SET(3501) -- starts playing the second file on the SD card * SET(3501) -- starts playing the second file (file 2) on the SD card
* etc. * etc.
* RESET(3500) -- stops all playing on the player * RESET(3500) -- stops all playing on the player
* WAITFOR(3500) -- wait for the file currently being played by the player to complete * WAITFOR(3500) -- wait for the file currently being played by the player to complete
* SERVO(3500,23,0) -- plays file 23 at current volume * SERVO(3500,2,Instant) -- plays file 2 at current volume
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum) * SERVO(3501,20,Instant) -- Sets the volume to 20
* SERVO(3501,20,0) -- Sets the volume to 20
* *
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly * NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse. * to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
* A 1k resistor in series with the module's RX terminal will alleviate this. * A 1k resistor in series with the module's RX terminal will alleviate this.
*
* Files on the SD card are numbered according to their order in the directory on the
* card (as listed by the DIR command in Windows). This may not match the order of the files
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
* files be copied into an empty SDcard in the desired order, one at a time.
*
* The driver now polls the device for its current status every second. Should the device
* fail to respond it will be marked off-line and its busy indicator cleared, to avoid
* lock-ups in automation scripts that are executing for a WAITFOR().
*/ */
#ifndef IO_DFPlayer_h #ifndef IO_DFPlayer_h
@ -63,12 +77,25 @@
class DFPlayer : public IODevice { class DFPlayer : public IODevice {
private: private:
const uint8_t MAXVOLUME=30;
HardwareSerial *_serial; HardwareSerial *_serial;
bool _playing = false; bool _playing = false;
uint8_t _inputIndex = 0; uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing unsigned long _commandSendTime; // Time (us) that last transmit took place.
unsigned long _timeoutTime;
uint8_t _recvCMD; // Last received command code byte
bool _awaitingResponse = false;
uint8_t _requestedVolumeLevel = MAXVOLUME;
uint8_t _currentVolume = MAXVOLUME;
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
public: public:
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial);
}
protected:
// Constructor // Constructor
DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) : DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) :
IODevice(firstVpin, nPins), IODevice(firstVpin, nPins),
@ -77,41 +104,72 @@ public:
addDevice(this); addDevice(this);
} }
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
new DFPlayer(firstVpin, nPins, serial);
}
protected:
void _begin() override { void _begin() override {
_serial->begin(9600); _serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
// Flush any data in input queue
while (_serial->available()) _serial->read();
_deviceState = DEVSTATE_INITIALISING; _deviceState = DEVSTATE_INITIALISING;
// Send a query to the device to see if it responds // Send a query to the device to see if it responds
sendPacket(0x42); sendPacket(0x42);
_commandSendTime = micros(); _timeoutTime = micros() + 5000000UL; // 5 second timeout
_awaitingResponse = true;
} }
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
// Check for incoming data on _serial, and update busy flag accordingly.
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF" // Read responses from device
processIncoming();
// Check if a command sent to device has timed out. Allow 0.5 second for response
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
_awaitingResponse = false;
_playing = false;
}
// Send any commands that need to go.
processOutgoing(currentMicros);
delayUntil(currentMicros + 10000); // Only enter every 10ms
}
// Check for incoming data on _serial, and update busy flag and other state accordingly
void processIncoming() {
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
bool ok = false;
while (_serial->available()) { while (_serial->available()) {
int c = _serial->read(); int c = _serial->read();
if (c == 0x7E) switch (_inputIndex) {
_inputIndex = 1; case 0:
else if ((c==0xFF && _inputIndex==1) if (c == 0x7E) ok = true;
|| (c==0x3D && _inputIndex==3) break;
|| (_inputIndex >=4 && _inputIndex <= 8)) case 1:
_inputIndex++; if (c == 0xFF) ok = true;
else if (c==0x06 && _inputIndex==2) { break;
// Valid message prefix, so consider the device online case 2:
if (c== 0x06) ok = true;
break;
case 3:
_recvCMD = c; // CMD byte
ok = true;
break;
case 6:
switch (_recvCMD) {
case 0x42:
// Response to status query
_playing = (c != 0);
// Mark the device online and cancel timeout
if (_deviceState==DEVSTATE_INITIALISING) { if (_deviceState==DEVSTATE_INITIALISING) {
_deviceState = DEVSTATE_NORMAL; _deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
} }
_inputIndex++; _awaitingResponse = false;
} else if (c==0xEF && _inputIndex==9) { break;
case 0x3d:
// End of play // End of play
if (_playing) { if (_playing) {
#ifdef DIAG_IO #ifdef DIAG_IO
@ -119,35 +177,86 @@ protected:
#endif #endif
_playing = false; _playing = false;
} }
_inputIndex = 0; break;
} else case 0x40:
_inputIndex = 0; // Unrecognised character sequence, start again! // Error code
DIAG(F("DFPlayer: Error %d returned from device"), c);
_playing = false;
break;
}
ok = true;
break;
case 4: case 5: case 7: case 8:
ok = true; // Skip over these bytes in message.
break;
case 9:
if (c==0xef) {
// Message finished
}
break;
default:
break;
}
if (ok)
_inputIndex++; // character as expected, so increment index
else
_inputIndex = 0; // otherwise reset.
}
}
// Send any commands that need to be sent
void processOutgoing(unsigned long currentMicros) {
// When two commands are sent in quick succession, the device will often fail to
// execute one. Testing has indicated that a delay of 100ms or more is required
// between successive commands to get reliable operation.
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
if (_currentVolume > _requestedVolumeLevel) {
// Change volume before changing song if volume is reducing.
_currentVolume = _requestedVolumeLevel;
sendPacket(0x06, _currentVolume);
} else if (_requestedSong > 0) {
// Change song
sendPacket(0x03, _requestedSong);
_requestedSong = -1;
} else if (_requestedSong == 0) {
sendPacket(0x16); // Stop playing
_requestedSong = -1;
} else if (_currentVolume < _requestedVolumeLevel) {
// Change volume after changing song if volume is increasing.
_currentVolume = _requestedVolumeLevel;
sendPacket(0x06, _currentVolume);
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
// Poll device every second that other commands aren't being sent,
// to check if it's still connected and responding.
sendPacket(0x42);
if (!_awaitingResponse) {
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
_awaitingResponse = true;
}
} }
// Check if the initial prompt to device has timed out. Allow 1 second
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
} }
delayUntil(currentMicros + 10000); // Only enter every 10ms
} }
// Write with value 1 starts playing a song. The relative pin number is the file number. // Write with value 1 starts playing a song. The relative pin number is the file number.
// Write with value 0 stops playing. // Write with value 0 stops playing.
void _write(VPIN vpin, int value) override { void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
if (value) { if (value) {
// Value 1, start playing // Value 1, start playing
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("DFPlayer: Play %d"), pin+1); DIAG(F("DFPlayer: Play %d"), pin+1);
#endif #endif
sendPacket(0x03, pin+1); _requestedSong = pin+1;
_playing = true; _playing = true;
} else { } else {
// Value 0, stop playing // Value 0, stop playing
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("DFPlayer: Stop")); DIAG(F("DFPlayer: Stop"));
#endif #endif
sendPacket(0x16); _requestedSong = 0; // No song
_playing = false; _playing = false;
} }
} }
@ -156,51 +265,43 @@ protected:
// Volume may be specified as second parameter to writeAnalogue. // Volume may be specified as second parameter to writeAnalogue.
// If value is zero, the player stops playing. // If value is zero, the player stops playing.
// WriteAnalogue on second pin sets the output volume. // WriteAnalogue on second pin sets the output volume.
//
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
if (_deviceState == DEVSTATE_FAILED) return;
uint8_t pin = vpin - _firstVpin; uint8_t pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("DFPlayer: VPIN:%u FileNo:%d Volume:%d"), vpin, value, volume);
#endif
// Validate parameter. // Validate parameter.
volume = min(30,volume); if (volume > MAXVOLUME) volume = MAXVOLUME;
if (pin == 0) { if (pin == 0) {
// Play track // Play track
if (value > 0) { if (value > 0) {
#ifdef DIAG_IO if (volume > 0)
DIAG(F("DFPlayer: Play %d"), value); _requestedVolumeLevel = volume;
#endif _requestedSong = value;
sendPacket(0x03, value); // Play track
_playing = true; _playing = true;
if (volume > 0) {
#ifdef DIAG_IO
DIAG(F("DFPlayer: Volume %d"), volume);
#endif
sendPacket(0x06, volume); // Set volume
}
} else { } else {
#ifdef DIAG_IO _requestedSong = 0; // stop playing
DIAG(F("DFPlayer: Stop"));
#endif
sendPacket(0x16); // Stop play
_playing = false; _playing = false;
} }
} else if (pin == 1) { } else if (pin == 1) {
// Set volume (0-30) // Set volume (0-30)
if (value > 30) value = 30; _requestedVolumeLevel = value;
else if (value < 0) value = 0;
#ifdef DIAG_IO
DIAG(F("DFPlayer: Volume %d"), value);
#endif
sendPacket(0x06, value);
} }
} }
// A read on any pin indicates whether the player is still playing. // A read on any pin indicates whether the player is still playing.
int _read(VPIN) override { int _read(VPIN) override {
if (_deviceState == DEVSTATE_FAILED) return false;
return _playing; return _playing;
} }
void _display() override { void _display() override {
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1, DIAG(F("DFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
} }
@ -230,7 +331,10 @@ private:
setChecksum(out); setChecksum(out);
// Output the command
_serial->write(out, sizeof(out)); _serial->write(out, sizeof(out));
_commandSendTime = micros();
} }
uint16_t calcChecksum(uint8_t* packet) uint16_t calcChecksum(uint8_t* packet)

125
IO_EXFastclock.h Normal file
View File

@ -0,0 +1,125 @@
/*
* © 2022, Colin Murdoch. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
*
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
*
*
*/
#ifndef IO_EXFastclock_h
#define IO_EXFastclock_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "EXRAIL2.h"
#include "CommandDistributor.h"
bool FAST_CLOCK_EXISTS = true;
class EXFastClock : public IODevice {
public:
// Constructor
EXFastClock(I2CAddress i2cAddress){
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(I2CAddress i2cAddress) {
DIAG(F("Checking for Clock"));
// Start by assuming we will find the clock
// Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code.
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
DIAG(F("Clock check result - %d"), _checkforclock);
// XXXX change thistosave2 bytes
if (_checkforclock == 0) {
FAST_CLOCK_EXISTS = true;
//DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
new EXFastClock(i2cAddress);
}
else {
FAST_CLOCK_EXISTS = false;
//DIAG(F("No Fast Clock found"));
LCD(6,F("CLOCK NOT FOUND"));
}
}
private:
// Initialisation of Fastclock
void _begin() override {
if (FAST_CLOCK_EXISTS == true) {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
//LCD(6,F("CLOCK NOT FOUND"));
DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString());
}
}
}
// Processing loop to obtain clock time
void _loop(unsigned long currentMicros) override{
if (FAST_CLOCK_EXISTS==true) {
uint8_t readBuffer[3];
byte a,b;
#ifdef EXRAIL_ACTIVE
I2CManager.read(_I2CAddress, readBuffer, 3);
// XXXX change this to save a few bytes
a = readBuffer[0];
b = readBuffer[1];
//_clocktime = (a << 8) + b;
//_clockrate = readBuffer[2];
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
// Clock interval is 60/ clockspeed i.e 60/b seconds
delayUntil(currentMicros + ((60/b) * 1000000));
#endif
}
}
// Display EX-FastClock device driver info.
void _display() override {
DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

400
IO_EXIOExpander.h Normal file
View File

@ -0,0 +1,400 @@
/*
* © 2022, Peter Cole. All rights reserved.
*
* 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/>.
*/
/*
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
* This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties.
*
* To create EX-IOExpander devices, these are defined in myHal.cpp:
* (Note the device driver is included by default)
*
* void halSetup() {
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
* EXIOExpander::create(800, 18, 0x65);
* }
*
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
* device in use. There is no way for the device driver to sanity check pins are used for the
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
*
* The total number of pins cannot exceed 256 because of the communications packet format.
* The number of analogue inputs cannot exceed 16 because of a limit on the maximum
* I2C packet size of 32 bytes (in the Wire library).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-IOExpander.
*/
class EXIOExpander : public IODevice {
public:
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
}
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
_firstVpin = firstVpin;
// Number of pins cannot exceed 256 (1 byte) because of I2C message structure.
if (nPins > 256) nPins = 256;
_nPins = nPins;
_I2CAddress = i2cAddress;
addDevice(this);
}
void _begin() {
uint8_t status;
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
// NB The I2C calls here are done as blocking calls, as they're not time-critical
// during initialisation and the reads require waiting for a response anyway.
// Hence we can allocate I/O buffers from the stack.
uint8_t receiveBuffer[3];
uint8_t commandBuffer[4] = {EXIOINIT, (uint8_t)_nPins, (uint8_t)(_firstVpin & 0xFF), (uint8_t)(_firstVpin >> 8)};
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
if (status == I2C_STATUS_OK) {
if (receiveBuffer[0] == EXIOPINS) {
_numDigitalPins = receiveBuffer[1];
_numAnaloguePins = receiveBuffer[2];
// See if we already have suitable buffers assigned
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
if (_digitalPinBytes < digitalBytesNeeded) {
// Not enough space, free any existing buffer and allocate a new one
if (_digitalPinBytes > 0) free(_digitalInputStates);
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
_digitalPinBytes = digitalBytesNeeded;
}
size_t analogueBytesNeeded = _numAnaloguePins * 2;
if (_analoguePinBytes < analogueBytesNeeded) {
// Free any existing buffers and allocate new ones.
if (_analoguePinBytes > 0) {
free(_analogueInputBuffer);
free(_analogueInputStates);
free(_analoguePinMap);
}
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
_analoguePinBytes = analogueBytesNeeded;
}
} else {
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
}
// We now need to retrieve the analogue pin map
if (status == I2C_STATUS_OK) {
commandBuffer[0] = EXIOINITA;
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
}
if (status == I2C_STATUS_OK) {
// Attempt to get version, if we don't get it, we don't care, don't go offline
uint8_t versionBuffer[3];
commandBuffer[0] = EXIOVER;
if (I2CManager.read(_I2CAddress, versionBuffer, sizeof(versionBuffer), commandBuffer, 1) == I2C_STATUS_OK) {
_majorVer = versionBuffer[0];
_minorVer = versionBuffer[1];
_patchVer = versionBuffer[2];
}
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer);
#ifdef DIAG_IO
_display();
#endif
}
if (status != I2C_STATUS_OK)
reportError(status);
} else {
DIAG(F("EX-IOExpander I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested.
// Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can
// be allocated from the stack to reduce RAM allocation.
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (paramCount != 1) return false;
int pin = vpin - _firstVpin;
if (configType == CONFIGURE_INPUT) {
uint8_t pullup = params[0];
uint8_t outBuffer[] = {EXIODPUP, (uint8_t)pin, pullup};
uint8_t responseBuffer[1];
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
outBuffer, sizeof(outBuffer));
if (status == I2C_STATUS_OK) {
if (responseBuffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("EXIOVpin %u cannot be used as a digital input pin"), (int)vpin);
}
} else
reportError(status);
} else if (configType == CONFIGURE_ANALOGINPUT) {
// TODO: Consider moving code from _configureAnalogIn() to here and remove _configureAnalogIn
// from IODevice class definition. Not urgent, but each virtual function defined
// means increasing the RAM requirement of every HAL device driver, whether it's relevant
// to the driver or not.
return false;
}
return false;
}
// Analogue input pin configuration, used to enable an EX-IOExpander device.
// Use I2C blocking calls and allocate buffers from stack to save RAM.
int _configureAnalogIn(VPIN vpin) override {
int pin = vpin - _firstVpin;
uint8_t commandBuffer[] = {EXIOENAN, (uint8_t)pin};
uint8_t responseBuffer[1];
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
commandBuffer, sizeof(commandBuffer));
if (status == I2C_STATUS_OK) {
if (responseBuffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("EX-IOExpander: Vpin %u cannot be used as an analogue input pin"), (int)vpin);
}
} else
reportError(status);
return false;
}
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
// Request block is used for analogue and digital reads from the IOExpander, which are performed
// on a cyclic basis. Writes are performed synchronously as and when requested.
if (_readState != RDS_IDLE) {
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
uint8_t status = _i2crb.status;
if (status == I2C_STATUS_OK) { // If device request ok, read input data
// First check if we need to process received data
if (_readState == RDS_ANALOGUE) {
// Read of analogue values was in progress, so process received values
// Here we need to copy the values from input buffer to the analogue value array. We need to
// do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed
// while the value is being read).
memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states
} else if (_readState == RDS_DIGITAL) {
// Read of digital states was in progress, so process received values
// The received digital states are placed directly into the digital buffer on receipt,
// so don't need any further processing at this point (unless we want to check for
// changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h).
}
} else
reportError(status, false); // report eror but don't go offline.
_readState = RDS_IDLE;
}
// If we're not doing anything now, check to see if a new input transfer is due.
if (_readState == RDS_IDLE) {
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
// be allocated from heap (object state).
_readCommandBuffer[0] = EXIORDD;
I2CManager.read(_I2CAddress, _digitalInputStates, (_numDigitalPins+7)/8, _readCommandBuffer, 1, &_i2crb);
// non-blocking read
_lastDigitalRead = currentMicros;
_readState = RDS_DIGITAL;
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
// Issue new read for analogue input states
_readCommandBuffer[0] = EXIORDAN;
I2CManager.read(_I2CAddress, _analogueInputBuffer,
_numAnaloguePins * 2, _readCommandBuffer, 1, &_i2crb);
_lastAnalogueRead = currentMicros;
_readState = RDS_ANALOGUE;
}
}
}
// Obtain the correct analogue input value, with reference to the analogue
// pin map.
// Obtain the correct analogue input value
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
if (_analoguePinMap[aPin] == pin) {
uint8_t _pinLSBByte = aPin * 2;
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
}
}
return -1; // pin not found in table
}
// Obtain the correct digital input value
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
uint8_t pinByte = pin / 8;
bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8);
return value;
}
// Write digital value. We could have an output buffer of states, that is periodically
// written to the device if there are any changes; this would reduce the I2C overhead
// if lots of output requests are being made. We could also cache the last value
// sent so that we don't write the same value over and over to the output.
// However, for the time being, we just write the current value (blocking I2C) to the
// IOExpander node. As it is a blocking request, we can use buffers allocated from
// the stack to save RAM allocation.
void _write(VPIN vpin, int value) override {
uint8_t digitalOutBuffer[3];
uint8_t responseBuffer[1];
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
digitalOutBuffer[0] = EXIOWRD;
digitalOutBuffer[1] = pin;
digitalOutBuffer[2] = value;
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, digitalOutBuffer, 3);
if (status != I2C_STATUS_OK) {
reportError(status);
} else {
if (responseBuffer[0] != EXIORDY) {
DIAG(F("Vpin %u cannot be used as a digital output pin"), (int)vpin);
}
}
}
// Write analogue (integer) value. Write the parameters (blocking I2C) to the
// IOExpander node. As it is a blocking request, we can use buffers allocated from
// the stack to reduce RAM allocation.
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
uint8_t servoBuffer[7];
uint8_t responseBuffer[1];
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
servoBuffer[0] = EXIOWRAN;
servoBuffer[1] = pin;
servoBuffer[2] = value & 0xFF;
servoBuffer[3] = value >> 8;
servoBuffer[4] = profile;
servoBuffer[5] = duration & 0xFF;
servoBuffer[6] = duration >> 8;
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, servoBuffer, 7);
if (status != I2C_STATUS_OK) {
DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED;
} else {
if (responseBuffer[0] != EXIORDY) {
DIAG(F("Vpin %u cannot be used as a servo/PWM pin"), (int)vpin);
}
}
}
// Display device information and status.
void _display() override {
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %u-%u %S"),
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
(int)_firstVpin, (int)_firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
// Helper function for error handling
void reportError(uint8_t status, bool fail=true) {
DIAG(F("EX-IOExpander I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
status, I2CManager.getErrorMessage(status));
if (fail)
_deviceState = DEVSTATE_FAILED;
}
uint8_t _numDigitalPins = 0;
uint8_t _numAnaloguePins = 0;
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
uint8_t* _digitalInputStates;
uint8_t* _analogueInputStates;
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers
uint8_t _readCommandBuffer[1];
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed)
uint8_t* _analoguePinMap;
I2CRB _i2crb;
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states
uint8_t _readState = RDS_IDLE;
unsigned long _lastDigitalRead = 0;
unsigned long _lastAnalogueRead = 0;
const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms
const unsigned long _analogueRefresh = 50000UL; // Delay refreshing analogue inputs for 50ms
// EX-IOExpander protocol flags
enum {
EXIOINIT = 0xE0, // Flag to initialise setup procedure
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
EXIOVER = 0xE3, // Flag to get version
EXIORDAN = 0xE4, // Flag to read an analogue input
EXIOWRD = 0xE5, // Flag for digital write
EXIORDD = 0xE6, // Flag to read digital input
EXIOENAN = 0xE7, // Flag to enable an analogue pin
EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings
EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers
EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM)
EXIOERR = 0xEF, // Flag we've received an error
};
};
#endif

121
IO_EXTurntable.h Normal file
View File

@ -0,0 +1,121 @@
/*
* © 2021, Peter Cole. 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/>.
*/
/*
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
*
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
*
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
* <D TT vpin steps activity> in the serial console
* MOVETT(vpin, steps, activity) in EX-RAIL
* Refer to the documentation for further information including the valid activities.
*/
#ifndef IO_EXTurntable_h
#define IO_EXTurntable_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
new EXTurntable(firstVpin, nPins, I2CAddress);
}
// Constructor
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
addDevice(this);
}
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Processing loop to obtain status of stepper
// 0 = finished moving and in correct position
// 1 = still moving
void EXTurntable::_loop(unsigned long currentMicros) {
uint8_t readBuffer[1];
I2CManager.read(_I2CAddress, readBuffer, 1);
_stepperStatus = readBuffer[0];
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
}
// Read returns status as obtained in our loop.
// Return false if our status value is invalid.
int EXTurntable::_read(VPIN vpin) {
if (_deviceState == DEVSTATE_FAILED) return 0;
// DIAG(F("_read status: %d"), _stepperStatus);
if (_stepperStatus > 1) {
return false;
} else {
return _stepperStatus;
}
}
// writeAnalogue to send the steps and activity to Turntable-EX.
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
// value contains the steps, bit shifted to MSB + LSB.
// activity contains the activity flag as per this list:
//
// Turn = 0, // Rotate turntable, maintain phase
// Turn_PInvert = 1, // Rotate turntable, invert phase
// Home = 2, // Initiate homing
// Calibrate = 3, // Initiate calibration sequence
// LED_On = 4, // Turn LED on
// LED_Slow = 5, // Set LED to a slow blink
// LED_Fast = 6, // Set LED to a fast blink
// LED_Off = 7, // Turn LED off
// Acc_On = 8, // Turn accessory pin on
// Acc_Off = 9 // Turn accessory pin off
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
if (_deviceState == DEVSTATE_FAILED) return;
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
#endif
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
}
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
#endif

View File

@ -1,129 +0,0 @@
/*
* © 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/>.
*/
#include <Arduino.h>
#include "IO_ExampleSerial.h"
#include "FSH.h"
// Constructor
IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
_firstVpin = firstVpin;
_nPins = nPins;
_pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
_baud = baud;
// Save reference to serial port driver
_serial = serial;
addDevice(this);
}
// Static create method for one module.
void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
new IO_ExampleSerial(firstVpin, nPins, serial, baud);
}
// Device-specific initialisation
void IO_ExampleSerial::_begin() {
_serial->begin(_baud);
#if defined(DIAG_IO)
_display();
#endif
// Send a few # characters to the output
for (uint8_t i=0; i<3; i++)
_serial->write('#');
}
// Device-specific write function. Write a string in the form "#Wm,n#"
// where m is the vpin number, and n is the value.
void IO_ExampleSerial::_write(VPIN vpin, int value) {
int pin = vpin -_firstVpin;
#ifdef DIAG_IO
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
#endif
// Send a command string over the serial line
_serial->print('#');
_serial->print('W');
_serial->print(pin);
_serial->print(',');
_serial->print(value);
_serial->println('#');
DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value);
}
// Device-specific read function.
int IO_ExampleSerial::_read(VPIN vpin) {
// Return a value for the specified vpin.
int result = _pinValues[vpin-_firstVpin];
return result;
}
// Loop function to do background scanning of the input port. State
// machine parses the incoming command as it is received. Command
// is in the form "#Nm,n#" where m is the index and n is the value.
void IO_ExampleSerial::_loop(unsigned long currentMicros) {
(void)currentMicros; // Suppress compiler warnings
if (_serial->available()) {
// Input data available to read. Read a character.
char c = _serial->read();
switch (_inputState) {
case 0: // Waiting for start of command
if (c == '#') // Start of command received.
_inputState = 1;
break;
case 1: // Expecting command character
if (c == 'N') { // 'Notify' character received
_inputState = 2;
_inputValue = _inputIndex = 0;
} else
_inputState = 0; // Unexpected char, reset
break;
case 2: // reading first parameter (index)
if (isdigit(c))
_inputIndex = _inputIndex * 10 + (c-'0');
else if (c==',')
_inputState = 3;
else
_inputState = 0; // Unexpected char, reset
break;
case 3: // reading reading second parameter (value)
if (isdigit(c))
_inputValue = _inputValue * 10 - (c-'0');
else if (c=='#') { // End of command
// Complete command received, do something with it.
DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
if (_inputIndex < _nPins) { // Store value
_pinValues[_inputIndex] = _inputValue;
}
_inputState = 0; // Done, start again.
} else
_inputState = 0; // Unexpected char, reset
break;
}
}
}
void IO_ExampleSerial::_display() {
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
(int)_firstVpin+_nPins-1);
}

View File

@ -35,24 +35,131 @@
#include "IODevice.h" #include "IODevice.h"
class IO_ExampleSerial : public IODevice { class IO_ExampleSerial : public IODevice {
public:
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
protected:
void _begin() override;
void _loop(unsigned long currentMicros) override;
void _write(VPIN vpin, int value) override;
int _read(VPIN vpin) override;
void _display() override;
private: private:
// Here we define the device-specific variables.
HardwareSerial *_serial; HardwareSerial *_serial;
uint8_t _inputState = 0; uint8_t _inputState = 0;
int _inputIndex = 0; int _inputIndex = 0;
int _inputValue = 0; int _inputValue = 0;
uint16_t *_pinValues; // Pointer to block of memory containing pin values uint16_t *_pinValues; // Pointer to block of memory containing pin values
unsigned long _baud; unsigned long _baud;
public:
// Static function to handle "IO_ExampleSerial::create(...)" calls.
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
}
protected:
// Constructor. This should initialise variables etc. but not call other objects yet
// (e.g. Serial, I2CManager, and other parts of the CS functionality).
// defer those until the _begin() function. The 'addDevice' call is required unless
// the device is not to be added (e.g. because of incorrect parameters).
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
_firstVpin = firstVpin;
_nPins = nPins;
_pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
_baud = baud;
// Save reference to serial port driver
_serial = serial;
addDevice(this);
}
// Device-specific initialisation
void _begin() override {
_serial->begin(_baud);
#if defined(DIAG_IO)
_display();
#endif
// Send a few # characters to the output
for (uint8_t i=0; i<3; i++)
_serial->write('#');
}
// Device-specific write function. Write a string in the form "#Wm,n#"
// where m is the vpin number, and n is the value.
void _write(VPIN vpin, int value) {
int pin = vpin -_firstVpin;
#ifdef DIAG_IO
DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value);
#endif
// Send a command string over the serial line
_serial->print('#');
_serial->print('W');
_serial->print(pin);
_serial->print(',');
_serial->print(value);
_serial->println('#');
DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value);
}
// Device-specific read function.
int _read(VPIN vpin) {
// Return a value for the specified vpin.
int result = _pinValues[vpin-_firstVpin];
return result;
}
// Loop function to do background scanning of the input port. State
// machine parses the incoming command as it is received. Command
// is in the form "#Nm,n#" where m is the index and n is the value.
void _loop(unsigned long currentMicros) {
(void)currentMicros; // Suppress compiler warnings
if (_serial->available()) {
// Input data available to read. Read a character.
char c = _serial->read();
switch (_inputState) {
case 0: // Waiting for start of command
if (c == '#') // Start of command received.
_inputState = 1;
break;
case 1: // Expecting command character
if (c == 'N') { // 'Notify' character received
_inputState = 2;
_inputValue = _inputIndex = 0;
} else
_inputState = 0; // Unexpected char, reset
break;
case 2: // reading first parameter (index)
if (isdigit(c))
_inputIndex = _inputIndex * 10 + (c-'0');
else if (c==',')
_inputState = 3;
else
_inputState = 0; // Unexpected char, reset
break;
case 3: // reading reading second parameter (value)
if (isdigit(c))
_inputValue = _inputValue * 10 - (c-'0');
else if (c=='#') { // End of command
// Complete command received, do something with it.
DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
if (_inputIndex >= 0 && _inputIndex < _nPins) { // Store value
_pinValues[_inputIndex] = _inputValue;
}
_inputState = 0; // Done, start again.
} else
_inputState = 0; // Unexpected char, reset
break;
}
}
}
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
// Here we display the current values held for the pins.
void _display() {
DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin,
(int)_firstVpin+_nPins-1);
for (int i=0; i<_nPins; i++)
DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]);
}
}; };
#endif // IO_EXAMPLESERIAL_H #endif // IO_EXAMPLESERIAL_H

View File

@ -34,7 +34,7 @@ class GPIOBase : public IODevice {
protected: protected:
// Constructor // Constructor
GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin); GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin);
// Device-specific initialisation // Device-specific initialisation
void _begin() override; void _begin() override;
// Device-specific pin configuration function. // Device-specific pin configuration function.
@ -47,15 +47,15 @@ protected:
void _loop(unsigned long currentMicros) override; void _loop(unsigned long currentMicros) override;
// Data fields // Data fields
uint8_t _I2CAddress;
// Allocate enough space for all input pins // Allocate enough space for all input pins
T _portInputState; T _portInputState; // 1=high (inactive), 0=low (activated)
T _portOutputState; T _portOutputState; // 1 =high, 0=low
T _portMode; T _portMode; // 0=input, 1=output
T _portPullup; T _portPullup; // 0=nopullup, 1=pullup
T _portInUse; T _portInUse; // 0=not in use, 1=in use
// Interval between refreshes of each input port // Target interval between refreshes of each input port
static const int _portTickTime = 4000; static const int _portTickTime = 4000; // 4ms
// Virtual functions for interfacing with I2C GPIO Device // Virtual functions for interfacing with I2C GPIO Device
virtual void _writeGpioPort() = 0; virtual void _writeGpioPort() = 0;
@ -76,15 +76,21 @@ protected:
// Constructor // Constructor
template <class T> template <class T>
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) : GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) :
IODevice(firstVpin, nPins) IODevice(firstVpin, nPins)
{ {
if (_nPins > (int)sizeof(T)*8) _nPins = sizeof(T)*8; // Ensure nPins is consistent with the number of bits in T
_deviceName = deviceName; _deviceName = deviceName;
_I2CAddress = I2CAddress; _I2CAddress = i2cAddress;
_gpioInterruptPin = interruptPin; _gpioInterruptPin = interruptPin;
_hasCallback = true; _hasCallback = true;
// Add device to list of devices. // Add device to list of devices.
addDevice(this); addDevice(this);
_portMode = 0; // default to input mode
_portPullup = -1; // default to pullup enabled
_portInputState = -1; // default to all inputs high (inactive)
_portInUse = 0; // No ports in use initially.
} }
template <class T> template <class T>
@ -99,21 +105,16 @@ void GPIOBase<T>::_begin() {
#if defined(DIAG_IO) #if defined(DIAG_IO)
_display(); _display();
#endif #endif
_portMode = 0; // default to input mode
_portPullup = -1; // default to pullup enabled
_portInputState = -1;
_portInUse = 0;
_setupDevice(); _setupDevice();
_deviceState = DEVSTATE_NORMAL; _deviceState = DEVSTATE_NORMAL;
} else { } else {
DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress); DIAG(F("%S I2C:%s Device not detected"), _deviceName, _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
} }
} }
// Configuration parameters for inputs: // Configuration parameters for inputs:
// params[0]: enable pullup // params[0]: enable pullup
// params[1]: invert input (optional)
template <class T> template <class T>
bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_INPUT) return false; if (configType != CONFIGURE_INPUT) return false;
@ -121,7 +122,7 @@ bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun
bool pullup = params[0]; bool pullup = params[0];
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, pullup); DIAG(F("%S I2C:%s Config Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, pullup);
#endif #endif
uint16_t mask = 1 << pin; uint16_t mask = 1 << pin;
if (pullup) if (pullup)
@ -151,7 +152,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
_deviceState = DEVSTATE_NORMAL; _deviceState = DEVSTATE_NORMAL;
} else { } else {
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, _I2CAddress, status, DIAG(F("%S I2C:%s Error:%d %S"), _deviceName, _I2CAddress.toString(), status,
I2CManager.getErrorMessage(status)); I2CManager.getErrorMessage(status));
} }
_processCompletion(status); _processCompletion(status);
@ -174,7 +175,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
#ifdef DIAG_IO #ifdef DIAG_IO
if (differences) if (differences)
DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
#endif #endif
} }
@ -195,7 +196,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
template <class T> template <class T>
void GPIOBase<T>::_display() { void GPIOBase<T>::_display() {
DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress, DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(),
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
} }
@ -204,7 +205,7 @@ void GPIOBase<T>::_write(VPIN vpin, int value) {
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
T mask = 1 << pin; T mask = 1 << pin;
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value); DIAG(F("%S I2C:%s Write Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, value);
#endif #endif
// Set port mode output if currently not output mode // Set port mode output if currently not output mode
@ -240,7 +241,7 @@ int GPIOBase<T>::_read(VPIN vpin) {
// Set unused pin and write mode pin value to 1 // Set unused pin and write mode pin value to 1
_portInputState |= ~_portInUse | _portMode; _portInputState |= ~_portInUse | _portMode;
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
#endif #endif
} }
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)

262
IO_HALDisplay.h Normal file
View File

@ -0,0 +1,262 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This driver provides a more immediate interface into the OLED display
* than the one installed through the config.h file. When an LCD(...) call
* is made, the text is output immediately to the specified display line,
* without waiting for the next 2.5 second refresh. However, if the line
* specified is off the screen then the text in the bottom line will be
* overwritten. There is however a special case that if line 255 is specified,
* the existing text will scroll up and the new line added to the bottom
* line of the screen.
*
* To install, use the following command in myHal.cpp:
*
* HALDisplay<OLED>::create(address, width, height);
*
* where address is the I2C address of the OLED display (0x3c or 0x3d),
* width is the width in pixels, and height is the height in pixels.
*
* Valid width and height are 128x32 (SSD1306 controller),
* 128x64 (SSD1306) and 132x64 (SH1106). The driver uses
* a 5x7 character set in a 6x8 pixel cell.
*
* OR
*
* HALDisplay<LiquidCrystal>::create(address, width, height);
*
* where address is the I2C address of the LCD display (0x27 typically),
* width is the width in characters (16 or 20 typically),
* and height is the height in characters (2 or 4 typically).
*/
#ifndef IO_HALDisplay_H
#define IO_HALDisplay_H
#include "IODevice.h"
#include "DisplayInterface.h"
#include "SSD1306Ascii.h"
#include "LiquidCrystal_I2C.h"
#include "version.h"
typedef SSD1306AsciiWire OLED;
typedef LiquidCrystal_I2C LiquidCrystal;
template <class T>
class HALDisplay : public IODevice, public DisplayInterface {
private:
// Here we define the device-specific variables.
uint8_t _height; // in pixels
uint8_t _width; // in pixels
T *_displayDriver;
uint8_t _rowNo = 0; // Row number being written by caller
uint8_t _colNo = 0; // Position in line being written by caller
uint8_t _numRows;
uint8_t _numCols;
char *_buffer = NULL;
uint8_t *_rowGeneration = NULL;
uint8_t *_lastRowGeneration = NULL;
uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0;
bool _startAgain = false;
DisplayInterface *_nextDisplay = NULL;
public:
// Static function to handle "HALDisplay::create(...)" calls.
static void create(I2CAddress i2cAddress, int width, int height) {
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height);
}
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(displayNo, i2cAddress, width, height);
}
protected:
// Constructor
HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayDriver = new T(i2cAddress, width, height);
if (!_displayDriver) return; // Check for memory allocation failure
_I2CAddress = i2cAddress;
_width = width;
_height = height;
_numCols = _displayDriver->getNumCols();
_numRows = _displayDriver->getNumRows();
_charPosToScreen = _numCols;
// Allocate arrays
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
if (!_buffer) return; // Check for memory allocation failure
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
if (!_rowGeneration) return; // Check for memory allocation failure
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
if (!_lastRowGeneration) return; // Check for memory allocation failure
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Add device to list of HAL devices (not necessary but allows
// status to be displayed using <D HAL SHOW> and device to be
// reinitialised using <D HAL RESET>).
IODevice::addDevice(this);
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);
// Is this the system display (0)?
if (displayNo == 0) {
// Set first two lines on screen
this->setRow(displayNo, 0);
print(F("DCC-EX v"));
print(F(VERSION));
setRow(displayNo, 1);
print(F("Lic GPLv3"));
}
}
void screenUpdate() {
// Loop through the buffer and if a row has changed
// (rowGeneration[row] is changed) then start writing the
// characters from the buffer, one character per entry,
// to the screen until that row has been refreshed.
// First check if the OLED driver is still busy from a previous
// call. If so, don't do anything until the next entry.
if (!_displayDriver->isBusy()) {
// Check if we've just done the end of a row
if (_charPosToScreen >= _numCols) {
// Move to next line
if (++_rowNoToScreen >= _numRows || _startAgain) {
_rowNoToScreen = 0; // Wrap to first row
_startAgain = false;
}
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
// Row content has changed, so start outputting it
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
_displayDriver->setRowNative(_rowNoToScreen);
_charPosToScreen = 0; // Prepare to output first character on next entry
} else {
// Row not changed, don't bother writing it.
}
} else {
// output character at current position
_displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
}
}
return;
}
/////////////////////////////////////////////////
// IODevice Class Member Overrides
/////////////////////////////////////////////////
// Device-specific initialisation
void _begin() override {
// Initialise device
if (_displayDriver->begin()) {
_display();
// Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++)
_rowGeneration[row]++;
// Start with top line (looks better).
// The numbers will wrap round on the first loop2 entry.
_rowNoToScreen = _numRows;
_charPosToScreen = _numCols;
}
}
void _loop(unsigned long) override {
screenUpdate();
}
// Display information about the device.
void _display() {
DIAG(F("HALDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString());
}
/////////////////////////////////////////////////
// DisplayInterface functions
//
/////////////////////////////////////////////////
public:
void _displayLoop() override {
screenUpdate();
}
// Position on nominated line number (0 to number of lines -1)
// Clear the line in the buffer ready for updating
// The displayNo referenced here is remembered and any following
// calls to write() will be directed to that display.
void _setRow(byte line) override {
if (line == 255) {
// LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") -
// scroll the contents of the buffer and put the new line
// at the bottom of the screen
for (int row=1; row<_numRows; row++) {
strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols);
_rowGeneration[row-1]++;
}
line = _numRows-1;
} else if (line >= _numRows)
line = _numRows - 1; // Overwrite bottom line.
_rowNo = line;
// Fill line with blanks
for (_colNo = 0; _colNo < _numCols; _colNo++)
_buffer[_rowNo*_numCols+_colNo] = ' ';
_colNo = 0;
// Mark that the buffer has been touched. It will start being
// sent to the screen on the next loop entry, by which time
// the line should have been written to the buffer.
_rowGeneration[_rowNo]++;
// Indicate that the output loop is to start updating the screen again from
// row 0. Otherwise, on a full screen rewrite the bottom part may be drawn
// before the top part!
_startAgain = true;
}
// Write one character to the screen referenced in the last setRow() call.
virtual size_t _write(uint8_t c) override {
// Write character to buffer (if there's space)
if (_colNo < _numCols) {
_buffer[_rowNo*_numCols+_colNo++] = c;
}
return 1;
}
// Write blanks to all of the screen buffer
void _clear() {
// Clear buffer
memset(_buffer, ' ', _numCols*_numRows);
_colNo = 0;
_rowNo = 0;
}
};
#endif // IO_HALDisplay_H

View File

@ -30,7 +30,7 @@
* *
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring * This driver polls the HC-SR04 by sending the trigger pulse and then measuring
* the length of the received pulse. If the calculated distance is less than * the length of the received pulse. If the calculated distance is less than
* the threshold, the output state returned by a read() call changes to 1. If * the threshold, the output _state returned by a read() call changes to 1. If
* the distance is greater than the threshold plus a hysteresis margin, the * the distance is greater than the threshold plus a hysteresis margin, the
* output changes to 0. The device also supports readAnalogue(), which returns * output changes to 0. The device also supports readAnalogue(), which returns
* the measured distance in cm, or 32767 if the distance exceeds the * the measured distance in cm, or 32767 if the distance exceeds the
@ -48,6 +48,20 @@
* Note: The timing accuracy required for measuring the pulse length means that * Note: The timing accuracy required for measuring the pulse length means that
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot * the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
* provide the required accuracy. * provide the required accuracy.
*
* Example configuration:
* HCSR04::create(23000, 32, 33, 80, 85);
*
* Where 23000 is the VPIN allocated,
* 32 is the pin connected to the HCSR04 trigger terminal,
* 33 is the pin connected to the HCSR04 echo terminal,
* 80 is the distance in cm below which pin 23000 will be active,
* and 85 is the distance in cm above which pin 23000 will be inactive.
*
* Alternative configuration, which hogs the processor until the measurement is complete
* (old behaviour, more accurate but higher impact on other CS tasks):
* HCSR04::create(23000, 32, 33, 80, 85, HCSR04::LOOP);
*
*/ */
#ifndef IO_HCSR04_H #ifndef IO_HCSR04_H
@ -61,37 +75,52 @@ private:
// pins must be arduino GPIO pins, not extender pins or HAL pins. // pins must be arduino GPIO pins, not extender pins or HAL pins.
int _trigPin = -1; int _trigPin = -1;
int _echoPin = -1; int _echoPin = -1;
// Thresholds for setting active state in cm. // Thresholds for setting active _state in cm.
uint8_t _onThreshold; // cm uint8_t _onThreshold; // cm
uint8_t _offThreshold; // cm uint8_t _offThreshold; // cm
// Last measured distance in cm. // Last measured distance in cm.
uint16_t _distance; uint16_t _distance;
// Active=1/inactive=0 state // Active=1/inactive=0 _state
uint8_t _value = 0; uint8_t _value = 0;
// Factor for calculating the distance (cm) from echo time (ms). // Factor for calculating the distance (cm) from echo time (us).
// Based on a speed of sound of 345 metres/second. // Based on a speed of sound of 345 metres/second.
const uint16_t factor = 58; // ms/cm const uint16_t factor = 58; // us/cm
// Limit the time spent looping by dropping out when the expected
// worst case threshold value is greater than an arbitrary value.
const uint16_t maxPermittedLoopTime = 10 * factor; // max in us
unsigned long _startTime = 0;
unsigned long _maxTime = 0;
enum {DORMANT, MEASURING}; // _state values
uint8_t _state = DORMANT;
uint8_t _counter = 0;
uint16_t _options = 0;
public: public:
// Constructor perfroms static initialisation of the device object enum Options {
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { LOOP = 1, // Option HCSR04::LOOP reinstates old behaviour, i.e. complete measurement in one loop entry.
};
// Static create function provides alternative way to create object
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options = 0) {
if (checkNoOverlap(vpin))
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold, options);
}
protected:
// Constructor performs static initialisation of the device object
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options) {
_firstVpin = vpin; _firstVpin = vpin;
_nPins = 1; _nPins = 1;
_trigPin = trigPin; _trigPin = trigPin;
_echoPin = echoPin; _echoPin = echoPin;
_onThreshold = onThreshold; _onThreshold = onThreshold;
_offThreshold = offThreshold; _offThreshold = offThreshold;
_options = options;
addDevice(this); addDevice(this);
} }
// Static create function provides alternative way to create object
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
}
protected:
// _begin function called to perform dynamic initialisation of the device // _begin function called to perform dynamic initialisation of the device
void _begin() override { void _begin() override {
_state = 0;
pinMode(_trigPin, OUTPUT); pinMode(_trigPin, OUTPUT);
pinMode(_echoPin, INPUT); pinMode(_echoPin, INPUT);
ArduinoPins::fastWriteDigital(_trigPin, 0); ArduinoPins::fastWriteDigital(_trigPin, 0);
@ -111,76 +140,102 @@ protected:
return _distance; return _distance;
} }
// _loop function - read HC-SR04 once every 50 milliseconds. // _loop function - read HC-SR04 once every 100 milliseconds.
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
read_HCSR04device(); unsigned long waitTime;
// Delay next loop entry until 50ms have elapsed. switch(_state) {
delayUntil(currentMicros + 50000UL); case DORMANT: // Issue pulse
} // If receive pin is still set on from previous call, do nothing till next entry.
if (ArduinoPins::fastReadDigital(_echoPin)) return;
void _display() override {
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
}
private:
// This polls the HC-SR04 device by sending a pulse and measuring the duration of
// the pulse observed on the receive pin. In order to be kind to the rest of the CS
// software, no interrupts are used and interrupts are not disabled. The pulse duration
// is measured in a loop, using the micros() function. Therefore, interrupts from other
// sources may affect the result. However, interrupts response code in CS typically takes
// much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm
// in the HC-SR04.
// To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
// greater than the offThreshold.
//
void read_HCSR04device() {
// uint16 enough to time up to 65ms
uint16_t startTime, waitTime, currentTime, maxTime;
// If receive pin is still set on from previous call, abort the read.
if (ArduinoPins::fastReadDigital(_echoPin))
return;
// Send 10us pulse to trigger transmitter // Send 10us pulse to trigger transmitter
ArduinoPins::fastWriteDigital(_trigPin, 1); ArduinoPins::fastWriteDigital(_trigPin, 1);
delayMicroseconds(10); delayMicroseconds(10);
ArduinoPins::fastWriteDigital(_trigPin, 0); ArduinoPins::fastWriteDigital(_trigPin, 0);
// Wait for receive pin to be set // Wait, with timeout, for echo pin to become set.
startTime = currentTime = micros(); // Measured time delay is just under 500us, so
maxTime = factor * _offThreshold * 2; // wait for max of 1000us.
_startTime = micros();
_maxTime = 1000;
while (!ArduinoPins::fastReadDigital(_echoPin)) { while (!ArduinoPins::fastReadDigital(_echoPin)) {
// lastTime = currentTime; // Not set yet, see if we've timed out.
currentTime = micros(); waitTime = micros() - _startTime;
waitTime = currentTime - startTime; if (waitTime > _maxTime) {
if (waitTime > maxTime) { // Timeout waiting for pulse start, abort the read and start again
// Timeout waiting for pulse start, abort the read _state = DORMANT;
return; return;
} }
} }
// Wait for receive pin to reset, and measure length of pulse // Echo pulse started, so wait for echo pin to reset, and measure length of pulse
startTime = currentTime = micros(); _startTime = micros();
maxTime = factor * _offThreshold; _maxTime = factor * _offThreshold;
while (ArduinoPins::fastReadDigital(_echoPin)) { _state = MEASURING;
currentTime = micros(); // If maximum measurement time is high, then skip until next loop entry before
waitTime = currentTime - startTime; // starting to look for pulse end.
// This gives better accuracy at shorter distance thresholds but without extending
// loop execution time for longer thresholds. If LOOP option is set on, then
// the entire measurement will be done in one loop entry, i.e. the code will fall
// through into the measuring phase.
if (!(_options & LOOP) && _maxTime > maxPermittedLoopTime) break;
/* fallthrough */
case MEASURING: // Check if echo pulse has finished
do {
waitTime = micros() - _startTime;
if (!ArduinoPins::fastReadDigital(_echoPin)) {
// Echo pulse completed; check if pulse length is below threshold and if so set value.
if (waitTime <= factor * _onThreshold) {
// Measured time is within the onThreshold, so value is one.
_value = 1;
// If the new distance value is less than the current, use it immediately.
// But if the new distance value is longer, then it may be erroneously long
// (because of extended loop times delays), so apply a delay to distance increases.
uint16_t estimatedDistance = waitTime / factor;
if (estimatedDistance < _distance)
_distance = estimatedDistance;
else
_distance += 1; // Just increase distance slowly.
_counter = 0;
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, _distance);
}
_state = DORMANT;
} else {
// Echo pulse hasn't finished, so check if maximum time has elapsed
// If pulse is too long then set return value to zero, // If pulse is too long then set return value to zero,
// and finish without waiting for end of pulse. // and finish without waiting for end of pulse.
if (waitTime > maxTime) { if (waitTime > _maxTime) {
// Pulse length longer than maxTime, reset value. // Pulse length longer than maxTime, value is provisionally zero.
// But don't change _value unless provisional value is zero for 10 consecutive measurements
if (_value == 1) {
if (++_counter >= 10) {
_value = 0; _value = 0;
_distance = 32767; _distance = 32767;
return; _counter = 0;
} }
} }
// Check if pulse length is below threshold, if so set value. _state = DORMANT; // start again
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance); }
_distance = waitTime / factor; // in centimetres }
if (_distance < _onThreshold) // If there's lots of time remaining before the expected completion time,
_value = 1; // then exit and wait for next loop entry. Otherwise, loop until we finish.
// If option LOOP is set, then we loop until finished anyway.
uint32_t remainingTime = _maxTime - waitTime;
if (!(_options & LOOP) && remainingTime < maxPermittedLoopTime) return;
} while (_state == MEASURING) ;
break;
}
// Datasheet recommends a wait of at least 60ms between measurement cycles
if (_state == DORMANT)
delayUntil(currentMicros+60000UL); // wait 60ms till next measurement
}
void _display() override {
DIAG(F("HCSR04 Configured on VPIN:%u TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
} }
}; };

View File

@ -1,4 +1,5 @@
/* /*
* © 2022 Paul M Antoine
* © 2021, Neil McKechnie. All rights reserved. * © 2021, Neil McKechnie. All rights reserved.
* *
* This file is part of DCC++EX API * This file is part of DCC++EX API
@ -24,20 +25,20 @@
class MCP23008 : public GPIOBase<uint8_t> { class MCP23008 : public GPIOBase<uint8_t> {
public: public:
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin);
} }
private:
// Constructor // Constructor
MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) { : GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) {
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer)); outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_GPIO; outputBuffer[0] = REG_GPIO;
} }
private:
void _writeGpioPort() override { void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState); I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
} }
@ -59,7 +60,7 @@ private:
if (immediate) { if (immediate) {
uint8_t buffer; uint8_t buffer;
I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO); I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO);
_portInputState = buffer; _portInputState = buffer | _portMode;
} else { } else {
// Queue new request // Queue new request
requestBlock.wait(); // Wait for preceding operation to complete requestBlock.wait(); // Wait for preceding operation to complete
@ -70,7 +71,7 @@ private:
// This function is invoked when an I/O operation on the requestBlock completes. // This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override { void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK) if (status == I2C_STATUS_OK)
_portInputState = inputBuffer[0]; _portInputState = inputBuffer[0] | _portMode;
else else
_portInputState = 0xff; _portInputState = 0xff;
} }

View File

@ -30,20 +30,19 @@
class MCP23017 : public GPIOBase<uint16_t> { class MCP23017 : public GPIOBase<uint16_t> {
public: public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin);
} }
private:
// Constructor // Constructor
MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin) : GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin)
{ {
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer)); outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_GPIOA; outputBuffer[0] = REG_GPIOA;
} }
private:
void _writeGpioPort() override { void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8); I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
} }
@ -66,7 +65,7 @@ private:
if (immediate) { if (immediate) {
uint8_t buffer[2]; uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA); I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA);
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode;
} else { } else {
// Queue new request // Queue new request
requestBlock.wait(); // Wait for preceding operation to complete requestBlock.wait(); // Wait for preceding operation to complete
@ -77,7 +76,7 @@ private:
// This function is invoked when an I/O operation on the requestBlock completes. // This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override { void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK) if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
else else
_portInputState = 0xffff; _portInputState = 0xffff;
} }

112
IO_PCA9555.h Normal file
View File

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

View File

@ -31,15 +31,14 @@ static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */
static const byte MODE1_RESTART=0x80; /**< Restart enabled */ static const byte MODE1_RESTART=0x80; /**< Restart enabled */
static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
static const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
// Predeclare helper function // Predeclare helper function
static void writeRegister(byte address, byte reg, byte value); static void writeRegister(byte address, byte reg, byte value);
// Create device driver instance. // Create device driver instance.
void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
new PCA9685(firstVpin, nPins, I2CAddress); if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency);
} }
// Configure a port on the PCA9685. // Configure a port on the PCA9685.
@ -47,7 +46,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
if (configType != CONFIGURE_SERVO) return false; if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false; if (paramCount != 5) return false;
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]); vpin, params[0], params[1], params[2], params[3], params[4]);
#endif #endif
@ -73,10 +72,14 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
} }
// Constructor // Constructor
PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) { PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = min(nPins, 16); _nPins = (nPins > 16) ? 16 : nPins;
_I2CAddress = I2CAddress; _I2CAddress = i2cAddress;
// Calculate prescaler value for PWM clock
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
// To save RAM, space for servo configuration is not allocated unless a pin is used. // To save RAM, space for servo configuration is not allocated unless a pin is used.
// Initialise the pointers to NULL. // Initialise the pointers to NULL.
for (int i=0; i<_nPins; i++) for (int i=0; i<_nPins; i++)
@ -98,7 +101,7 @@ void PCA9685::_begin() {
// Initialise I/O module here. // Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow // In theory, we should wait 500us before sending any other commands to each device, to allow
@ -115,7 +118,7 @@ void PCA9685::_begin() {
// For this function, the configured profile is used. // For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) { void PCA9685::_write(VPIN vpin, int value) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
#endif #endif
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
if (value) value = 1; if (value) value = 1;
@ -142,7 +145,7 @@ void PCA9685::_write(VPIN vpin, int value) {
// //
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif #endif
if (_deviceState == DEVSTATE_FAILED) return; if (_deviceState == DEVSTATE_FAILED) return;
@ -239,13 +242,13 @@ void PCA9685::updatePosition(uint8_t pin) {
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void PCA9685::writeDevice(uint8_t pin, int value) { void PCA9685::writeDevice(uint8_t pin, int value) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); DIAG(F("PCA9685 I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif #endif
// Wait for previous request to complete // Wait for previous request to complete
uint8_t status = requestBlock.wait(); uint8_t status = requestBlock.wait();
if (status != I2C_STATUS_OK) { if (status != I2C_STATUS_OK) {
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
DIAG(F("PCA9685 I2C:x%x failed %S"), _I2CAddress, I2CManager.getErrorMessage(status)); DIAG(F("PCA9685 I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
} else { } else {
// Set up new request. // Set up new request.
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
@ -259,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
// Display details of this device. // Display details of this device.
void PCA9685::_display() { void PCA9685::_display() {
DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
} }
@ -272,5 +275,5 @@ static void writeRegister(byte address, byte reg, byte value) {
// The profile below is in the range 0-100% and should be combined with the desired limits // The profile below is in the range 0-100% and should be combined with the desired limits
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. // i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
const byte FLASH PCA9685::_bounceProfile[30] = const uint8_t FLASH PCA9685::_bounceProfile[30] =
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};

170
IO_PCA9685pwm.h Normal file
View File

@ -0,0 +1,170 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This driver performs the basic interface between the HAL and an
* I2C-connected PCA9685 16-channel PWM module. When requested, it
* commands the device to set the PWM mark-to-period ratio accordingly.
* The call to IODevice::writeAnalogue(vpin, value) specifies the
* desired value in the range 0-4095 (0=0% and 4095=100%).
*
* This driver can be used for simple servo control by writing values between
* about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250
* for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length).
* A value of zero will switch off the servo. To create the device, use
* the following syntax:
*
* PCA9685_basic::create(vpin, npins, i2caddress);
*
* For LED control, a value of 0 is fully off, and 4095 is fully on. It is
* recommended, to reduce flicker of LEDs, that the frequency be configured
* to a value higher than the default of 50Hz. To do this, create the device
* as follows, for a frequency of 200Hz.:
*
* PCA9685_basic::create(vpin, npins, i2caddress, 200);
*
*/
#ifndef PCA9685_BASIC_H
#define PCA9685_BASIC_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
/*
* IODevice subclass for PCA9685 16-channel PWM module.
*/
class PCA9685pwm : public IODevice {
public:
// Create device driver instance.
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency);
}
private:
// structures for setting up non-blocking writes to PWM controller
I2CRB requestBlock;
uint8_t outputBuffer[5];
uint16_t prescaler;
// REGISTER ADDRESSES
const uint8_t PCA9685_MODE1=0x00; // Mode Register
const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/
const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */
// MODE1 bits
const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */
const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */
const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */
const uint32_t FREQUENCY_OSCILLATOR=25000000; /** Accurate enough for our purposes */
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
// Constructor
PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin;
_nPins = (nPins>16) ? 16 : nPins;
_I2CAddress = i2cAddress;
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
addDevice(this);
// Initialise structure used for setting pulse rate
requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer));
}
// Device-specific initialisation
void _begin() override {
I2CManager.begin();
I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C
// In reality, other devices including the Arduino will limit
// the clock speed to a lower rate.
// Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow
// the PWM oscillator to get running. However, we don't do any specific wait, as there's
// plenty of other stuff to do before we will send a command.
#if defined(DIAG_IO)
_display();
#endif
} else
_deviceState = DEVSTATE_FAILED;
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
//
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
(void)param1; (void)param2; // suppress compiler warning
#ifdef DIAG_IO
DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"),
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
writeDevice(pin, value);
}
// Display details of this device.
void _display() override {
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
// writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void writeDevice(uint8_t pin, int value) {
#ifdef DIAG_IO
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif
// Wait for previous request to complete
uint8_t status = requestBlock.wait();
if (status != I2C_STATUS_OK) {
_deviceState = DEVSTATE_FAILED;
DIAG(F("PCA9685pwm I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
} else {
// Set up new request.
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
outputBuffer[1] = 0;
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
outputBuffer[3] = value & 0xff;
outputBuffer[4] = value >> 8;
I2CManager.queueRequest(&requestBlock);
}
}
// Internal helper function for this device
static void writeRegister(I2CAddress address, uint8_t reg, uint8_t value) {
I2CManager.write(address, 2, reg, value);
}
};
#endif

View File

@ -1,4 +1,5 @@
/* /*
* © 2022 Paul M Antoine
* © 2021, Neil McKechnie. All rights reserved. * © 2021, Neil McKechnie. All rights reserved.
* *
* This file is part of DCC++EX API * This file is part of DCC++EX API
@ -42,20 +43,22 @@
class PCF8574 : public GPIOBase<uint8_t> { class PCF8574 : public GPIOBase<uint8_t> {
public: public:
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin);
} }
PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) private:
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin)
{ {
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
} }
private: // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. // The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
// Unused pins are driven '0'.
void _writeGpioPort() override { void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse);
} }
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
@ -63,9 +66,8 @@ private:
// and enable pull-up. // and enable pull-up.
void _writePullups() override { } void _writePullups() override { }
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
void _writePortModes() override { void _writePortModes() override {
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); _writeGpioPort();
} }
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
@ -75,7 +77,7 @@ private:
if (immediate) { if (immediate) {
uint8_t buffer[1]; uint8_t buffer[1];
I2CManager.read(_I2CAddress, buffer, 1); I2CManager.read(_I2CAddress, buffer, 1);
_portInputState = buffer[0]; _portInputState = buffer[0] | _portMode;
} else { } else {
requestBlock.wait(); // Wait for preceding operation to complete requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register // Issue new request to read GPIO register
@ -86,7 +88,7 @@ private:
// This function is invoked when an I/O operation on the requestBlock completes. // This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override { void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK) if (status == I2C_STATUS_OK)
_portInputState = inputBuffer[0]; _portInputState = inputBuffer[0] | _portMode;
else else
_portInputState = 0xff; _portInputState = 0xff;
} }

109
IO_PCF8575.h Normal file
View File

@ -0,0 +1,109 @@
/*
* © 2023, Paul Antoine, and Discord user @ADUBOURG
* © 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/>.
*/
/*
* The PCF8575 is a simple device; it only has one register. The device
* input/output mode and pullup are configured through this, and the
* output state is written and the input state read through it too.
*
* This is accomplished by having a weak resistor in series with the output,
* and a read-back of the other end of the resistor. As an output, the
* pin state is set to 1 or 0, and the output voltage goes to +5V or 0V
* (through the weak resistor).
*
* In order to use the pin as an input, the output is written as
* a '1' in order to pull up the resistor. Therefore the input will be
* 1 unless the pin is pulled down externally, in which case it will be 0.
*
* As a consequence of this approach, it is not possible to use the device for
* inputs without pullups.
*/
#ifndef IO_PCF8575_H
#define IO_PCF8575_H
#include "IO_GPIOBase.h"
#include "FSH.h"
class PCF8575 : public GPIOBase<uint16_t> {
public:
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin);
}
private:
PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin)
{
requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer));
}
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
// The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
// Unused pins are driven '0'.
void _writeGpioPort() override {
uint16_t bits = (_portOutputState | ~_portMode) & _portInUse;
I2CManager.write(_I2CAddress, 2, bits, bits>>8);
}
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
// Therefore, writing '1' in _writePortModes is enough to set the module to input mode
// and enable pull-up.
void _writePullups() override { }
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
void _writePortModes() override {
_writeGpioPort();
}
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
// When not in immediate mode, it initiates a request using the request block and returns.
// When the request completes, _processCompletion finishes the operation.
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2);
_portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode;
} else {
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]) | _portMode;
else
_portInputState = 0xffff;
}
// Set up device ports
void _setupDevice() override {
_writePortModes();
_writeGpioPort();
_writePullups();
}
uint8_t inputBuffer[2];
};
#endif

192
IO_RotaryEncoder.h Normal file
View File

@ -0,0 +1,192 @@
/*
* © 2023, Peter Cole. All rights reserved.
* © 2022, Peter Cole. All rights reserved.
*
* 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/>.
*/
/*
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
*
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
*
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
* can be tested in EX-RAIL with:
* 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
*
* 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.
*
* 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.
*/
#ifndef IO_ROTARYENCODER_H
#define IO_ROTARYENCODER_H
#include "EXRAIL2.h"
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
class RotaryEncoder : public IODevice {
public:
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
}
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
void _begin() {
uint8_t _status;
// Attempt to initilalise device
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
// Send RE_RDY, must receive RE_RDY to be online
_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];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
}
} 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
_display();
#endif
} else {
DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed
if (_i2crb.isBusy()) return; // Return if I2C operation still in progress
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) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin, 1);
} else {
RMFT2::changeEvent(_firstVpin, 0);
}
#endif
}
}
// Return the position sent by the rotary encoder software
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
// Send the feedback value to the rotary encoder software
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
if (value != 0) value = 0x01;
byte _feedbackBuffer[2] = {RE_OP, (byte)value};
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
// 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 {
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(""));
}
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _sendBuffer[1];
uint8_t _rcvBuffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
I2CRB _i2crb;
unsigned long _lastPositionRead = 0;
const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms
enum {
RE_RDY = 0xA0, // Flag to check if encoder is ready for 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
};
};
#endif

33
IO_Servo.cpp Normal file
View File

@ -0,0 +1,33 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "IO_Servo.h"
#include "FSH.h"
// Profile for a bouncing signal or turnout
// The profile below is in the range 0-100% and should be combined with the desired limits
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
//
// Note: This has been put into its own .CPP file to ensure that duplicates aren't created
// if the IO_Servo.h library is #include'd in multiple source files.
//
const uint8_t FLASH Servo::_bounceProfile[30] =
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};

298
IO_Servo.h Normal file
View File

@ -0,0 +1,298 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This device is a layered device which is designed to sit on top of another
* device. The underlying device class is expected to accept writeAnalogue calls
* which will normally cause some physical movement of something. The device may be a servo,
* a motor or some other kind of positioner, and the something might be a turnout,
* a semaphore signal or something else. One user has used this capability for
* moving a figure along the platform on their layout!
*
* Example of use:
* In myHal.cpp,
*
* #include "IO_Servo.h"
* ...
* PCA9685::create(100,16,0x40); // First create the hardware interface device
* Servo::create(300,16,100); // Then create the higher level device which
* // references pins 100-115 or a subset of them.
*
* Then any reference to pins 300-315 will cause the servo driver to send output
* PWM commands to the corresponding PCA9685 driver pins 100-115. The PCA9685 driver may
* be substituted with any other driver which provides analogue output
* capability, e.g. EX-IOExpander devices, as long as they are capable of interpreting
* the writeAnalogue() function calls.
*/
#include "IODevice.h"
#ifndef IO_SERVO_H
#define IO_SERVO_H
#include "I2CManager.h"
#include "DIAG.h"
class Servo : IODevice {
public:
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
// Create device driver instance.
static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin=VPIN_NONE) {
new Servo(firstVpin, nPins, firstSlavePin);
}
private:
VPIN _firstSlavePin;
IODevice *_slaveDevice = NULL;
struct ServoData {
uint16_t activePosition : 12; // Config parameter
uint16_t inactivePosition : 12; // Config parameter
uint16_t currentPosition : 12;
uint16_t fromPosition : 12;
uint16_t toPosition : 12;
uint8_t profile; // Config parameter
uint16_t stepNumber; // Index of current step (starting from 0)
uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
uint8_t currentProfile; // profile being used for current animation.
uint16_t duration; // time (tenths of a second) for animation to complete.
}; // 14 bytes per element, i.e. per pin in use
struct ServoData *_servoData [16];
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
static const uint8_t FLASH _bounceProfile[30];
const unsigned int refreshInterval = 50; // refresh every 50ms
// Configure a port on the Servo.
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (_deviceState == DEVSTATE_FAILED) return false;
if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false;
#ifdef DIAG_IO
DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]);
#endif
int8_t pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
s = _servoData[pin];
if (!s) return false; // Check for failed memory allocation
}
s->activePosition = params[0];
s->inactivePosition = params[1];
s->profile = params[2];
s->duration = params[3];
int state = params[4];
if (state != -1) {
// Position servo to initial state
writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition);
}
return true;
}
// Constructor
Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin = VPIN_NONE) {
_firstVpin = firstVpin;
_nPins = (nPins > 16) ? 16 : nPins;
if (firstSlavePin == VPIN_NONE)
_firstSlavePin = firstVpin;
else
_firstSlavePin = firstSlavePin;
// To save RAM, space for servo configuration is not allocated unless a pin is used.
// Initialise the pointers to NULL.
for (int i=0; i<_nPins; i++)
_servoData[i] = NULL;
// Get reference to slave device.
_slaveDevice = findDevice(_firstSlavePin);
if (!_slaveDevice) {
DIAG(F("Servo: Slave device not found on Vpins %u-%u"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
DIAG(F("Servo: Slave device does not cover all Vpins %u-%u"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
addDevice(this, _slaveDevice); // Link device ahead of slave device to intercept requests
}
// Device-specific initialisation
void _begin() override {
#if defined(DIAG_IO)
_display();
#endif
}
// Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value);
#endif
int pin = vpin - _firstVpin;
if (value) value = 1;
struct ServoData *s = _servoData[pin];
if (s != NULL) {
// Use configured parameters
writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
} else {
/* simulate digital pin on PWM */
writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
}
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
// Profile is as follows:
// Bit 7: 0=Set output to 0% to power off servo motor when finished
// 1=Keep output at final position (better with LEDs, which will stay lit)
// Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds)
// 1 (Fast) Move servo in 0.5 seconds
// 2 (Medium) Move servo in 1.0 seconds
// 3 (Slow) Move servo in 2.0 seconds
// 4 (Bounce) Servo 'bounces' at extremes.
// Duration is in deciseconds (tenths of a second) and defaults to 0.
//
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
// Servo pin not configured, so configure now using defaults
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
if (s == NULL) return; // Check for memory allocation failure
s->activePosition = 4095;
s->inactivePosition = 0;
s->currentPosition = value;
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
}
// Animated profile. Initiate the appropriate action.
s->currentProfile = profile;
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
profileValue==Slow ? 40 : // 2.0 seconds
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
s->stepNumber = 0;
s->toPosition = value;
s->fromPosition = s->currentPosition;
}
// _read returns true if the device is currently in executing an animation,
// changing the output over a period of time.
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL)
return false; // No structure means no animation!
else
return (s->stepNumber < s->numSteps);
}
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return;
for (int pin=0; pin<_nPins; pin++) {
updatePosition(pin);
}
delayUntil(currentMicros + refreshInterval * 1000UL);
}
// Private function to reposition servo
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
void updatePosition(uint8_t pin) {
struct ServoData *s = _servoData[pin];
if (s == NULL) return; // No pin configuration/state data
if (s->numSteps == 0) return; // No animation in progress
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
// Go straight to end of sequence, output final position.
s->stepNumber = s->numSteps-1;
}
if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo
s->stepNumber++;
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash
uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
} else {
// All other profiles - calculate step by linear interpolation between from and to positions.
s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition);
}
// Send servo command to output driver
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, s->currentPosition);
} else if (s->stepNumber < s->numSteps + _catchupSteps) {
// We've finished animation, wait a little to allow servo to catch up
s->stepNumber++;
} else if (s->stepNumber == s->numSteps + _catchupSteps
&& s->currentPosition != 0) {
#ifdef IO_SWITCH_OFF_SERVO
if ((s->currentProfile & NoPowerOff) == 0) {
// Wait has finished, so switch off output driver to avoid servo buzz.
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, 0);
}
#endif
s->numSteps = 0; // Done now.
}
}
// Display details of this device.
void _display() override {
DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1,
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

134
IO_TouchKeypad.h Normal file
View File

@ -0,0 +1,134 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Driver for capacitative touch-pad based on the TTP229-B chip with serial
* (not I2C) output. The touchpad has 16 separate pads in a 4x4 matrix,
* numbered 1-16. The communications with the pad are via a clock signal sent
* from the controller to the device, and a data signal sent back by the device.
* The pins clockPin and dataPin must be local pins, not external (GPIO Expander)
* pins.
*
* To use,
* TouchKeypad::create(firstVpin, 16, clockPin, dataPin);
*
* NOTE: Most of these keypads ship with only 8 pads enabled. To enable all
* sixteen pads, locate the area of the board labelled P1 (four pairs of
* holes labelled 1 to 4 from the left); solder a jumper link between the pair
* labelled 3 (connected to pin TP2 on the chip). When this link is connected,
* the pins OUT1 to OUT8 are not used but all sixteen touch pads are operational.
*
* TODO: Allow a list of datapins to be provided so that multiple keypads can
* be read simultaneously by the one device driver and the one shared clock signal.
* As it stands, we can configure multiple driver instances, one for each keypad,
* and it will work fine. The clock will be driven to all devices but only one
* driver will be reading the responses from its corresponding device at a time.
*/
#ifndef IO_TOUCHKEYPAD_H
#define IO_TOUCHKEYPAD_H
#include "IODevice.h"
class TouchKeypad : public IODevice {
private:
// Here we define the device-specific variables.
uint16_t _inputStates = 0;
VPIN _clockPin;
VPIN _dataPin;
public:
// Static function to handle create calls.
static void create(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) {
if (checkNoOverlap(firstVpin,nPins)) new TouchKeypad(firstVpin, nPins, clockPin, dataPin);
}
protected:
// Constructor.
TouchKeypad(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) {
_firstVpin = firstVpin;
_nPins = (nPins > 16) ? 16 : nPins; // Maximum of 16 pads per device
_clockPin = clockPin;
_dataPin = dataPin;
addDevice(this);
}
// Device-specific initialisation
void _begin() override {
#if defined(DIAG_IO)
_display();
#endif
// Set clock pin as output, initially high, and data pin as input.
// Enable pullup on the input so that the default (not connected) state is
// 'keypad not pressed'.
ArduinoPins::fastWriteDigital(_clockPin, 1);
pinMode(_clockPin, OUTPUT);
pinMode(_dataPin, INPUT_PULLUP); // Force defined state when no connection
}
// Device-specific read function.
int _read(VPIN vpin) {
if (vpin < _firstVpin || vpin >= _firstVpin + _nPins) return 0;
// Return a value for the specified vpin.
return _inputStates & (1<<(vpin-_firstVpin)) ? 1 : 0;
}
// Loop function to do background scanning of the keyboard.
// The TTP229 device requires clock pulses to be sent to it,
// and the data bits can be read on the rising edge of the clock.
// By default the clock and data are inverted (active-low).
// A gap of more than 2ms is advised between successive read
// cycles, we wait for 100ms between reads of the keyboard as this
// provide a good enough response time.
// Maximum clock frequency is 512kHz, so put a 1us delay
// between clock transitions.
//
void _loop(unsigned long currentMicros) {
// Clock 16 bits from the device
uint16_t data = 0, maskBit = 0x01;
for (uint8_t pad=0; pad<16; pad++) {
ArduinoPins::fastWriteDigital(_clockPin, 0);
delayMicroseconds(1);
ArduinoPins::fastWriteDigital(_clockPin, 1);
data |= (ArduinoPins::fastReadDigital(_dataPin) ? 0 : maskBit);
maskBit <<= 1;
delayMicroseconds(1);
}
_inputStates = data;
#ifdef DIAG_IO
static uint16_t lastData = 0;
if (data != lastData) DIAG(F("KeyPad: %x"), data);
lastData = data;
#endif
delayUntil(currentMicros + 100000); // read every 100ms
}
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
void _display() {
DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin,
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
}
};
#endif // IO_TOUCHKEYPAD_H

View File

@ -42,14 +42,17 @@
* If you have more than one module, then you will need to specify a digital VPIN (Arduino * If you have more than one module, then you will need to specify a digital VPIN (Arduino
* digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now, * digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now,
* when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once * when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once
* all VL53L0X modules are turned off, the driver works through each module in turn by * all VL53L0X modules are turned off, the driver works through each module in turn,
* setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address. * setting XSHUT to HIGH to turn that module on, then writing that module's desired I2C address.
* In this way, many VL53L0X modules can be connected to the one I2C bus, each one * In this way, many VL53L0X modules can be connected to the one I2C bus, each one
* using a distinct I2C address. * using a distinct I2C address. The process is described in ST Microelectronics application
* note AN4846.
* *
* WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise, * WARNING: If the device's XSHUT pin is not connected, then it may be prone to noise,
* and the device may even reset when handled. If you're not using XSHUT, then it's * and the device may reset spontaneously or when handled and the device will stop responding
* best to tie it to +5V. * on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor
* (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example)
* include a pull-up on the module, but others don't.
* *
* The driver is configured as follows: * The driver is configured as follows:
* *
@ -70,7 +73,8 @@
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm), * lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
* highThreshold is the distance at which the digital vpin state is set to 0 (in mm), * highThreshold is the distance at which the digital vpin state is set to 0 (in mm),
* and xshutPin is the VPIN number corresponding to a digital output that is connected to the * and xshutPin is the VPIN number corresponding to a digital output that is connected to the
* XSHUT terminal on the module. * XSHUT terminal on the module. The digital output may be an Arduino pin or an
* I/O extender pin.
* *
* Example: * Example:
* In mySetup function within mySetup.cpp: * In mySetup function within mySetup.cpp:
@ -93,7 +97,6 @@
class VL53L0X : public IODevice { class VL53L0X : public IODevice {
private: private:
uint8_t _i2cAddress;
uint16_t _ambient; uint16_t _ambient;
uint16_t _distance; uint16_t _distance;
uint16_t _signal; uint16_t _signal;
@ -101,20 +104,23 @@ private:
uint16_t _offThreshold; uint16_t _offThreshold;
VPIN _xshutPin; VPIN _xshutPin;
bool _value; bool _value;
uint8_t _nextState = 0; uint8_t _nextState = STATE_INIT;
I2CRB _rb; I2CRB _rb;
uint8_t _inBuffer[12]; uint8_t _inBuffer[12];
uint8_t _outBuffer[2]; uint8_t _outBuffer[2];
static bool _addressConfigInProgress;
// State machine states. // State machine states.
enum : uint8_t { enum : uint8_t {
STATE_INIT = 0, STATE_INIT,
STATE_CONFIGUREADDRESS = 1, STATE_RESTARTMODULE,
STATE_SKIP = 2, STATE_CONFIGUREADDRESS,
STATE_CONFIGUREDEVICE = 3, STATE_CONFIGUREDEVICE,
STATE_INITIATESCAN = 4, STATE_INITIATESCAN,
STATE_CHECKSTATUS = 5, STATE_CHECKSTATUS,
STATE_GETRESULTS = 6, STATE_GETRESULTS,
STATE_DECODERESULTS = 7, STATE_DECODERESULTS,
STATE_FAILED,
}; };
// Register addresses // Register addresses
@ -127,98 +133,126 @@ private:
}; };
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29; const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
public:
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { public:
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
}
protected:
VL53L0X(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = min(nPins, 3); _nPins = (nPins > 3) ? 3 : nPins;
_i2cAddress = i2cAddress; _I2CAddress = i2cAddress;
_onThreshold = onThreshold; _onThreshold = onThreshold;
_offThreshold = offThreshold; _offThreshold = offThreshold;
_xshutPin = xshutPin; _xshutPin = xshutPin;
_value = 0; _value = 0;
addDevice(this); addDevice(this);
} }
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
}
protected:
void _begin() override { void _begin() override {
if (_xshutPin == VPIN_NONE) { // If there's only one device, then the XSHUT pin need not be connected. However,
// Check if device is already responding on the nominated address. // the device will not respond on its default address if it has
if (I2CManager.exists(_i2cAddress)) { // already been changed. Therefore, we skip the address configuration if the
// Yes, it's already on this address, so skip the address initialisation. // desired address is already responding on the I2C bus.
_nextState = STATE_CONFIGUREDEVICE;
} else {
_nextState = STATE_INIT; _nextState = STATE_INIT;
} _addressConfigInProgress = false;
}
} }
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
uint8_t status; uint8_t status;
switch (_nextState) { switch (_nextState) {
case STATE_INIT: case STATE_INIT:
// On first entry to loop, reset this module by pulling XSHUT low. All modules if (I2CManager.exists(_I2CAddress)) {
// will be reset in turn. // Device already present on the nominated address, so skip the address initialisation.
_nextState = STATE_CONFIGUREDEVICE;
} else {
// On first entry to loop, reset this module by pulling XSHUT low. Each module
// will be addressed in turn, until all are in the reset state.
// If no XSHUT pin is configured, then only one device is supported.
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
_nextState = STATE_RESTARTMODULE;
delayUntil(currentMicros+10000);
}
break;
case STATE_RESTARTMODULE:
// On second entry, set XSHUT pin high to allow this module to restart.
// I've observed that the device tends to randomly reset if the XSHUT
// pin is set high from a 5V arduino, even through a pullup resistor.
// Assume that there will be a pull-up on the XSHUT pin to +2.8V as
// recommended in the device datasheet. Then we only need to
// turn our output pin high-impedence (by making it an input) and the
// on-board pullup will do its job.
// Ensure XSHUT is set for only one module at a time by using a
// shared flag accessible to all device instances.
if (!_addressConfigInProgress) {
_addressConfigInProgress = true;
// Configure XSHUT pin (if connected) to bring the module out of sleep mode.
if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, false);
// Allow the module time to restart
delayUntil(currentMicros+10000);
_nextState = STATE_CONFIGUREADDRESS; _nextState = STATE_CONFIGUREADDRESS;
}
break; break;
case STATE_CONFIGUREADDRESS: case STATE_CONFIGUREADDRESS:
// On second entry, set XSHUT pin high to allow the module to restart.
// On the module, there is a diode in series with the XSHUT pin to
// protect the low-voltage pin against +5V.
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1);
// Allow the module time to restart
delay(10);
// Then write the desired I2C address to the device, while this is the only // Then write the desired I2C address to the device, while this is the only
// module responding to the default address. // module responding to the default address.
I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); {
_nextState = STATE_SKIP; #if defined(I2C_EXTENDED_ADDRESS)
break; // Add subbus reference for desired address to the device default address.
case STATE_SKIP: I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS};
// Do nothing on the third entry. status = I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress());
#else
status = I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress);
#endif
if (status != I2C_STATUS_OK) {
reportError(status);
}
}
delayUntil(currentMicros+10000);
_nextState = STATE_CONFIGUREDEVICE; _nextState = STATE_CONFIGUREDEVICE;
break; break;
case STATE_CONFIGUREDEVICE: case STATE_CONFIGUREDEVICE:
// On next entry, check if device address has been set. // Allow next VL53L0X device to be configured
if (I2CManager.exists(_i2cAddress)) { _addressConfigInProgress = false;
// Now check if device address has been set.
if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
// Set 2.8V mode // Set 2.8V mode
write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, status = write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
} else { if (status != I2C_STATUS_OK) {
DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); reportError(status);
_deviceState = DEVSTATE_FAILED; } else
}
_nextState = STATE_INITIATESCAN; _nextState = STATE_INITIATESCAN;
} else {
DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
_nextState = STATE_FAILED;
}
break; break;
case STATE_INITIATESCAN: case STATE_INITIATESCAN:
// Not scanning, so initiate a scan // Not scanning, so initiate a scan
_outBuffer[0] = VL53L0X_REG_SYSRANGE_START; _outBuffer[0] = VL53L0X_REG_SYSRANGE_START;
_outBuffer[1] = 0x01; _outBuffer[1] = 0x01;
I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb); I2CManager.write(_I2CAddress, _outBuffer, 2, &_rb);
_nextState = STATE_CHECKSTATUS; _nextState = STATE_CHECKSTATUS;
break; break;
case STATE_CHECKSTATUS: case STATE_CHECKSTATUS:
status = _rb.status; status = _rb.status;
if (status == I2C_STATUS_PENDING) return; // try next time if (status == I2C_STATUS_PENDING) return; // try next time
if (status != I2C_STATUS_OK) { if (status != I2C_STATUS_OK) {
DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); reportError(status);
_deviceState = DEVSTATE_FAILED;
_value = false;
} else } else
_nextState = 2;
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
_nextState = STATE_GETRESULTS; _nextState = STATE_GETRESULTS;
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
break; break;
case STATE_GETRESULTS: case STATE_GETRESULTS:
// Ranging completed. Request results // Ranging completed. Request results
_outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb); I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
_nextState = 3;
delayUntil(currentMicros + 5000); // Allow 5ms to get data delayUntil(currentMicros + 5000); // Allow 5ms to get data
_nextState = STATE_DECODERESULTS; _nextState = STATE_DECODERESULTS;
break; break;
@ -239,15 +273,28 @@ protected:
else if (_distance > _offThreshold) else if (_distance > _offThreshold)
_value = false; _value = false;
} }
}
// Completed. Restart scan on next loop entry. // Completed. Restart scan on next loop entry.
_nextState = STATE_INITIATESCAN; _nextState = STATE_INITIATESCAN;
} else {
reportError(status);
}
break;
case STATE_FAILED:
// Do nothing.
delayUntil(currentMicros+1000000UL);
break; break;
default: default:
break; break;
} }
} }
// Function to report a failed I2C operation.
void reportError(uint8_t status) {
DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED;
_value = false;
}
// For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level. // For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level.
int _readAnalogue(VPIN vpin) override { int _readAnalogue(VPIN vpin) override {
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
@ -272,8 +319,8 @@ protected:
} }
void _display() override { void _display() override {
DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"),
_i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
} }
@ -287,13 +334,15 @@ private:
uint8_t outBuffer[2]; uint8_t outBuffer[2];
outBuffer[0] = reg; outBuffer[0] = reg;
outBuffer[1] = data; outBuffer[1] = data;
return I2CManager.write(_i2cAddress, outBuffer, 2); return I2CManager.write(_I2CAddress, outBuffer, 2);
} }
uint8_t read_reg(uint8_t reg) { uint8_t read_reg(uint8_t reg) {
// read byte from register and return value // read byte from register and return value
I2CManager.read(_i2cAddress, _inBuffer, 1, &reg, 1); I2CManager.read(_I2CAddress, _inBuffer, 1, &reg, 1);
return _inBuffer[0]; return _inBuffer[0];
} }
}; };
bool VL53L0X::_addressConfigInProgress = false;
#endif // IO_VL53L0X_h #endif // IO_VL53L0X_h

173
IO_duinoNodes.h Normal file
View File

@ -0,0 +1,173 @@
/*
* © 2022, Chris Harlow. All rights reserved.
* Based on original by: Robin Simonds, Beagle Bay Inc
*
* 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_duinoNodes_h
#define IO_duinoNodes_h
#include <Arduino.h>
#include "defines.h"
#include "IODevice.h"
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
class IO_duinoNodes : public IODevice {
public:
IO_duinoNodes(VPIN firstVpin, int nPins,
byte clockPin, byte latchPin, byte dataPin,
const byte* pinmap) :
IODevice(firstVpin, nPins) {
_latchPin=latchPin;
_clockPin=clockPin;
_dataPin=dataPin;
_pinMap=pinmap;
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
_pinValues=(byte*) calloc(_nShiftBytes,1);
// Connect to HAL so my _write, _read and _loop will be called as required.
IODevice::addDevice(this);
}
// Called by HAL to start handling this device
void _begin() override {
_deviceState = DEVSTATE_NORMAL;
pinMode(_latchPin,OUTPUT);
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
if (!_pinMap) _loopOutput();
}
// loop called by HAL supervisor
void _loop(unsigned long currentMicros) override {
if (_pinMap) _loopInput(currentMicros);
else if (_xmitPending) _loopOutput();
}
void _loopInput(unsigned long currentMicros) {
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
_prevMicros=currentMicros;
//set latch to HIGH to freeze & store parallel data
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
delayMicroseconds(1);
//set latch to LOW to enable the data to be transmitted serially
ArduinoPins::fastWriteDigital(_latchPin, LOW);
// stream in the bitmap using mapping order provided at constructor
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
byte newByte=0;
for (int xmitBit=0;xmitBit<8; xmitBit++) {
ArduinoPins::fastWriteDigital(_clockPin, LOW);
delayMicroseconds(1);
bool data = ArduinoPins::fastReadDigital(_dataPin);
byte map=_pinMap[xmitBit];
if (data) newByte |= map;
else newByte &= ~map;
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
delayMicroseconds(1);
}
_pinValues[xmitByte]=newByte;
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
}
}
void _loopOutput() {
// stream out the bitmap (highest pin first)
_xmitPending=false;
ArduinoPins::fastWriteDigital(_latchPin, LOW);
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
ArduinoPins::fastWriteDigital(_clockPin,LOW);
}
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
}
int _read(VPIN vpin) override {
int pin=vpin - _firstVpin;
bool b=DN_GET_BIT(pin);
return b?1:0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
bool oldval=DN_GET_BIT(pin);
bool newval=value!=0;
if (newval==oldval) return; // no change
if (newval) DN_SET_BIT(pin);
else DN_CLR_BIT(pin);
_xmitPending=true; // shift register will be sent on next _loop()
}
void _display() override {
DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"),
_pinMap?F("IN"):F("OUT"),
(int)_firstVpin,
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
}
private:
static const unsigned long POLL_MICROS=100000; // 10 / S
unsigned long _prevMicros;
int _nShiftBytes=0;
VPIN _latchPin,_clockPin,_dataPin;
byte* _pinValues;
bool _xmitPending; // Only relevant in output mode
const byte* _pinMap; // NULL in output mode
};
class IO_DNIN8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0,7,6,5,1,2,3,4
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNIN8K {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNOU8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
}
};
#endif

View File

@ -1,167 +0,0 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// CAUTION: the device dependent parts of this class are created in the .ini
// using LCD_Implementation.h
/* The strategy for drawing the screen is as follows.
* 1) There are up to eight rows of text to be displayed.
* 2) Blank rows of text are ignored.
* 3) If there are more non-blank rows than screen lines,
* then all of the rows are displayed, with the rest of the
* screen being blank.
* 4) If there are fewer non-blank rows than screen lines,
* then a scrolling strategy is adopted so that, on each screen
* refresh, a different subset of the rows is presented.
* 5) On each entry into loop2(), a single operation is sent to the
* screen; this may be a position command or a character for
* display. This spreads the onerous work of updating the screen
* and ensures that other loop() functions in the application are
* not held up significantly. The exception to this is when
* the loop2() function is called with force=true, where
* a screen update is executed to completion. This is normally
* only done during start-up.
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
* in the config.h.
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
* #define SCROLLMODE 1 is by page (alternate between pages),
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
*/
#include "LCDDisplay.h"
void LCDDisplay::clear() {
clearNative();
for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0';
topRow = -1; // loop2 will fill from row 0
}
void LCDDisplay::setRow(byte line) {
hotRow = line;
hotCol = 0;
}
size_t LCDDisplay::write(uint8_t b) {
if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1;
rowBuffer[hotRow][hotCol] = b;
hotCol++;
rowBuffer[hotRow][hotCol] = 0;
return 1;
}
void LCDDisplay::loop() {
if (!lcdDisplay) return;
lcdDisplay->loop2(false);
}
LCDDisplay *LCDDisplay::loop2(bool force) {
if (!lcdDisplay) return NULL;
// If output device is busy, don't do anything on this loop
// This avoids blocking while waiting for the device to complete.
if (isBusy()) return NULL;
unsigned long currentMillis = millis();
if (!force) {
// See if we're in the time between updates
if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME)
return NULL;
} else {
// force full screen update from the beginning.
rowFirst = -1;
rowNext = 0;
bufferPointer = 0;
done = false;
slot = 0;
}
do {
if (bufferPointer == 0) {
// Find a line of data to write to the screen.
if (rowFirst < 0) rowFirst = rowNext;
skipBlankRows();
if (!done) {
// Non-blank line found, so copy it.
for (uint8_t i = 0; i < sizeof(buffer); i++)
buffer[i] = rowBuffer[rowNext][i];
} else
buffer[0] = '\0'; // Empty line
setRowNative(slot); // Set position for display
charIndex = 0;
bufferPointer = &buffer[0];
} else {
// Write next character, or a space to erase current position.
char ch = *bufferPointer;
if (ch) {
writeNative(ch);
bufferPointer++;
} else
writeNative(' ');
if (++charIndex >= MAX_LCD_COLS) {
// Screen slot completed, move to next slot on screen
slot++;
bufferPointer = 0;
if (!done) {
moveToNextRow();
skipBlankRows();
}
}
if (slot >= lcdRows) {
// Last slot finished, reset ready for next screen update.
#if SCROLLMODE==2
if (!done) {
// On next refresh, restart one row on from previous start.
rowNext = rowFirst;
moveToNextRow();
skipBlankRows();
}
#endif
done = false;
slot = 0;
rowFirst = -1;
lastScrollTime = currentMillis;
return NULL;
}
}
} while (force);
return NULL;
}
void LCDDisplay::moveToNextRow() {
rowNext = (rowNext + 1) % MAX_LCD_ROWS;
#if SCROLLMODE == 1
// Finished if we've looped back to row 0
if (rowNext == 0) done = true;
#else
// Finished if we're back to the first one shown
if (rowNext == rowFirst) done = true;
#endif
}
void LCDDisplay::skipBlankRows() {
while (!done && rowBuffer[rowNext][0] == 0)
moveToNextRow();
}

View File

@ -41,27 +41,28 @@
// can't assume that its in that state when a sketch starts (and the // can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called). // LiquidCrystal constructor is called).
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols, LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols,
uint8_t lcd_rows) { uint8_t lcd_rows) {
_Addr = lcd_Addr; _Addr = lcd_Addr;
lcdRows = lcd_rows; lcdRows = lcd_rows; // Number of character rows (typically 2 or 4).
lcdCols = lcd_cols; lcdCols = lcd_cols; // Number of character columns (typically 16 or 20)
_backlightval = 0; _backlightval = 0;
}
bool LiquidCrystal_I2C::begin() {
I2CManager.begin(); I2CManager.begin();
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
if (I2CManager.exists(lcd_Addr)) { if (I2CManager.exists(_Addr)) {
DIAG(F("%dx%d LCD configured on I2C:x%x"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString());
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin();
backlight(); backlight();
lcdDisplay = this; } else {
DIAG(F("LCD not found on I2C:%s"), _Addr.toString());
return false;
} }
}
void LiquidCrystal_I2C::begin() {
if (lcdRows > 1) { if (lcdRows > 1) {
_displayfunction |= LCD_2LINE; _displayfunction |= LCD_2LINE;
} }
@ -79,15 +80,15 @@ void LiquidCrystal_I2C::begin() {
// we start in 8bit mode, try to set 4 bit mode // we start in 8bit mode, try to set 4 bit mode
write4bits(0x03); write4bits(0x03);
delayMicroseconds(4500); // wait min 4.1ms delayMicroseconds(5000); // wait min 4.1ms
// second try // second try
write4bits(0x03); write4bits(0x03);
delayMicroseconds(4500); // wait min 4.1ms delayMicroseconds(5000); // wait min 4.1ms
// third go! // third go!
write4bits(0x03); write4bits(0x03);
delayMicroseconds(150); delayMicroseconds(5000);
// finally, set to 4-bit interface // finally, set to 4-bit interface
write4bits(0x02); write4bits(0x02);
@ -99,26 +100,23 @@ void LiquidCrystal_I2C::begin() {
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display(); display();
// clear it off
clear();
// Initialize to default text direction (for roman languages) // Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode // set the entry mode
command(LCD_ENTRYMODESET | _displaymode); command(LCD_ENTRYMODESET | _displaymode);
setRowNative(0); return true;
} }
/********** high level commands, for the user! */ /********** high level commands, for the user! */
void LiquidCrystal_I2C::clearNative() { void LiquidCrystal_I2C::clearNative() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes 1.52ms delayMicroseconds(2000); // this command takes 1.52ms but allow plenty
} }
void LiquidCrystal_I2C::setRowNative(byte row) { void LiquidCrystal_I2C::setRowNative(byte row) {
int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row >= lcdRows) { if (row >= lcdRows) {
row = lcdRows - 1; // we count rows starting w/0 row = lcdRows - 1; // we count rows starting w/0
} }
@ -146,6 +144,10 @@ size_t LiquidCrystal_I2C::writeNative(uint8_t value) {
return 1; return 1;
} }
bool LiquidCrystal_I2C::isBusy() {
return rb.isBusy();
}
/*********** mid level commands, for sending data/cmds */ /*********** mid level commands, for sending data/cmds */
inline void LiquidCrystal_I2C::command(uint8_t value) { inline void LiquidCrystal_I2C::command(uint8_t value) {
@ -192,11 +194,12 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode; uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode;
// Send both nibbles // Send both nibbles
uint8_t len = 0; uint8_t len = 0;
rb.wait();
outputBuffer[len++] = highnib|En; outputBuffer[len++] = highnib|En;
outputBuffer[len++] = highnib; outputBuffer[len++] = highnib;
outputBuffer[len++] = lownib|En; outputBuffer[len++] = lownib|En;
outputBuffer[len++] = lownib; outputBuffer[len++] = lownib;
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
} }
// write 4 data bits to the HD44780 LCD controller. // write 4 data bits to the HD44780 LCD controller.
@ -206,14 +209,16 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) {
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the // I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
// HD44780 on the trailing edge of the Enable pin. // HD44780 on the trailing edge of the Enable pin.
uint8_t len = 0; uint8_t len = 0;
rb.wait();
outputBuffer[len++] = _data|En; outputBuffer[len++] = _data|En;
outputBuffer[len++] = _data; outputBuffer[len++] = _data;
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
} }
// write a byte to the PCF8574 I2C interface. We don't need to set // write a byte to the PCF8574 I2C interface. We don't need to set
// the enable pin for this. // the enable pin for this.
void LiquidCrystal_I2C::expanderWrite(uint8_t value) { void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
rb.wait();
outputBuffer[0] = value | _backlightval; outputBuffer[0] = value | _backlightval;
I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously
} }

View File

@ -22,7 +22,7 @@
#define LiquidCrystal_I2C_h #define LiquidCrystal_I2C_h
#include <Arduino.h> #include <Arduino.h>
#include "LCDDisplay.h" #include "Display.h"
#include "I2CManager.h" #include "I2CManager.h"
// commands // commands
@ -62,33 +62,38 @@
#define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit
#define Rs (1 << BACKPACK_Rs_BIT) // Register select bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit
class LiquidCrystal_I2C : public LCDDisplay { class LiquidCrystal_I2C : public DisplayDevice {
public: public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(); bool begin() override;
void clearNative() override; void clearNative() override;
void setRowNative(byte line) override; void setRowNative(byte line) override;
size_t writeNative(uint8_t c) override; size_t writeNative(uint8_t c) override;
// I/O is synchronous, so if this is called we're not busy!
bool isBusy() override;
void display(); void display();
void noBacklight(); void noBacklight();
void backlight(); void backlight();
void command(uint8_t); void command(uint8_t);
uint16_t getNumCols() { return lcdCols; }
uint16_t getNumRows() { return lcdRows; }
private: private:
void send(uint8_t, uint8_t); void send(uint8_t, uint8_t);
void write4bits(uint8_t); void write4bits(uint8_t);
void expanderWrite(uint8_t); void expanderWrite(uint8_t);
uint8_t _Addr; uint8_t lcdCols=0, lcdRows=0;
I2CAddress _Addr;
uint8_t _displayfunction; uint8_t _displayfunction;
uint8_t _displaycontrol; uint8_t _displaycontrol;
uint8_t _displaymode; uint8_t _displaymode;
uint8_t _backlightval; uint8_t _backlightval = 0;
uint8_t outputBuffer[4]; uint8_t outputBuffer[4];
// I/O is synchronous, so if this is called we're not busy! I2CRB rb;
bool isBusy() override { return false; }
}; };
#endif #endif

View File

@ -1,7 +1,8 @@
/* /*
* © 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.
* *
@ -22,66 +23,147 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include "MotorDriver.h" #include "MotorDriver.h"
#include "DCCWaveform.h"
#include "DCCTimer.h" #include "DCCTimer.h"
#include "DIAG.h" #include "DIAG.h"
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH unsigned long MotorDriver::globalOverloadStart = 0;
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
bool MotorDriver::usePWM=false; volatile portreg_t shadowPORTA;
bool MotorDriver::commonFaultPin=false; volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC;
MotorDriver::MotorDriver(byte 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 **");
getFastPin(F("POWER"),powerPin,fastPowerPin);
pinMode(powerPin, OUTPUT); invertPower=power_pin < 0;
if (invertPower) {
powerPin = 0-power_pin;
IODevice::write(powerPin,HIGH);// set to OUTPUT and off
} else {
powerPin = power_pin;
IODevice::write(powerPin,LOW);// set to OUTPUT and off
}
signalPin=signal_pin; signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin); getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT); pinMode(signalPin, OUTPUT);
fastSignalPin.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTA;
}
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
DIAG(F("Found PORTB pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTB;
}
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
DIAG(F("Found PORTC pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTC;
}
signalPin2=signal_pin2; signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) { if (signalPin2!=UNUSED_PIN) {
dualSignal=true; dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2); getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT); pinMode(signalPin2, OUTPUT);
fastSignalPin2.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTA;
}
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
DIAG(F("Found PORTB pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTB;
}
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
DIAG(F("Found PORTC pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTC;
}
} }
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
pinMode(brakePin, OUTPUT); pinMode(brakePin, OUTPUT);
setBrake(false); 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) { if (currentPin!=UNUSED_PIN) {
pinMode(currentPin, INPUT); int ret = ADCee::init(currentPin);
senseOffset=analogRead(currentPin); // value of sensor at zero current 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
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;
} }
senseFactor=sense_factor; // This conversion performed at compile time so the remainder of the code never needs
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps; tripMilliamps=trip_milliamps;
rawCurrentTripValue=(int)(trip_milliamps / sense_factor); #ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
// can reach the trip value. So independent of the current, the
// short circuit protection would never trip. So we adjust the
// trip value so that it is tiggered when the ADC reports it's
// maximum value instead.
// DIAG(F("Changing short detection value from %d to %d mA"),
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
}
if (currentPin==UNUSED_PIN) if (currentPin==UNUSED_PIN)
DIAG(F("MotorDriver ** WARNING ** No current or short detection")); DIAG(F("%S No current or short detection"), warnString);
else else {
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"), DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
currentPin-A0, senseOffset,rawCurrentTripValue);
// self testing diagnostic for the non-float converters... may be removed when happy
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
}
progTripValue = mA2raw(TRIP_CURRENT_PROG);
} }
bool MotorDriver::isPWMCapable() { bool MotorDriver::isPWMCapable() {
@ -89,15 +171,28 @@ bool MotorDriver::isPWMCapable() {
} }
void MotorDriver::setPower(bool on) { void MotorDriver::setPower(POWERMODE mode) {
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) {
// toggle brake before turning power on - resets overcurrent error // when switching a track On, we need to check the crrentOffset with the pin OFF
// on the Pololu board if brake is wired to ^D2. if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
setBrake(true); senseOffset = ADCee::read(currentPin);
setBrake(false); DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset);
setHIGH(fastPowerPin);
} }
else setLOW(fastPowerPin);
IODevice::write(powerPin,invertPower ? LOW : HIGH);
if (isProgTrack)
DCCWaveform::progTrack.clearResets();
}
else {
IODevice::write(powerPin,invertPower ? HIGH : LOW);
}
powerMode=mode;
} }
// setBrake applies brake if on == true. So to get // setBrake applies brake if on == true. So to get
@ -108,80 +203,252 @@ void MotorDriver::setPower(bool on) {
// (HIGH == release brake) and setBrake does // (HIGH == release brake) and setBrake does
// compensate for that. // compensate for that.
// //
void MotorDriver::setBrake(bool on) { void MotorDriver::setBrake(bool on, bool interruptContext) {
if (brakePin == UNUSED_PIN) return; if (brakePin == UNUSED_PIN) return;
if (on ^ invertBrake) setHIGH(fastBrakePin); if (!interruptContext) {noInterrupts();}
else setLOW(fastBrakePin); if (on ^ invertBrake)
setHIGH(fastBrakePin);
else
setLOW(fastBrakePin);
if (!interruptContext) {interrupts();}
} }
void MotorDriver::setSignal( bool high) {
if (usePWM) {
DCCTimer::setPWM(signalPin,high);
}
else {
if (high) {
setHIGH(fastSignalPin);
if (dualSignal) setLOW(fastSignalPin2);
}
else {
setLOW(fastSignalPin);
if (dualSignal) setHIGH(fastSignalPin2);
}
}
}
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
volatile unsigned int overflow_count=0;
#endif
bool MotorDriver::canMeasureCurrent() { 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, create 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
* a central value depending on direction. * a central value depending on direction.
*
* Bool fromISR should be adjusted dependent how function is called
*/ */
int MotorDriver::getCurrentRaw() { int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR;
if (currentPin==UNUSED_PIN) return 0; if (currentPin==UNUSED_PIN) return 0;
int current; int current;
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) current = ADCee::read(currentPin, fromISR);
bool irq = disableInterrupts(); // here one can diag raw value
current = analogRead(currentPin)-senseOffset; // if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
enableInterrupts(irq); current = current-senseOffset; // adjust with offset
#else // Uno, Mega and all the TEENSY3* but not TEENSY4*
unsigned char sreg_backup;
sreg_backup = SREG; /* save interrupt enable/disable state */
cli();
current = analogRead(currentPin)-senseOffset;
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
overflow_count = 0;
#endif
if (sreg_backup & 128) sei(); /* restore interrupt state */
#endif // outer #
if (current<0) current=0-current; if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) // 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;
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
// The default analogRead takes ~100uS which is catastrphic
// so DCCTimer has set the sample time to be much faster.
} }
unsigned int MotorDriver::raw2mA( int raw) { #ifdef ANALOG_READ_INTERRUPT
return (unsigned int)(raw * senseFactor); /*
* This should only be called in interrupt context
* Copies current value from HW to cached value in
* Motordriver.
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
bool MotorDriver::sampleCurrentFromHW() {
byte low, high;
//if (!bit_is_set(ADCSRA, ADIF))
if (bit_is_set(ADCSRA, ADSC))
return false;
// if ((ADMUX & mask) != (currentPin - A0))
// return false;
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
sampleCurrent = (high << 8) | low;
sampleCurrentTimestamp = millis();
return true;
} }
int MotorDriver::mA2raw( unsigned int mA) { void MotorDriver::startCurrentFromHW() {
return (int)(mA / senseFactor); #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
const byte mask = 7;
#else
const byte mask = 31;
#endif
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
}
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
#ifdef VARIABLE_TONES
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
349, 392, 440, 494,
523, 587, 659, 698,
494, 440, 392, 249,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is differnet)
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
#endif
default:
break;
}
// spedcoode is a dcc speed & direction
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
byte brake;
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
if (tSpeed <= 1) brake = 255;
else if (tSpeed >= 127) brake = 0;
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32)
DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
#endif
//DIAG(F("DCSignal %d"), speedcode);
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
noInterrupts();
HAVE_PORTA(shadowPORTA=PORTA);
setSignal(tDir);
HAVE_PORTA(PORTA=shadowPORTA);
interrupts();
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
noInterrupts();
HAVE_PORTB(shadowPORTB=PORTB);
setSignal(tDir);
HAVE_PORTB(PORTB=shadowPORTB);
interrupts();
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
noInterrupts();
HAVE_PORTC(shadowPORTC=PORTC);
setSignal(tDir);
HAVE_PORTC(PORTC=shadowPORTC);
interrupts();
} else {
noInterrupts();
setSignal(tDir);
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) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
}
unsigned int MotorDriver::mA2raw( unsigned int mA) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
return (int32_t)mA * senseScale / senseFactorInternal;
} }
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin); // DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
(void) type; // avoid compiler warning if diag not used above. (void) type; // avoid compiler warning if diag not used above.
#if defined(ARDUINO_ARCH_SAMD)
PortGroup *port = digitalPinToPort(pin);
#elif defined(ARDUINO_ARCH_STM32)
GPIO_TypeDef *port = digitalPinToPort(pin);
#else
uint8_t port = digitalPinToPort(pin); uint8_t port = digitalPinToPort(pin);
#endif
if (input) if (input)
result.inout = portInputRegister(port); result.inout = portInputRegister(port);
else else
@ -190,3 +457,171 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
result.maskLOW = ~result.maskHIGH; result.maskLOW = ~result.maskHIGH;
// 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);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// checkPowerOverload(useProgLimit, trackno)
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
// 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) {
switch (powerMode) {
case POWERMODE::OFF: {
lastPowerMode = POWERMODE::OFF;
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
break;
}
case POWERMODE::ON: {
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 {
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
}
setPower(POWERMODE::ALERT);
break;
}
// all well
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
}
case POWERMODE::ALERT: {
// set local flags that handle how much is output to diag (do not output duplicates)
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
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);
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 maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
// 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;
}
case POWERMODE::OVERLOAD: {
lastPowerMode = POWERMODE::OVERLOAD;
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
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;
}
default:
break;
}
}

View File

@ -1,10 +1,12 @@
/* /*
* © 2022 Paul M Antoine
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020 Chris Harlow * © 2020 Chris Harlow
* © 2022 Harald Barth
* All rights reserved. * All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,70 +24,273 @@
#ifndef MotorDriver_h #ifndef MotorDriver_h
#define MotorDriver_h #define MotorDriver_h
#include "FSH.h" #include "FSH.h"
#include "IODevice.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 setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define HAVE_PORTA(X) X
#define HAVE_PORTB(X) X
#define HAVE_PORTC(X) X
#endif
#if defined(ARDUINO_AVR_UNO)
#define HAVE_PORTB(X) X
#endif
#if defined(ARDUINO_ARCH_SAMD)
#define PORTA REG_PORT_OUT0
#define HAVE_PORTA(X) X
#define PORTB REG_PORT_OUT1
#define HAVE_PORTB(X) X
#endif
#if defined(ARDUINO_ARCH_STM32)
#define PORTA GPIOA->ODR
#define HAVE_PORTA(X) X
#define PORTB GPIOB->ODR
#define HAVE_PORTB(X) X
#define PORTC GPIOC->ODR
#define HAVE_PORTC(X) X
#endif
// if macros not defined as pass-through we define
// them here as someting that is valid as a
// statement and evaluates to false.
#ifndef HAVE_PORTA
#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTB
#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTC
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
// 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
#define MAX_PIN 254
class pinpair {
public:
pinpair(byte p1, byte p2) {
pin = p1;
invpin = p2;
};
byte pin = UNUSED_PIN;
byte invpin = UNUSED_PIN;
};
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef uint32_t portreg_t;
#else
typedef uint8_t portreg_t;
#endif #endif
#if defined(__IMXRT1062__)
struct FASTPIN { struct FASTPIN {
volatile uint32_t *inout; volatile portreg_t *inout;
uint32_t maskHIGH; portreg_t maskHIGH;
uint32_t maskLOW; portreg_t maskLOW;
volatile portreg_t *shadowinout;
}; };
#else // The port registers that are shadowing
struct FASTPIN { // the real port registers. These are
volatile uint8_t *inout; // defined in Motordriver.cpp
uint8_t maskHIGH; extern volatile portreg_t shadowPORTA;
uint8_t maskLOW; extern volatile portreg_t shadowPORTB;
}; extern volatile portreg_t shadowPORTC;
#endif
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
class MotorDriver { class MotorDriver {
public: public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
virtual void setPower( bool on); byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
virtual void setSignal( bool high); void setPower( POWERMODE mode);
virtual void setBrake( bool on); POWERMODE getPower() { return powerMode;}
virtual int getCurrentRaw(); // as the port registers can be shadowed to get syncronized DCC signals
virtual unsigned int raw2mA( int raw); // we need to take care of that and we have to turn off interrupts if
virtual int mA2raw( unsigned int mA); // we setSignal() or setBrake() or setPower() during that time as
// otherwise the call from interrupt context can undo whatever we do
// from outside interrupt
void setBrake( bool on, bool interruptContext=false);
__attribute__((always_inline)) inline void setSignal( bool high) {
if (trackPWM) {
DCCTimer::setPWM(signalPin,high);
}
else {
if (high) {
setHIGH(fastSignalPin);
if (dualSignal) setLOW(fastSignalPin2);
}
else {
setLOW(fastSignalPin);
if (dualSignal) setHIGH(fastSignalPin2);
}
}
};
inline void enableSignal(bool on) {
if (on)
pinMode(signalPin, OUTPUT);
else
pinMode(signalPin, INPUT);
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
void throttleInrush(bool on);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
#elif defined(ARDUINO_ARCH_ESP32)
ledcDetachPin(brakePin);
#else
setDCSignal(128);
#endif
};
int getCurrentRaw(bool fromISR=false);
unsigned int raw2mA( int raw);
unsigned int mA2raw( unsigned int mA);
inline bool brakeCanPWM() {
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
return true;
#else
#ifdef digitalPinToTimer
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
#else
return (brakePin<14 && brakePin >1);
#endif //digitalPinToTimer
#endif //ESP32/ARM
}
inline int getRawCurrentTripValue() { inline int getRawCurrentTripValue() {
return rawCurrentTripValue; return rawCurrentTripValue;
} }
bool isPWMCapable(); bool isPWMCapable();
bool canMeasureCurrent(); bool canMeasureCurrent();
static bool usePWM; 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;
} }
private: inline void makeProgTrack(bool on) { // let this output know it's a prog track.
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); isProgTrack = on;
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result);
} }
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin; void checkPowerOverload(bool useProgLimit, byte trackno);
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; inline void setTrackLetter(char 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
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
};
inline TRACK_MODE getMode() {
return trackMode;
};
private:
char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
inline void getFastPin(const FSH* type,int pin, FASTPIN & 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;
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
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
float senseFactor; 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.
// Milliamps=rawADCreading * sensefactorInternal / senseScale
//
// senseScale is chosen as 256 to give enough scale for 2 decimal place
// raw->mA conversion with an ultra fast optimised integer multiplication
int senseFactorInternal; // set to senseFactor * senseScale
static const int senseScale=256;
int senseOffset; int senseOffset;
unsigned int tripMilliamps; unsigned int tripMilliamps;
int rawCurrentTripValue; int rawCurrentTripValue;
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) // current sampling
static bool disableInterrupts() { POWERMODE powerMode;
uint32_t primask; POWERMODE lastPowerMode;
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::); unsigned long lastPowerChange[4]; // timestamp in microseconds
__disable_irq(); unsigned long lastBadSample; // timestamp in microseconds
return (primask == 0) ? true : false; // used to sync restore time when common Fault pin detected
} static unsigned long globalOverloadStart; // timestamp in microseconds
static void enableInterrupts(bool doit) { int progTripValue;
if (doit) __enable_irq(); int lastCurrent; //temp value
} int tripValue; //temp value
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
#endif #endif
int maxmA;
int tripmA;
// Times for overload management. Unit: microseconds.
// Base for wait time until power is turned on again
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
// 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
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
}; };
#endif #endif

View File

@ -1,6 +1,7 @@
/* /*
* © 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.
@ -35,26 +36,78 @@
// 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:
//
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
// float senseFactor, unsigned int tripMilliamps, byte faultPin); // float senseFactor, unsigned int tripMilliamps, byte faultPin);
// //
// If the brakePin is negative that means the sense // power_pin: Turns the board on/off. Often called ENABLE or PWM on the shield
// signal_pin: Where the DCC signal goes in. Often called DIR on the shield
// signal_pin2: Inverse of signal_pin. A few shields need this as well, can be replace by hardware inverter
// brake_pin: When tuned on, brake is set - output shortened (*)
// current_pin: Current sense voltage pin from shield to ADC
// senseFactor: Relation between volts on current_pin and actual output current
// tripMilliamps: Short circuit trip limit in milliampere, max 32767 (32.767A)
// faultPin: Some shields have a pin to to report a fault condition to the uCPU. High when fault occurs
//
// (*) If the brake_pin is negative that means the sense
// of the brake pin on the motor bridge is inverted // of the brake pin on the motor bridge is inverted
// (HIGH == release brake) // (HIGH == release brake)
//
// Arduino standard Motor Shield
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// DCC-EX TI DRV8874 based motor shield // Arduino STANDARD Motor Shield, used on different architectures:
// This motor shield has reverse sense fault pins thus the -A4 and -A5 pin values.
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
// 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
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
// to tweak both these
#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 SAMD_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"), \ #define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, A4), \ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, A5) new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5)
#elif defined(ARDUINO_ARCH_ESP32)
// 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
// analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
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)
// 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
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN)
#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(-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
// Pololu Motor Shield // Pololu Motor Shield
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
@ -71,6 +124,17 @@
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12) // new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet. // See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
// Pololu Dual TB9051FTG Motor Shield
// This is the shield without modifications. Unfortunately the TB9051FTG driver chip on
// the shield makes short delays when direction is switched. That means that the chip
// can NOT provide a standard conformant DCC signal independent how hard we try. If your
// Decoders tolerate that signal, use it by all mean but it is not recommended. Without
// modifications it uses the following pins below which means no HA waveform and no
// RailCom on an Arduino Mega 2560 but the DCC signal is broken anyway.
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
// Firebox Mk1 // Firebox Mk1
#define FIREBOX_MK1 F("FIREBOX_MK1"), \ #define FIREBOX_MK1 F("FIREBOX_MK1"), \
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
@ -83,17 +147,17 @@
// FunduMoto Motor Shield // FunduMoto Motor Shield
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ #define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(10, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog // IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \ #define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \ new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// YFROBOT Motor Shield (V3.1) // YFROBOT Motor Shield (V3.1)
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \ #define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// Makeblock ORION UNO like sized board with integrated motor driver // Makeblock ORION UNO like sized board with integrated motor driver
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows. // This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
@ -110,7 +174,34 @@
// to an NANO EVERY board. You have to make the connectons from the shield to the board // to an NANO EVERY board. You have to make the connectons from the shield to the board
// as in this example or adjust the values yourself. // as in this example or adjust the values yourself.
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \ #define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\ new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// This is an example how to stack two standard motor shields. The upper shield
// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and
// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5
// Pin assigment table:
// 2 Enable C jumpered
// 3 Enable A direct
// 4 Dir D jumpered
// 5 Enable D jumpered
// 6 Brake D jumpered
// 7 Brake C jumpered
// 8 Brake B direct
// 9 Brake A direct
// 10 Dir C jumpered
// 11 Enable B direct
// 12 Dir A direct
// 13 Dir B direct
// A0 Sense A direct
// A1 Sense B direct
// A4 Sense C jumpered
// A5 Sense D jumpered
//
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
//
#endif #endif

View File

@ -485,10 +485,10 @@
<text x="32.86" y="598.05" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Accessories <v:newlineChar/><tspan <text x="32.86" y="598.05" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Accessories <v:newlineChar/><tspan
x="30.42" dy="1.2em" class="st5">(</tspan>Output.cpp)</text> </g> x="30.42" dy="1.2em" class="st5">(</tspan>Output.cpp)</text> </g>
</a> </a>
<a xlink:href="https://github.com/DCC-EX/CommandStation-EX/blob/master/LCDDisplay.cpp"> <a xlink:href="https://github.com/DCC-EX/CommandStation-EX/blob/master/Display.cpp">
<g id="shape14-81" v:mID="14" v:groupContext="shape" v:layerMember="0" transform="translate(288,-116.156)"> <g id="shape14-81" v:mID="14" v:groupContext="shape" v:layerMember="0" transform="translate(288,-116.156)">
<title>Process.14</title> <title>Process.14</title>
<desc>Other Utilities (LCDDisplay.cpp)</desc> <desc>Other Utilities (Display.cpp)</desc>
<v:custProps> <v:custProps>
<v:cp v:nameU="Cost" v:lbl="Cost" v:prompt="" v:type="7" v:format="@" v:sortKey="" v:invis="false" <v:cp v:nameU="Cost" v:lbl="Cost" v:prompt="" v:type="7" v:format="@" v:sortKey="" v:invis="false"
v:ask="false" v:langID="1033" v:cal="0"/> v:ask="false" v:langID="1033" v:cal="0"/>
@ -522,7 +522,7 @@
</g> </g>
<rect x="0" y="580.5" width="90" height="31.5" rx="13.5" ry="13.5" class="st3"/> <rect x="0" y="580.5" width="90" height="31.5" rx="13.5" ry="13.5" class="st3"/>
<text x="19.29" y="593.55" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Other Utilities<v:newlineChar/><tspan <text x="19.29" y="593.55" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Other Utilities<v:newlineChar/><tspan
x="14.29" dy="1.2em" class="st5">(</tspan>LCDDisplay.cpp)</text> </g> x="14.29" dy="1.2em" class="st5">(</tspan>Display.cpp)</text> </g>
</a> </a>
<g id="shape3-88" v:mID="3" v:groupContext="shape" v:layerMember="1" transform="translate(108,-443.812)"> <g id="shape3-88" v:mID="3" v:groupContext="shape" v:layerMember="1" transform="translate(108,-443.812)">
<title>Dynamic connector</title> <title>Dynamic connector</title>

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

197
Release_Notes/CommandRef.md Normal file
View File

@ -0,0 +1,197 @@
This file is being used to consolidate the command reference information.
General points:
- Commands below have a single character opcode and parameters.
Even <JA> is actually read as <J A>
- Keyword parameters are shown in upper case but may be entered in mixed case.
- value parameters are decimal numeric (unless otherwise noted)
- [something] indicates its optional.
- Not all commands have a response, and broadcasts mean that not all responses come from the last commands that you have issued.
Startup status
<s> Return status like
<iDCC-EX V-4.2.22 / MEGA / STANDARD_MOTOR_SHIELD G-devel-202302281422Z>
also returns defined turnout list:
<H id 1|0> 1=thrown
Track power management. After power commands a power state is broadcast to all throttles.
<1> Power on all
<1 MAIN|PROG|JOIN> Power on MAIN or PROG track
<1 JOIN> Power on MAIN and PROG track but send main track data on both.
<0> Power off all tracks
<0 MAIN|PROG> Power off main or prog track
Basic manual loco control
<t locoid speed direction> Throttle loco.
speed in JMRI-form (-1=ESTOP, 0=STOP, 1..126 = DCC speeds 2..127)
direction 1=forward, 0=reverse
For response see broadcast <l>
<F locoid function 1|0> Set loco function 1=ON, 0-OFF
For response see broadcast <l>
<!> emergency stop all locos
<T id 0|1|T|C> Control turnout id, 0=C=Closed, 1=T=Thrown
response broadcast <H id 0|1>
DCC accessory control
<a address subaddress activate [onoff]>
<a linearaddress activate>
Turnout definition
Note: Turnouts are best defined in myAutomation.h where a turnout description can also be provided ( refer to EXRAIL documentation) or by using these commands in a mySetup.h file.
<T id SERVO vpin thrown closed profile>
<T id VPIN vpin>
<T id DCC addr subaddr>
<T id DCC linearaddr>
Valid commands respond with <O>
Direct pin manipulation (replaces <Z commands, no predefinition required)
<z vpin> Set pin HIGH
<z -vpin> Set pin LOW
<z vpin value> Set pin analog value
<z vpin value profile> Set pin analog with profile
<z vpin value profile duration> set pin analog with profile and value
Sensors (Used by JMRI, not required by EXRAIL)
<S id vpin pullup> define a sensor to be monitored.
Responses <Q id> and <q id> as sensor changes
Decoder programming - main track
<w cab cv value> POM write value to cv on loco
<b cab cv bit value> POM write bit to cv on loco
Decoder Programming - prog track
<W cabid> Clear consist and write new cab id (includes long/short settings)
Responds <W cabid> or <W -1> for error
<W cv value> Write value to cv
<V cv predictedValue> Read cv value, much faster if prediction is correct.
<V cv bit predictedValue> Read CV bit
<R> Read drive-away loco id. (May be a consist id)
<D ACK ON|OFF>
<D ACK LIMIT|MIN|MAX|RETRY value>
<D PROGBOOST>
Advanced DCC control
<M packet.... >
<P packet ...>
<f map1 map2 [map3]>
<#>
<->
<- cabid>
<D CABS>
<D SPEED28>
<D SPEED128>
EEPROM commands
These commands exist for
backwards JMRI compatibility.
You are strongly discouraged from maintaining your configuration settings in EEPROM.
<E>
<e>
<D EEPROM>
<T>
<T id>
<S>
<S id>
<Z>
<Z id>
Diagnostic commands
<D CMD ON|OFF>
<D WIFI ON|OFF>
<D ETHERNET ON|OFF>
<D WIT ON|OFF>
<D LCN ON|OFF>
<D EXRAIL ON|OFF>
<D RESET>
<D SERVO|ANOUT vpin position [profile]>
<D ANIN vpin>
<D HAL SHOW>
<D HAL RESET>
<+ cmd>
<+>
<Q>
User defined filter commands
<U ....>
<u ....>
Track Management
<=>
<= track DCC|PROG|OFF>
<= track DC|DCX cabid>
<JG>
<JI>
Turntable interface
<D TT vpin steps [activity]>
Fast clock interface
<JC>
<JC mins rate>
Advanced Throttle access to features
<t cab>
<JA>
<JA id>
<JR>
<JR id>
<JT>
<JT id>
*******************
EXRAIL Commands
*******************
</>
</PAUSE>
</RESUME>
</START cab sequence>
</START sequence>
</KILL taskid>
</KILL ALL>
</RESERVE|FREE blockid>
</LATCH|UNLATCH latchid>
</RED|AMBER|GREEN signalid>
Obsolete commands/formats
<c>
<t ignored cab speed direction>
<T id vpin thrown closed>
<T id addr subaddr>
<B cv bit value obsolete obsolete>
<R cv obsolete obsolete>
<W cv value obsolete obsolete>
<R cv> V command is much faster if prediction is correct.
<B cv bit value> V command is much faster if prediction is correct.
<Z id vpin active> (use <z) Define an output pin that JMRI can set by id
<Z id activate> (use <z) Activate an output pin by id
Broadcast responses
Note: broadcasts are sent to all throttles when appropriate (usually because something has changed)
<p0>
<p1>
<p1 MAIN|PROG|JOIN>
<l cab slot dccspeed functionmap>
<H id 1|0>
<jC mmmm speed>
Diagnostic responses
These are not meant to be software readable. They contain diagnostic information for programmers to identify issues.
<X>
<* ... *>

View File

@ -5,72 +5,70 @@ Chris Harlow April 2022
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations. There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
# Turnouts Turnouts:
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons. The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons.
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. ```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
e.g. response ```<jT 1 17 22 19>``` e.g. response ```<jT 1 17 22 19>```
```<JT 17>``` requests info on turnout 17\ ```<JT 17>`` requests info on turnout 17.
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```\ e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```
(T=thrown, C=closed)\ (T=thrown, C=closed)
or ```<jT 17 C "">``` indicating turnout description not given.\ or ```<jT 17 C "">``` indicating turnout description not given.
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.) or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
Note: It is still the throttles responsibility to monitor the status broadcasts.\ Note: It is still the throttles responsibility to monitor the status broadcasts.
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)\ (TBD I'm thinking that the existing broadcast is messy and needs cleaning up)
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
# Automations/Routes
Automations/Routes
A throttle need to know which EXRAIL Automations and Routes it can show the user. A throttle need to know which EXRAIL Automations and Routes it can show the user.
```<JA>``` Returns a list of Automations/Routes\ ```<JA>``` Returns a list of Automations/Routes
e.g. ```<jA 13 16 23>```\ e.g. ```<jA 13 16 23>```
Indicates route/automation ids.\ Indicates route/automation ids.
Information on each route needs to be obtained by\ Information on each route needs to be obtained by
```<JA 13>```\ ```<JA 13>```
returns e.g. ```<jA 13 R "description">``` for a route\ returns e.g. ```<jA 13 R "description">``` for a route
or ```<jA 13 A "description">``` for an automation.\ or ```<jA 13 A "description">``` for an automation.
or ```<jA 13 X>``` for id not found or ```<jA 13 X>``` for id not found
## What's the difference? Whats the difference:
A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
Thus a route can be triggered by sending in for example ```</START 13>```.
A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
Thus an Automation expects a start command with a cab id
Thus, a route can be triggered by sending in for example ```</START 13>```.
An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
Thus an Automation expects a start command with a cab id\
e.g. ```</START 13 3>``` e.g. ```</START 13 3>```
### Roster Information
The ```<JR>``` command requests a list of cab ids from the roster\ Roster Information:
e.g. responding ```<jR 3 200 6336>```\ The ```<JR>``` command requests a list of cab ids from the roster.
or <jR> for none. e.g. responding ```<jR 3 200 6336>```
or <jR> for none.
Each Roster entry had a name and function map obtained by:\ Each Roster entry had a name and function map obtained by:
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic"> ```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
Refer to EXRAIL ROSTER command for function map format. Refer to EXRAIL ROSTER command for function map format.
### Obtaining throttle status
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast\ Obtaining throttle status.
```<l cabid slot speedbyte functionMap>``` ```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast.
```<l cabid slot speedbyte functionMap>```
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
# COMMANDS TO AVOID COMMANDS TO AVOID
```<f cab func1 func2>``` Instead Use ```<F cab function 1/0>```\ ```<f cab func1 func2>``` Use ```<F cab function 1/0>```
```<t slot cab speed dir>``` Just drop the slot number\ ```<t slot cab speed dir>``` Just drop the slot number
```<T commands>``` other than ```<T id 0/1>```\ ```<T commands>``` other than ```<T id 0/1>```
```<s>```\ ```<s>```
```<c>``` ```<c>```

View File

@ -0,0 +1,139 @@
# DCC++EX Track Manager
Chris Harlow 2022/03/23
**If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.**
What follows is for advanced users interested in managing power districts and/or running DC locomotives through DCC++EX.
## What is the Track Manager
Track Manger (TM from now on) is an integral part of DCC++EX software that is responsible for:
- Managing track power state.
- Monitoring track overloads and shorts.
- Routing the DCC main or prog track waveforms to the correct Motor Driver and thus track.
- Managing the JOIN feature.
- Intercepting throttle commands to locos running on DC tracks.
- Handling user or EXRAIL commands to switch track status.
In the default scenario of a single DCC track and a PROG track, the TM behaves as for the previous versions of DCC++EX so if thats what you want, you dont need to mess with it.
The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog.
Unlike the previous version of DCC++EX, where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows :
- None, any or all the tracks can be DCC Main.
- None or ONE track may be DCC prog at any given time.
- Any track may be powered on or off independently of the others.
- Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later)
With such flexibility comes responsibility... the potential for making mistakes means taking extra care with your configuration!
**NOTE** TM does NOT use "zero stretching" to control your DC motor. Instead, it uses true Pulse Width Modulation (PWM) to efficiently run your loco using the same method a decoder uses to control a DCC loco's motor. DC locos can even run better on TM than they can on a normal analog throttle, especially at low speed, since it is always applying the full track voltage, albeit in pulses of varying duration.
## Using the Track Manager (DCC)
TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog.
There is a new user command `<=>` which is used to control the TM but the `<0>` and `<1>` commands operate as before.
- `<=>` lists the current track settings.
In a default setup this will normally return
```
<=A DCC>
<=B PROG>
```
- `<=t DCC>` sets track t (A..H) to use the DCC main track. For example `<=C DCC>` sets track C. All tracks that are set to DCC will receive the same DCC signal waveform.
- `<=t PROG>` Sets track t (A..H) to be the one and only PROG track. Any previous PROG track is turned off.
- `<=t OFF>` turns off the track t. It will not power on with `<1>` because it will not know what signal to send.
In an all-DCC environment it is unlikely that you will need to do anything other than setting any additional tracks (C...H) as DCC in your `mySetup.h` file.
Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice.
Note that when setting a track to PROG or OFF, its power is switched off automatically. (The PROG track manages power on an as-needed basis under normal circumstances.
When setting a track to MAIN (or DC, DCX see later) the power is applied according to the most recent `<1>` or `<0>` command as being the most compatible with previous versions.
## using the Track Manager (DC)
TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC.
Your throttle (JMRI, EX-Webthrottle, Withrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change.
For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address.
The command to set a track to a DC address is as follows
- `<=t DC a>` Sets track t (A..H) to use loco address a. e.g. <=A DC 3>
A simple 2 separate loop DC track, wired the traditional way in opposite directions, may be set like this to use loco addresses 1 and 2.
```
<=A DC 1>
<=A DC 2>
```
### Crossing between DC tracks
There are some slightly mind-bending issues to be addressed, especially if you want to be able to cross between two separate DC tracks or use your layout in DCC or DC mode. This is because the control of DC loco direction is relative to the TRACK and not the LOCO. (you turn a DC loco round on the track and it continues in the same geographical direction. You turn a DCC loco around and it continues to go forwards or backwards in the opposite geographical direction.)
Generally DC tracks are wired so that two mainline tracks are in opposite direction which makes operation easy BUT crossovers between tracks will cause shorts unless you have very complex switching arrangements.
This is generally incompatible with DCC wiring which expects to be able to cross between tracks with impunity because they are all wired with the same polarity.
To get over this issue TM allows the polarity of a DC track to be swapped so that tracks wired for DCC may be switched to DC with a polarity chosen at run time according to your operations. So, for example, you may have two loops with a crossing between them. Normally you need them in opposite directions, but when you need to drive over the crossing, you need to switch one or other track so that they are at the same polarity.
(This is a good case for using EXRAIL to help)
The command `<=t DCX a>` will set track t (A..H) to be DC but with reversed polarity compared with a track set to DC.
Its perfectly OK to cross between DC tracks by setting them to the same loco address and making sure you get the polarity right!
## Connecting Hardware
Each track requires hardware to control it
- Power on/off
- Polarity (direction, signal etc)
- Brake (shorts tracks together)
- Current (analog reading)
The standard motor shields provide this for two separate tracks and are predictable and easy to use. However STACKING shields is not a viable way of adding more tracks because it prevents the software from gaining access to the individual track pins. Similarly, wiring all the signal pins together for example, will give you a shared DCC signal but it will eliminate any possibility of switching the track purpose at run time. So, you are going to have to understand enough to wire track drivers to various pins if you wish to extend beyond 2 tracks and take advantage of TM.
You will also need to consider the implications of differing electronic implementations that would cause unexpected issues when a loco moves between tracks. We know this works fine for a typical shield because we use `<1 JOIN>` quite happily but this may be different if you mix hardware types..... (NOT MY PROBLEM !)
The easiest way to consider the wiring is to treat each track individually (either as a separate driver or as half of a shield).
You will require,for each track, on the Arduino:
- A GPIO pin (or a HAL vpin perhaps on an I2C extender, code TBA!!!) to switch power.
- A GPIO pin to switch the signal direction
- A GPIO pin with PWM capability to switch the Brake (you may omit this if you dont want any DC capability)
- Optionally An Analog pin to read the current (unless your hardware cant do that, perhaps its just feeding a booster)
- Optionally a GPIO fault pin if thats how your hardware works. (NOT recommended as you're going to run out of pins)
IF you have no more than 3 tracks and you can arrange for the signal pins to be one of 11,12,13 on a Mega, THEN there is a slight advantage internally and the waveform will be super-sharp.
**Hardware that has two signal pins still needs some code thought!!!!!!!!**
## Configuring the Software
Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h
Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1)
## Using EXRAIL to control Track Manager
EXRAIL has a single additional command that can be used to automate TM.
- `SET_TRACK(t,mode)`
where t is the track letter A..H and mode is one of
- `OFF` track is switched off
- `DCC` track gets DCC signal
- `PROG` track gets DCC prog signal
- `DC` track is set to DC mode with the cab address of the currently executing EXRAIL sequence.
- `DCX` as DC but with reversed polarity.
DC/DCX are designed so that you can be automating a DCC loco, drive it onto a separate track and switch to DC without having to know the cab address. (e.g AUTOMATION)
If however you are just running a ROUTE you can always do something like this:
```
ROUTE(77,"Set track G to DC 123")
SETLOCO(123)
SET_TRACK(G,DC)
DONE
```
## Where and How for the Code.
The TM code is primarily in TrackManager.cpp which is responsible for coordinating the track settings and commands.
Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h
Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow.

View File

@ -0,0 +1,39 @@
Using Lew's Duino Gear boards:
1. DNIN8 Input
This is a shift-register implementation of a digital input collector.
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
Use in myAnimation.h
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
DNOU8 works the same way,
Use in myAnimation.h
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.

View File

@ -1,346 +0,0 @@
Version 4.1.1 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public.
This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes.
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)
**New Command Station & EX-RAIL Features**
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
- Automatically detect and run a myFilter add-on (no need to call setFilter)
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
- </RED signal_id> to turn a individual LED Signal On & Off
- </AMBER signal_id> "
- </GREEN signal_id> "
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
- < t cab> command to obtain current throttle setting
- Allow WRITE CV on PROG <W CV VALUE>
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
- Allow WRITE CV on PROG <W CV VALUE)
- Change callback parameters are now optional on PROG
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
- VIRTUAL_TURNOUT definition
**EX-RAIL Updates**
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
** Other Enhancements**
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
- Position servo can use spare servo pin as a GPIO pin.
**4.1.1 Bug Fixes**
- Preserve the turnout format
- Parse multiple commands in one buffer string currectly
- Fix </> command signal status in EX-RAIL
- Read long loco addresses in EX-RAIL
- FIX negative route IDs in WIthrottle
See the version.h file for notes about which of the 4.1.1 features were added/changed by point release.
**Known Issues**
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
- Increased use of display for showing loco decoder programming information.
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
**Summary of the key new features added to CommandStation-EX V3.0.15**
- Send function commands just once instead of repeating them 4 times
**Summary of the key new features added to CommandStation-EX V3.0.14**
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
**Summary of the key new features added to CommandStation-EX V3.0.13**
- Fix for CAB Functions greater than 127
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
- Major update for efficiencies in displays (LCD, OLED)
- Add I2C Support functions
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Add Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for automation in future version
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**EX-CommandStation Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**Engine Driver and JMRI Interface**
- M Steve Todd
**EX-Installer Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
- Peter Akers - Brisbane, QLD, Australia (flash62au)
**EX-WebThrottle**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Hardware / Electronics**
- Harald Barth - Stockholm, Sweden (Haba)
- Paul Antoine, Western Australia
- Neil McKechnie - Worcestershire, UK
- Fred Decker - Holly Springs NC, USA
- Herb Morton - Kingwood Texas, USA (Ash++)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)

View File

@ -1,349 +0,0 @@
Version 4.1.2 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public.
This release is a Bugfix release which fixes support for Ethernet Shields based on the W5100 chip that broke with the release of v4.1.1. This chip does not report HW and link status the way the W5200 and W5500 do, so the check routine needed to be changed.
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
**New Command Station & EX-RAIL Features**
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
- Automatically detect and run a myFilter add-on (no need to call setFilter)
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
- </RED signal_id> to turn a individual LED Signal On & Off
- </AMBER signal_id> "
- </GREEN signal_id> "
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
- < t cab> command to obtain current throttle setting
- Allow WRITE CV on PROG <W CV VALUE>
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
- Allow WRITE CV on PROG <W CV VALUE)
- Change callback parameters are now optional on PROG
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
- VIRTUAL_TURNOUT definition
**EX-RAIL Updates**
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
** Other Enhancements**
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
- Position servo can use spare servo pin as a GPIO pin.
**4.1.2 Bug Fixes**
- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips.
**4.1.1 Bug Fixes**
- Preserve the turnout format
- Parse multiple commands in one buffer string currectly
- Fix </> command signal status in EX-RAIL
- Read long loco addresses in EX-RAIL
- FIX negative route IDs in WIthrottle
See the version.h file for notes about which of the 4.1.2 features were added/changed by point release.
**Known Issues**
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
- Increased use of display for showing loco decoder programming information.
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
**Summary of the key new features added to CommandStation-EX V3.0.15**
- Send function commands just once instead of repeating them 4 times
**Summary of the key new features added to CommandStation-EX V3.0.14**
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
**Summary of the key new features added to CommandStation-EX V3.0.13**
- Fix for CAB Functions greater than 127
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
- Major update for efficiencies in displays (LCD, OLED)
- Add I2C Support functions
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Add Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for automation in future version
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**EX-CommandStation Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**Engine Driver and JMRI Interface**
- M Steve Todd
**EX-Installer Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
- Peter Akers - Brisbane, QLD, Australia (flash62au)
**EX-WebThrottle**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Hardware / Electronics**
- Harald Barth - Stockholm, Sweden (Haba)
- Paul Antoine, Western Australia
- Neil McKechnie - Worcestershire, UK
- Fred Decker - Holly Springs NC, USA
- Herb Morton - Kingwood Texas, USA (Ash++)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)

View File

@ -18,9 +18,17 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
// NOTE: The use of a marker byte without an escape algorithm means
// RingStream is unsuitable for binary data. Should binary data need to be
// streamed it will be necessary to implementr an escape strategy to handle the
// marker char when embedded in data.
#include "RingStream.h" #include "RingStream.h"
#include "DIAG.h" #include "DIAG.h"
const byte FLASH_INSERT_MARKER=0xff;
RingStream::RingStream( const uint16_t len) RingStream::RingStream( const uint16_t len)
{ {
_len=len; _len=len;
@ -31,6 +39,7 @@ RingStream::RingStream( const uint16_t len)
_overflow=false; _overflow=false;
_mark=0; _mark=0;
_count=0; _count=0;
_flashInsert=0;
} }
size_t RingStream::write(uint8_t b) { size_t RingStream::write(uint8_t b) {
@ -46,8 +55,84 @@ size_t RingStream::write(uint8_t b) {
return 1; return 1;
} }
// Ideally, I would prefer to override the Print:print(_FlashStringHelper) function
// but the library authors omitted to make this virtual.
// Therefore we obveride the only other simple function that has no side effects
// in order that StringFormatter can recognise a RingStream and call its
// printFlash() directly.
int RingStream::availableForWrite() {
return THIS_IS_A_RINGSTREAM;
}
size_t RingStream::printFlash(const FSH * flashBuffer) {
// This function does not work on a 32 bit processor where the runtime
// sometimes misrepresents the pointer size in uintptr_t.
// In any case its not really necessary in a 32 bit processor because
// we have adequate ram.
if (sizeof(void*)>2) return print(flashBuffer);
// We are about to add a PROGMEM string to the buffer.
// To save RAM we can insert a marker and the
// progmem address into the buffer instead.
// The buffer reading code must recognise this marker and
// silently extract the progmem bytes.
// In addition, we must make the count correct as if the
// string had been embedded so that things like the wifi code
// can read the expected count before reading the buffer.
// Establish the actual length of the progmem string.
char * flash=(char *)flashBuffer;
int16_t plength=STRLEN_P(flash);
if (plength==0) return 0; // just ignore empty string
// Retain the buffer count as it will be modified by the marker+address insert
int prevCount=_count;
write(FLASH_INSERT_MARKER); // write the marker
uintptr_t iFlash=reinterpret_cast<uintptr_t>(flash); // expect size match with pointer
// write address bytes LSB first (size depends on CPU)
for (byte f=0;f<sizeof(iFlash); f++) {
write((byte) (iFlash & 0xFF));
iFlash>>=8;
}
// correct the buffer count to reflect the flash length, not the marker/addr.
_count=prevCount+plength;
return plength;
}
int RingStream::read() { int RingStream::read() {
if (_flashInsert) {
// we are reading out of a flash string
byte fb=GETFLASH(_flashInsert);
_flashInsert++;
if (fb) return fb; // we have a byte from the flash
// flash insert complete, clear and drop through to next buffer byte
_flashInsert=NULL;
}
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b;
// Detected a flash insert
if (sizeof(void*)>2) {
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
return '?';
}
// read address bytes LSB first (size depends on CPU)
uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) {
uintptr_t bf=readRawByte();
bf&=0x00ff;
bf<<= (8*f); // shift byte to correct position in iFlash
iFlash |= bf;
}
_flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert.
return read();
}
byte RingStream::readRawByte() {
byte b=_buffer[_pos_read]; byte b=_buffer[_pos_read];
_pos_read++; _pos_read++;
if (_pos_read==_len) _pos_read=0; if (_pos_read==_len) _pos_read=0;
@ -55,9 +140,8 @@ int RingStream::read() {
return b; return b;
} }
int RingStream::count() { int RingStream::count() {
return (read()<<8) | read(); return (readRawByte()<<8) | readRawByte();
} }
int RingStream::freeSpace() { int RingStream::freeSpace() {
@ -69,6 +153,8 @@ int RingStream::freeSpace() {
// mark start of message with client id (0...9) // mark start of message with client id (0...9)
void RingStream::mark(uint8_t b) { void RingStream::mark(uint8_t b) {
//DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID());
_ringClient = b;
_mark=_pos_write; _mark=_pos_write;
write(b); // client id write(b); // client id
write((uint8_t)0); // count MSB placemarker write((uint8_t)0); // count MSB placemarker
@ -79,20 +165,27 @@ void RingStream::mark(uint8_t b) {
// peekTargetMark is used by the parser stash routines to know which client // peekTargetMark is used by the parser stash routines to know which client
// to send a callback response to some time later. // to send a callback response to some time later.
uint8_t RingStream::peekTargetMark() { uint8_t RingStream::peekTargetMark() {
return _buffer[_mark]; return _ringClient;
}
void RingStream::info() {
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
} }
bool RingStream::commit() { bool RingStream::commit() {
_flashInsert=NULL; // prepared for first read
if (_overflow) { if (_overflow) {
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); //DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
// just throw it away // just throw it away
_pos_write=_mark; _pos_write=_mark;
_overflow=false; _overflow=false;
return false; // commit failed return false; // commit failed
} }
if (_count==0) { if (_count==0) {
//DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID());
// ignore empty response // ignore empty response
_pos_write=_mark; _pos_write=_mark;
_ringClient = NO_CLIENT; //XXX make else clause later
return true; // true=commit ok return true; // true=commit ok
} }
// Go back to the _mark and inject the count 1 byte later // Go back to the _mark and inject the count 1 byte later
@ -102,14 +195,14 @@ bool RingStream::commit() {
_mark++; _mark++;
if (_mark==_len) _mark=0; if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count); _buffer[_mark]=lowByte(_count);
_ringClient = NO_CLIENT;
return true; // commit worked return true; // commit worked
} }
void RingStream::flush() { void RingStream::flush() {
_pos_write=0; _pos_write=0;
_pos_read=0; _pos_read=0;
_buffer[0]=0; _buffer[0]=0;
_flashInsert=NULL; // prepared for first read
_ringClient = NO_CLIENT;
} }
void RingStream::printBuffer(Print * stream) {
_buffer[_pos_write]='\0';
stream->print((char *)_buffer);
}

View File

@ -21,22 +21,38 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include "FSH.h"
class RingStream : public Print { class RingStream : public Print {
public: public:
RingStream( const uint16_t len); RingStream( const uint16_t len);
static const int THIS_IS_A_RINGSTREAM=777;
virtual size_t write(uint8_t b); virtual size_t write(uint8_t b);
// This availableForWrite function is subverted from its original intention so that a caller
// can destinguish between a normal stream and a RingStream.
// The Arduino compiler does not support runtime dynamic cast to perform
// an instranceOf check.
// This is necessary since the Print functions are mostly not virtual so
// we cant override the print(__FlashStringHelper *) function.
virtual int availableForWrite() override;
using Print::write; using Print::write;
size_t printFlash(const FSH * flashBuffer);
int read(); int read();
int count(); int count();
int freeSpace(); int freeSpace();
void mark(uint8_t b); void mark(uint8_t b);
bool commit(); bool commit();
uint8_t peekTargetMark(); uint8_t peekTargetMark();
void printBuffer(Print * streamer);
void flush(); void flush();
void info();
byte readRawByte();
inline int peek() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
return _buffer[_pos_read];
};
static const byte NO_CLIENT=255;
private: private:
int _len; int _len;
int _pos_write; int _pos_write;
@ -45,6 +61,8 @@ class RingStream : public Print {
int _mark; int _mark;
int _count; int _count;
byte * _buffer; byte * _buffer;
char * _flashInsert;
byte _ringClient = NO_CLIENT;
}; };
#endif #endif

View File

@ -143,56 +143,69 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
// SSD1306AsciiWire Method Definitions // SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constructor // Auto-detect address
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { SSD1306AsciiWire::SSD1306AsciiWire(int width, int height)
: SSD1306AsciiWire(0, width, height) { }
// Constructor with explicit address
SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) {
m_i2cAddr = address;
m_displayWidth = width; m_displayWidth = width;
m_displayHeight = height; m_displayHeight = height;
// Set size in characters in base class // Set size in characters
lcdRows = height / 8; m_charsPerColumn = m_displayHeight / fontHeight;
lcdCols = width / 6; m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up
}
bool SSD1306AsciiWire::begin() {
I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speede
if (m_i2cAddr == 0) {
// Probe for I2C device on 0x3c and 0x3d.
for (uint8_t address = 0x3c; address <= 0x3d; address++) {
if (I2CManager.exists(address)) {
m_i2cAddr = address;
break;
}
}
if (m_i2cAddr == 0)
DIAG(F("OLED display not found"));
}
m_col = 0; m_col = 0;
m_row = 0; m_row = 0;
m_colOffset = 0; m_colOffset = 0;
I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speed
for (byte address = 0x3c; address <= 0x3d; address++) {
if (I2CManager.exists(address)) {
m_i2cAddr = address;
if (m_displayWidth==132 && m_displayHeight==64) { if (m_displayWidth==132 && m_displayHeight==64) {
// SH1106 display. This uses 128x64 centered within a 132x64 OLED. // SH1106 display. This uses 128x64 centered within a 132x64 OLED.
m_colOffset = 2; m_colOffset = 2;
I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init)); I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init));
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
// SSD1306 128x64 or 128x32 // SSD1306 or SSD1309 128x64 or 128x32
I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
if (m_displayHeight == 32) if (m_displayHeight == 32)
I2CManager.write(address, 5, 0, // Set command mode I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
} else { } else {
DIAG(F("OLED configuration option not recognised")); DIAG(F("OLED configuration option not recognised"));
return; return false;
} }
// Device found // Device found
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address); DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString());
// Set singleton address return true;
lcdDisplay = this;
clear();
return;
}
}
DIAG(F("OLED display not found"));
} }
/* Clear screen by writing blank pixels. */ /* Clear screen by writing blank pixels. */
void SSD1306AsciiWire::clearNative() { void SSD1306AsciiWire::clearNative() {
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire const int maxBytes = sizeof(blankPixels) - 1; // max number of pixel columns (bytes) per transmission
for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) {
setRowNative(r); // Position at start of row to be erased setRowNative(r); // Position at start of row to be erased
for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) {
uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; uint8_t len = m_displayWidth-c; // Number of pixel columns remaining
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns if (len > maxBytes) len = maxBytes;
I2CManager.write_P(m_i2cAddr, blankPixels, len+1); // Write command + 'len' blank columns
} }
} }
} }
@ -223,11 +236,6 @@ void SSD1306AsciiWire::setRowNative(uint8_t line) {
size_t SSD1306AsciiWire::writeNative(uint8_t ch) { size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
const uint8_t* base = m_font; const uint8_t* base = m_font;
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0;
// Check if character would be partly or wholly off the display
if (m_col + fontWidth > m_displayWidth)
return 0;
#if defined(NOLOWERCASE) #if defined(NOLOWERCASE)
// Adjust if lowercase is missing // Adjust if lowercase is missing
if (ch >= 'a') { if (ch >= 'a') {
@ -237,6 +245,12 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
ch -= 26; // Allow for missing lowercase letters ch -= 26; // Allow for missing lowercase letters
} }
#endif #endif
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0;
// Check if character would be partly or wholly off the display
if (m_col + fontWidth > m_displayWidth)
return 0;
ch -= m_fontFirstChar; ch -= m_fontFirstChar;
base += fontWidth * ch; base += fontWidth * ch;
// Before using buffer, wait for last request to complete // Before using buffer, wait for last request to complete
@ -245,127 +259,261 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
outputBuffer[0] = 0x40; // set SSD1306 controller to data mode outputBuffer[0] = 0x40; // set SSD1306 controller to data mode
uint8_t bufferPos = 1; uint8_t bufferPos = 1;
// Copy character pixel columns // Copy character pixel columns
for (uint8_t i = 0; i < fontWidth; i++) for (uint8_t i = 0; i < fontWidth; i++) {
if (m_col++ < m_displayWidth)
outputBuffer[bufferPos++] = GETFLASH(base++); outputBuffer[bufferPos++] = GETFLASH(base++);
// Add blank pixels between letters }
for (uint8_t i = 0; i < letterSpacing; i++)
outputBuffer[bufferPos++] = 0;
// Write the data to I2C display // Write the data to I2C display
I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock); I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock);
m_col += fontWidth + letterSpacing;
return 1; return 1;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Font characters, 5x7 pixels, 0x61 characters starting at 0x20. // Font characters, 6x8 pixels, starting at 0x20.
// Lower case characters optionally omitted. // Lower case characters optionally omitted.
const uint8_t FLASH SSD1306AsciiWire::System5x7[] = { const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
// Fixed width; char width table not used !!!! // Fixed width; char width table not used !!!!
// or with lowercase character omitted.
// font data // font data
0x00, 0x00, 0x00, 0x00, 0x00, // (space) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) (20)
0x00, 0x00, 0x5F, 0x00, 0x00, // ! 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, // ! (21)
0x00, 0x07, 0x00, 0x07, 0x00, // " 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, // "
0x14, 0x7F, 0x14, 0x7F, 0x14, // # 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, // #
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, // $
0x23, 0x13, 0x08, 0x64, 0x62, // % 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, // %
0x36, 0x49, 0x55, 0x22, 0x50, // & 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, // &
0x00, 0x05, 0x03, 0x00, 0x00, // ' 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // '
0x00, 0x1C, 0x22, 0x41, 0x00, // ( 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, // (
0x00, 0x41, 0x22, 0x1C, 0x00, // ) 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, // )
0x08, 0x2A, 0x1C, 0x2A, 0x08, // * 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, // *
0x08, 0x08, 0x3E, 0x08, 0x08, // + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // +
0x00, 0x50, 0x30, 0x00, 0x00, // , 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, // ,
0x08, 0x08, 0x08, 0x08, 0x08, // - 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // -
0x00, 0x60, 0x60, 0x00, 0x00, // . 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // .
0x20, 0x10, 0x08, 0x04, 0x02, // / 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // / (47)
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 0 (48)
0x00, 0x42, 0x7F, 0x40, 0x00, // 1 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 1
0x42, 0x61, 0x51, 0x49, 0x46, // 2 0x42, 0x61, 0x51, 0x49, 0x46, 0x00, // 2
0x21, 0x41, 0x45, 0x4B, 0x31, // 3 0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, // 3
0x18, 0x14, 0x12, 0x7F, 0x10, // 4 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, // 4
0x27, 0x45, 0x45, 0x45, 0x39, // 5 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, // 5
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, // 6
0x01, 0x71, 0x09, 0x05, 0x03, // 7 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, // 7
0x36, 0x49, 0x49, 0x49, 0x36, // 8 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, // 8
0x06, 0x49, 0x49, 0x29, 0x1E, // 9 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, // 9 (57)
0x00, 0x36, 0x36, 0x00, 0x00, // : 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, // :
0x00, 0x56, 0x36, 0x00, 0x00, // ; 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ;
0x00, 0x08, 0x14, 0x22, 0x41, // < 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // <
0x14, 0x14, 0x14, 0x14, 0x14, // = 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // =
0x41, 0x22, 0x14, 0x08, 0x00, // > 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, // >
0x02, 0x01, 0x51, 0x09, 0x06, // ? 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ?
0x32, 0x49, 0x79, 0x41, 0x3E, // @ 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, // @ (64)
0x7E, 0x11, 0x11, 0x11, 0x7E, // A 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, // A (65)
0x7F, 0x49, 0x49, 0x49, 0x36, // B 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B
0x3E, 0x41, 0x41, 0x41, 0x22, // C 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, // C
0x7F, 0x41, 0x41, 0x22, 0x1C, // D 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D
0x7F, 0x49, 0x49, 0x49, 0x41, // E 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, // E
0x7F, 0x09, 0x09, 0x01, 0x01, // F 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, // F
0x3E, 0x41, 0x41, 0x51, 0x32, // G 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, // G
0x7F, 0x08, 0x08, 0x08, 0x7F, // H 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H
0x00, 0x41, 0x7F, 0x41, 0x00, // I 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I
0x20, 0x40, 0x41, 0x3F, 0x01, // J 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J
0x7F, 0x08, 0x14, 0x22, 0x41, // K 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K
0x7F, 0x40, 0x40, 0x40, 0x40, // L 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L
0x7F, 0x02, 0x04, 0x02, 0x7F, // M 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, // M
0x7F, 0x04, 0x08, 0x10, 0x7F, // N 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N
0x3E, 0x41, 0x41, 0x41, 0x3E, // O 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, // O
0x7F, 0x09, 0x09, 0x09, 0x06, // P 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, // P
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q
0x7F, 0x09, 0x19, 0x29, 0x46, // R 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, // R
0x46, 0x49, 0x49, 0x49, 0x31, // S 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, // S
0x01, 0x01, 0x7F, 0x01, 0x01, // T 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, // T
0x3F, 0x40, 0x40, 0x40, 0x3F, // U 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, // U
0x1F, 0x20, 0x40, 0x20, 0x1F, // V 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, // V
0x7F, 0x20, 0x18, 0x20, 0x7F, // W 0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00, // W
0x63, 0x14, 0x08, 0x14, 0x63, // X 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, // X
0x03, 0x04, 0x78, 0x04, 0x03, // Y 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, // Y
0x61, 0x51, 0x49, 0x45, 0x43, // Z 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, // Z (90)
0x00, 0x00, 0x7F, 0x41, 0x41, // [ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [
0x02, 0x04, 0x08, 0x10, 0x20, // "\" 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // "\"
0x41, 0x41, 0x7F, 0x00, 0x00, // ] 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, // ]
0x04, 0x02, 0x01, 0x02, 0x04, // ^ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, // ^
0x40, 0x40, 0x40, 0x40, 0x40, // _ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, // _
0x00, 0x01, 0x02, 0x04, 0x00, // ` 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // ' (96)
#ifndef NOLOWERCASE #ifndef NOLOWERCASE
0x20, 0x54, 0x54, 0x54, 0x78, // a 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a (97)
0x7F, 0x48, 0x44, 0x44, 0x38, // b 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b
0x38, 0x44, 0x44, 0x44, 0x20, // c 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, // c
0x38, 0x44, 0x44, 0x48, 0x7F, // d 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d
0x38, 0x54, 0x54, 0x54, 0x18, // e 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e
0x08, 0x7E, 0x09, 0x01, 0x02, // f 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f
0x08, 0x14, 0x54, 0x54, 0x3C, // g 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, // g
0x7F, 0x08, 0x04, 0x04, 0x78, // h 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h
0x00, 0x44, 0x7D, 0x40, 0x00, // i 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, // i
0x20, 0x40, 0x44, 0x3D, 0x00, // j 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, // j
0x00, 0x7F, 0x10, 0x28, 0x44, // k 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k
0x00, 0x41, 0x7F, 0x40, 0x00, // l 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l
0x7C, 0x04, 0x18, 0x04, 0x78, // m 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, // m
0x7C, 0x08, 0x04, 0x04, 0x78, // n 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, // n
0x38, 0x44, 0x44, 0x44, 0x38, // o 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o
0x7C, 0x14, 0x14, 0x14, 0x08, // p 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, // p
0x08, 0x14, 0x14, 0x18, 0x7C, // q 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, // q
0x7C, 0x08, 0x04, 0x04, 0x08, // r 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r
0x48, 0x54, 0x54, 0x54, 0x20, // s 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s
0x04, 0x3F, 0x44, 0x40, 0x20, // t 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t
0x3C, 0x40, 0x40, 0x20, 0x7C, // u 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u
0x1C, 0x20, 0x40, 0x20, 0x1C, // v 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v
0x3C, 0x40, 0x30, 0x40, 0x3C, // w 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w
0x44, 0x28, 0x10, 0x28, 0x44, // x 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x
0x0C, 0x50, 0x50, 0x50, 0x3C, // y 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, // y
0x44, 0x64, 0x54, 0x4C, 0x44, // z 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z (122)
#endif #endif
0x00, 0x08, 0x36, 0x41, 0x00, // { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, // { (123)
0x00, 0x00, 0x7F, 0x00, 0x00, // | 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, // |
0x00, 0x41, 0x36, 0x08, 0x00, // } 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, // }
0x08, 0x08, 0x2A, 0x1C, 0x08, // -> 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, // ->
0x08, 0x1C, 0x2A, 0x08, 0x08, // <- 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, // <- (127)
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol #ifndef NO_EXTENDED_CHARACTERS
// Extended characters - based on "DOS Western Europe" characters
// International characters not yet implemented.
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x90
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x38, 0x44, 0xc6, 0x44, 0x20, 0x00, // cent 0x9b
0x44, 0x6e, 0x59, 0x49, 0x62, 0x00, // £ 0x9c
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xa0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x10, 0x28, 0x54, 0x28, 0x44, 0x00, // <<
0x44, 0x28, 0x54, 0x28, 0x10, 0x00, // >>
// Extended characters 176-180
0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0
0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x55, // Mid grey 0xb1
0x6d, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3
0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4
0x14, 0x14, 0x14, 0xff, 0x00, 0x00, // Vertical line with double left spur 0xb5
0x08, 0x08, 0xff, 0x00, 0xff, 0x00, // Double vertical line with single left spur
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x00, // Top right corner, single horiz, double vert
0x14, 0x14, 0x14, 0xfc, 0x00, 0x00, // Top right corner, double horiz, single vert
// Extended characters 185-190
0x14, 0x14, 0xf7, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba
0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb
0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x00, // Bottom right corner, single horiz, double vert 0xbd
0x14, 0x14, 0x14, 0x1f, 0x00, 0x00, // Bottom right corner, double horiz, single vert 0xbe
// Extended characters 191-199
0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf
0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, // Bottom left corner 0xc0
0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, // Horizontal line with upward spur 0xc1
0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, // Horizontal line with downward spur 0xc2
0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4
0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5
0x00, 0x00, 0x00, 0xff, 0x14, 0x14, // Vertical line double right spur 0xc6
0x00, 0x00, 0xff, 0x00, 0xff, 0x08, // Double vertical line single right spur 0xc7
// Extended characters 200-206
0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8
0x00, 0x00, 0xfc, 0x04, 0xf4, 0x14, // Double top left corner 0xc9
0x14, 0x14, 0x17, 0x10, 0x17, 0x14, // Double horizontal with double upward spur 0xca
0x14, 0x14, 0xf4, 0x04, 0xf4, 0x14, // Double horizontal with double downward spur 0xcb
0x00, 0x00, 0xff, 0x00, 0xf7, 0x14, // Double vertical line with double right spur 0xcc
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd
0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce
0x14, 0x14, 0x14, 0x17, 0x14, 0x14, // Double horizontal line single upward spur 0xcf
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, // Horiz single line with double upward spur 0xd0
0x14, 0x14, 0x14, 0xf4, 0x14, 0x14, // Horiz double line with single downward spur 0xd1
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x08, // Horiz single line with double downward spur 0xd2
0x00, 0x00, 0x0f, 0x08, 0x0f, 0x08, // Bottom left corner, double vert single horiz 0xd3
0x00, 0x00, 0x00, 0x1f, 0x14, 0x14, // Bottom left corner, single vert double horiz 0xd4
0x00, 0x00, 0x00, 0xfc, 0x14, 0x14, // Top left corner, single vert double horiz 0xd5
0x00, 0x00, 0xf8, 0x08, 0xf8, 0x08, // Top left corner, double vert single horiz 0xd6
0x08, 0x08, 0xff, 0x00, 0xff, 0x08, // Cross, double vert single horiz 0xd7
0x14, 0x14, 0x14, 0xf7, 0x14, 0x14, // Cross, single vert double horiz 0xd8
// Extended characters 217-223
0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9
0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, // Top left corner 0xda
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Solid block 0xdb
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // Bottom half block 0xdc
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, // Left half block 0xdd
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf
0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, // Bottom Left block 0xe0
0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, // Bottom Right block
0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, // Top left block
0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, // Top right block 0xe3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xf0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
// Extended character 248
0x00, 0x06, 0x09, 0x09, 0x06, 0x00 // degree symbol 0xf8
#endif
}; };
const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;

View File

@ -23,24 +23,28 @@
#include "Arduino.h" #include "Arduino.h"
#include "FSH.h" #include "FSH.h"
#include "LCDDisplay.h" #include "Display.h"
#include "I2CManager.h" #include "I2CManager.h"
#include "DIAG.h" #include "DIAG.h"
#include "DisplayInterface.h"
// Uncomment to remove lower-case letters to save 108 bytes of flash // Uncomment to remove lower-case letters to save 108 bytes of flash
//#define NOLOWERCASE //#define NOLOWERCASE
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Constructor // Constructor
class SSD1306AsciiWire : public LCDDisplay { class SSD1306AsciiWire : public DisplayDevice {
public: public:
// Constructor // Constructors
SSD1306AsciiWire(int width, int height); SSD1306AsciiWire(int width, int height); // Auto-detects I2C address
SSD1306AsciiWire(I2CAddress address, int width, int height);
// Initialize the display controller. // Initialize the display controller.
void begin(uint8_t i2cAddr); bool begin();
// Clear the display and set the cursor to (0, 0). // Clear the display and set the cursor to (0, 0).
void clearNative() override; void clearNative() override;
@ -52,6 +56,8 @@ class SSD1306AsciiWire : public LCDDisplay {
size_t writeNative(uint8_t c) override; size_t writeNative(uint8_t c) override;
bool isBusy() override { return requestBlock.isBusy(); } bool isBusy() override { return requestBlock.isBusy(); }
uint16_t getNumCols() { return m_charsPerRow; }
uint16_t getNumRows() { return m_charsPerColumn; }
private: private:
// Cursor column. // Cursor column.
@ -62,26 +68,31 @@ class SSD1306AsciiWire : public LCDDisplay {
uint8_t m_displayWidth; uint8_t m_displayWidth;
// Display height. // Display height.
uint8_t m_displayHeight; uint8_t m_displayHeight;
// Display width in characters
uint8_t m_charsPerRow;
// Display height in characters
uint8_t m_charsPerColumn;
// Column offset RAM to SEG. // Column offset RAM to SEG.
uint8_t m_colOffset = 0; uint8_t m_colOffset = 0;
// Current font. // Current font.
const uint8_t* const m_font = System5x7; const uint8_t* const m_font = System6x8;
// Flag to prevent calling begin() twice
uint8_t m_initialised = false;
// Only fixed size 5x7 fonts in a 6x8 cell are supported. // Only fixed size 6x8 fonts in a 6x8 cell are supported.
static const uint8_t fontWidth = 5; static const uint8_t fontWidth = 6;
static const uint8_t fontHeight = 7; static const uint8_t fontHeight = 8;
static const uint8_t letterSpacing = 1;
static const uint8_t m_fontFirstChar = 0x20; static const uint8_t m_fontFirstChar = 0x20;
static const uint8_t m_fontCharCount = 0x61; static const uint8_t m_fontCharCount;
uint8_t m_i2cAddr; I2CAddress m_i2cAddr = 0;
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[fontWidth+letterSpacing+1]; uint8_t outputBuffer[fontWidth+1];
static const uint8_t blankPixels[]; static const uint8_t blankPixels[];
static const uint8_t System5x7[]; static const uint8_t System6x8[];
static const uint8_t FLASH Adafruit128xXXinit[]; static const uint8_t FLASH Adafruit128xXXinit[];
static const uint8_t FLASH SH1106_132x64init[]; static const uint8_t FLASH SH1106_132x64init[];
}; };

Some files were not shown because too many files have changed in this diff Show More