1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 10:23:45 +02:00

Compare commits

...

181 Commits

Author SHA1 Message Date
Harald Barth
2ada89f918 LCN bugfix 2023-02-12 20:35:57 +01: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
peteGSX
47cda83210 Disabled servo animations 2023-02-12 10:36:26 +10: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
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
Harald Barth
57292c2250 installer.sh script bug fix and enhancements 2023-02-07 20:44:03 +01: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
peteGSX
abe79b854e Fix digital read bug 2023-02-04 09:19:32 +10: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
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
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
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
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
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
Colin Murdoch
8fac20a451 Add #ifdef selections
Add #ifdef selections linked to #define in config.exampe.h
2023-01-16 18:16:25 +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
47 changed files with 1891 additions and 516 deletions

View File

@@ -29,6 +29,11 @@
#include "DCCWaveform.h"
#include "DCC.h"
#include "TrackManager.h"
#include "StringFormatter.h"
// variables to hold clock time
int16_t lastclocktime;
int8_t lastclockrate;
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
@@ -97,7 +102,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
}
void CommandDistributor::forget(byte clientId) {
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE;
}
#endif
@@ -155,6 +160,50 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
#endif
}
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%d<;>%d\n"), 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){
if (Diag::CMD) {
DIAG(F("Clock Command Received"));
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
}
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) {
DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);

View File

@@ -25,6 +25,7 @@
#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
@@ -45,10 +46,14 @@ public :
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
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 broadcastText(const FSH * msg);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
};
#endif

View File

@@ -99,6 +99,9 @@ void setup()
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// Responsibility 3: Start the DCC engine.
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
// Standard supported devices have pre-configured macros but custome hardware installations require

41
DCC.cpp
View File

@@ -595,30 +595,15 @@ void DCC::loop() {
void DCC::issueReminders() {
// if the main track transmitter still has a pending packet, skip this time around.
if ( DCCWaveform::mainTrack.getPacketPending()) return;
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
/*
for (int reg=0;reg<MAX_LOCOS;reg++) {
int slot=reg+nextLoco;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot)) nextLoco=slot+1;
return;
}
}
*/
for (int reg=nextLoco;reg<MAX_LOCOS+nextLoco;reg++) {
int slot=reg%MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot))
nextLoco=(slot+1)%MAX_LOCOS;
return;
}
}
// Move to next loco slot. If occupied, send a reminder.
int reg = lastLocoReminder+1;
if (reg > highestUsedReg) reg = 0; // Go to start of table
if (speedTable[reg].loco > 0) {
// have found loco to remind
if (issueReminder(reg))
lastLocoReminder = reg;
} else
lastLocoReminder = reg;
}
bool DCC::issueReminder(int reg) {
@@ -698,6 +683,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
speedTable[reg].groupFlags=0;
speedTable[reg].functions=0;
}
if (reg > highestUsedReg) highestUsedReg = reg;
return reg;
}
@@ -705,7 +691,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// 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;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {
@@ -725,13 +711,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
}
DCC::LOCO DCC::speedTable[MAX_LOCOS];
int DCC::nextLoco = 0;
int DCC::lastLocoReminder = 0;
int DCC::highestUsedReg = 0;
void DCC::displayCabList(Print * stream) {
int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) {
for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),

3
DCC.h
View File

@@ -108,7 +108,8 @@ private:
static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg);
static int nextLoco;
static int lastLocoReminder;
static int highestUsedReg;
static FSH *shieldName;
static byte globalSpeedsteps;

View File

@@ -41,6 +41,14 @@
#include "DCCTimer.h"
#include "EXRAIL2.h"
// This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access.
#define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==0) break; \
StringFormatter::send(stream,F(" %d"),value); \
}
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
@@ -73,6 +81,7 @@ const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309;
@@ -501,6 +510,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 's': // <s>
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
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
@@ -561,15 +571,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
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;
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_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
@@ -588,7 +608,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
@@ -605,14 +627,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
else { // <JT id>
Turnout * t=Turnout::get(id);
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
else StringFormatter::send(stream, F(" %d %c \"%S\""),
id,t->isThrown()?'T':'C',
else {
const FSH *tdesc = NULL;
#ifdef EXRAIL_ACTIVE
RMFT2::getTurnoutDescription(id)
#else
F("")
#endif
);
tdesc = RMFT2::getTurnoutDescription(id);
#endif
if (tdesc == NULL)
tdesc = F("");
StringFormatter::send(stream, F(" %d %c \"%S\""),
id,t->isThrown()?'T':'C',
tdesc);
}
}
StringFormatter::send(stream, F(">\n"));
return;
@@ -633,14 +658,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
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[])
{
@@ -730,15 +747,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
switch (params)
{
case 0: // <T> list turnout definitions
{
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
}
return Turnout::printAll(stream); // will <X> if none found
case 1: // <T id> delete turnout
if (!Turnout::remove(p[0]))
@@ -759,12 +768,19 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_T:
state= false;
break;
default:
return false; // Invalid parameter
case HASH_KEYWORD_X:
{
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;
return true;
}

View File

@@ -1,5 +1,5 @@
/*
* © 2022 Paul M. Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
@@ -102,9 +102,14 @@ private:
// that an offset can be initialized.
class ADCee {
public:
// init does add the pin to the list of scanned pins (if this
// begin is called for any setup that must be done before
// **init** can be called. On some architectures this involves ADC
// initialisation and clock routing, sampling times etc.
static void begin();
// init adds the pin to the list of scanned pins (if this
// platform's implementation scans pins) and returns the first
// read value. It is called before the regular scan is started.
// read value (which is why it required begin to have been called first!)
// It must be called before the regular scan is started.
static int init(uint8_t pin);
// 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
@@ -117,9 +122,6 @@ private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
// bit array of used pins (max 16)
static uint16_t usedpins;
// cached analog values (malloc:ed to actual number of ADC channels)

View File

@@ -88,7 +88,10 @@ void DCCTimer::clearPWM() {
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
for (byte i=0; i<6; i++) {
mac[i]=boot_signature_byte_get(0x0E + 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;
@@ -120,12 +123,13 @@ void DCCTimer::reset() {
}
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define NUM_ADC_INPUTS 7
#else
#define NUM_ADC_INPUTS 15
#else
#define NUM_ADC_INPUTS 7
#endif
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
bool ADCusesHighPort = false;
/*
* Register a new pin to be scanned
@@ -136,6 +140,8 @@ 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)
@@ -196,7 +202,15 @@ void ADCee::scan() {
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADMUX=(1<<REFS0)|id; //select AVCC as reference and set MUX
#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
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;

View File

@@ -1,4 +1,5 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
@@ -124,5 +125,31 @@ void DCCTimer::reset() {
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

View File

@@ -168,23 +168,6 @@ int ADCee::init(uint8_t pin) {
if (id > NUM_ADC_INPUTS)
return -1023;
// Dummy read using Arduino library
analogReadResolution(12);
value = analogRead(pin);
// 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
// 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
@@ -205,9 +188,11 @@ int ADCee::init(uint8_t pin) {
return value;
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/

View File

@@ -28,23 +28,23 @@
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_STM32
#include "FSH.h" //PMA temp debug
#include "DIAG.h" //PMA temp debug
#include "DCCTimer.h"
#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow
#if defined(STM32F411RE)
// STM32F411RE doesn't have Serial1 defined by default
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 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 STM32F411RE. 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 F411RE)
// 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(STM32F446ZE)
// STM32F446ZE doesn't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - 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.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
#else
#warning Serial1 not defined
#endif
@@ -94,10 +94,9 @@ void DCCTimer::clearPWM() {
}
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 *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;
@@ -132,31 +131,148 @@ void DCCTimer::reset() {
while(true) {};
}
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
uint id = pin - A0;
int value = 0;
PinName stmpin = digitalPin[analogInputPin[id]];
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO 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;
}
// Set pin mux mode to analog input
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
// Set the sampling rate for that analog input
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
if (analogvals == NULL)
{
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
return value;
}
/*
* 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;
uint8_t id = pin - A0;
// 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 uint 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
// 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
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
// 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();
//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

View File

@@ -141,4 +141,31 @@ void DCCTimer::reset() {
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

@@ -62,7 +62,6 @@ const bool signalTransform[]={
/* WAVE_PENDING (should not happen) -> */ LOW};
void DCCWaveform::begin() {
ADCee::begin();
DCCTimer::begin(DCCWaveform::interruptHandler);
}

View File

@@ -1,6 +1,6 @@
/*
* © 2021 Neil McKechnie
* © 2021-2022 Harald Barth
* © 2021-2023 Harald Barth
* © 2020-2022 Chris Harlow
* All rights reserved.
*
@@ -41,6 +41,7 @@
*/
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
#include "DCCWaveform.h"
@@ -90,11 +91,27 @@ 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_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#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);
if (offset&1) {
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
return GETHIGHFLASHW(RouteCode,offset);
}
LookList::LookList(int16_t size) {
m_size=size;
@@ -135,12 +152,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter);
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;
@@ -153,11 +175,14 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
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
// add sequences onRoutines to the lookups
for (int sigpos=0;;sigpos+=4) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
@@ -166,7 +191,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0);
VPIN operand=getOperand(progCounter,0);
switch (opcode) {
case OPCODE_AT:
@@ -176,6 +201,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
IODevice::configureInput((VPIN)pin,true);
break;
}
@@ -185,31 +211,32 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2);
int addr=getOperand(progCounter,1);
byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break;
}
case OPCODE_SERVOTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4);
VPIN pin=getOperand(progCounter,1);
int activeAngle=getOperand(progCounter,2);
int inactiveAngle=getOperand(progCounter,3);
int profile=getOperand(progCounter,4);
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
break;
}
case OPCODE_PINTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
VPIN pin=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
@@ -229,20 +256,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route
diag=saved_diag;
}
void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
}
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(routeIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==id) return 'R';
if (rid==0) break;
}
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(automationIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A';
if (rid==0) break;
}
@@ -304,7 +333,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals
// flags[n] represents the state of the nth signal in the table
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
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
@@ -551,7 +580,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
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
bool skipIf=false;
@@ -617,13 +646,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
@@ -634,7 +663,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
break; // and drop through
}
@@ -677,7 +706,7 @@ void RMFT2::loop2() {
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
@@ -711,19 +740,27 @@ void RMFT2::loop2() {
break;
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;
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;
case OPCODE_IFNOT: // do next operand if sensor not set
skipIf=readSensor(operand);
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
skipIf=(int16_t)(micros()%100) >= operand;
skipIf=(uint8_t)micros() >= operand * 255/100;
break;
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
@@ -798,11 +835,11 @@ void RMFT2::loop2() {
}
case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true);
DCC::setFn(operand,getOperand(1),true);
break;
case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false);
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_DCCACTIVATE: {
@@ -894,7 +931,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(GET_OPERAND(1));
int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@@ -912,7 +949,7 @@ void RMFT2::loop2() {
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;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@@ -944,6 +981,8 @@ void RMFT2::loop2() {
case OPCODE_ONRED:
case OPCODE_ONAMBER:
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
break;
@@ -961,7 +1000,7 @@ void RMFT2::delayMe(long delay) {
delayStart=millis();
}
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
if (FLAGOVERFLOW(id)) return false; // Outside range limit
byte f=flags[id];
f &= ~offMask;
@@ -982,8 +1021,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
}
int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) {
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id);
return -1;
@@ -993,9 +1032,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
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(int16_t id,char rag) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
@@ -1012,11 +1052,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values
int16_t sigpos=sigslot*4;
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
int16_t sigpos=sigslot*8;
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@@ -1044,11 +1084,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// Manage invert (HIGH on) pins
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
// set the three pins
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
if (redpin) {
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (!aHigh) redval=!redval;
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(int16_t id,char rag) {
@@ -1069,7 +1121,19 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
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);
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;
@@ -1092,3 +1156,95 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
void RMFT2::printMessage2(const FSH * 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(&Serial,F("<* EXRAIL(%d) "),loco);
stream=&Serial;
break;
case thrunge_serial: stream=&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;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_lcn:
#if defined(LCN_SERIAL)
stream=&LCN_SERIAL;
#endif
break;
case thrunge_parse:
case thrunge_broadcast:
case thrunge_lcd:
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(&Serial,F(" *>\n"));
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_parse:
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
break;
default: break;
}
}

View File

@@ -1,6 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2023 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -54,6 +55,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
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
// placed here so that they may be skipped as a group
@@ -64,9 +68,17 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
};
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcd, thrunge_lcn};
// Flag bits for status of hardware and TPL
@@ -107,16 +119,17 @@ class LookList {
static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed);
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 ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t FLASH routeIdList[];
static const int16_t FLASH automationIdList[];
static const int16_t FLASH rosterIdList[];
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
@@ -137,6 +150,7 @@ private:
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 * pausingTask;
void delayMe(long millisecs);
@@ -148,10 +162,12 @@ private:
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag;
static const FLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[];
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
@@ -161,7 +177,8 @@ private:
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain

View File

@@ -1,6 +1,6 @@
/*
* © 2021-2022 Chris Harlow
* © 2020,2021 Chris Harlow. All rights reserved.
* © 2020-2022 Chris Harlow. All rights reserved.
* © 2023 Harald Barth
*
* This file is part of CommandStation-EX
*
@@ -65,6 +65,7 @@
#undef IFCLOSED
#undef IFGREEN
#undef IFGTE
#undef IFLOCO
#undef IFLT
#undef IFNOT
#undef IFRANDOM
@@ -72,6 +73,7 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
@@ -85,9 +87,12 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONGREEN
#undef ONRED
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
@@ -110,6 +115,9 @@
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
@@ -175,6 +183,7 @@
#define IFCLOSED(turnout_id)
#define IFGREEN(signal_id)
#define IFGTE(sensor_id,value)
#define IFLOCO(loco_id)
#define IFLT(sensor_id,value)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
@@ -182,6 +191,7 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
@@ -192,12 +202,15 @@
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
@@ -220,6 +233,9 @@
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)

View File

@@ -1,6 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2023 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -55,6 +56,10 @@
// helper macro for turnout description as HIDDEN
#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
#include "EXRAIL2MacroReset.h"
#undef ALIAS
@@ -73,14 +78,14 @@ void exrailHalSetup() {
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= {
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t FLASH RMFT2::automationIdList[]= {
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
@@ -100,30 +105,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
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
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#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
#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;\
}
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) {
#include "myAutomation.h"
default: break ;
}
if (strfar) thrungeString(strfar,tmode,lcdid);
}
@@ -158,7 +187,7 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
@@ -198,7 +227,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
@@ -254,6 +283,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_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 IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
@@ -261,6 +291,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#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 JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
@@ -272,11 +303,14 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#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 ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#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 ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
@@ -299,6 +333,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(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 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)
@@ -323,7 +360,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = {
const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

View File

@@ -1,6 +1,7 @@
/*
* © 2022 Bruno Sanches
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
@@ -36,8 +37,13 @@ EthernetInterface * EthernetInterface::singleton=NULL;
*/
void EthernetInterface::setup()
{
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
if (singleton!=NULL) {
DIAG(F("Prog Error!"));
return;
}
if ((singleton=new EthernetInterface()))
return;
DIAG(F("Ethernet not initialized"));
};
@@ -62,37 +68,33 @@ EthernetInterface::EthernetInterface()
return;
}
#endif
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found"));
return;
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100"));
}
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
if (Ethernet.linkStatus() == LinkON)
break;
DIAG(F("Ethernet waiting for link (1sec) "));
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();
}
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("Ethernet cable not connected"));
return;
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
/**
* @brief Cleanup any resources
*
* @return none
*/
EthernetInterface::~EthernetInterface() {
delete server;
delete outboundRing;
}
/**
@@ -101,33 +103,73 @@ EthernetInterface::EthernetInterface()
*/
void EthernetInterface::loop()
{
if (!singleton) return;
if (!singleton || (!singleton->checkLink()))
return;
switch (Ethernet.maintain())
{
switch (Ethernet.maintain()) {
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
}
void EthernetInterface::loop2()
{
/**
* @brief Checks ethernet link cable status and detects when it connects / disconnects
*
* @return true when cable is connected, false otherwise
*/
bool EthernetInterface::checkLink() {
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
//if we are not connected yet, setup a new server
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
#ifdef IP_ADDRESS
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->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
// only create a outboundRing it none exists, this may happen if the cable
// gets disconnected and connected again
if(!outboundRing)
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
return true;
} else { // connected
DIAG(F("Ethernet cable disconnected"));
connected=false;
//clean up any client
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
if(clients[socket].connected())
clients[socket].stop();
}
// tear down server
delete server;
server = nullptr;
LCD(4,F("IP: None"));
}
return false;
}
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
EthernetClient client = server->accept();
@@ -182,7 +224,9 @@ void EthernetInterface::loop()
// handle at most 1 outbound transmission
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();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());

View File

@@ -56,15 +56,16 @@ class EthernetInterface {
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
void loop2();
EthernetServer * server;
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
~EthernetInterface();
void loop2();
bool checkLink();
EthernetServer * server = NULL;
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
RingStream * outboundRing;
RingStream * outboundRing = NULL;
};
#endif

55
FSH.h
View File

@@ -34,32 +34,51 @@
* PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH 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>
#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)
#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
#undef F
#endif
#define F(str) (str)
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte(addr)
#define GETFLASHW(addr) (*(const unsigned int8_t *)(addr)) | ((*(const unsigned int8_t *)(addr+1)) << 8)
#ifdef FLASH
#undef FLASH
#endif
#define FLASH PROGMEM
#else
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define FLASH PROGMEM
#define F(str) (str)
typedef char FSH;
#define FLASH
#define HIGHFLASH
#define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#endif
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202210311845Z"
#define GITHUB_SHA "devel-202302121935Z"

View File

@@ -161,6 +161,8 @@ public:
// once the GPIO port concerned has been read.
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, uint8_t i2cAddress=0);
protected:
@@ -234,9 +236,6 @@ protected:
// pin low if an input changes state.
int16_t _gpioInterruptPin = -1;
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
// Static support function for subclass creation
static void addDevice(IODevice *newDevice);
@@ -408,5 +407,8 @@ private:
#include "IO_MCP23008.h"
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#endif // iodevice_h

View File

@@ -1,5 +1,5 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* © 2022, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
@@ -36,24 +36,31 @@
* In mySetup function within mySetup.cpp:
* DFPlayer::create(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 0-30 to the second pin will set the volume of the output;
* Writing a digital value to the first pin will play or stop the file;
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
* 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.
*
* From EX-RAIL, the following commands may be used:
* SET(3500) -- starts playing the first file on the SD card
* SET(3501) -- starts playing the second 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 (file 2) on the SD card
* etc.
* RESET(3500) -- stops all playing on the player
* 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,23,30) -- plays file 23 at volume 30 (maximum)
* SERVO(3501,20,0) -- Sets the volume to 20
* SERVO(3500,2,Instant) -- plays file 2 at current volume
* SERVO(3501,20,Instant) -- Sets the volume to 20
*
* 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.
* 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.
*/
#ifndef IO_DFPlayer_h
@@ -68,6 +75,19 @@ private:
uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing
// When two commands are sent in quick succession, the device sometimes
// fails to execute one. A delay is required between successive commands.
// This could be implemented by buffering commands and outputting them
// from the loop() function, but it would somewhat complicate the
// driver. A simpler solution is to output a number of NUL pad characters
// between successive command strings if there isn't sufficient elapsed time
// between them. At 9600 baud, each pad character takes approximately
// 1ms to complete. Experiments indicate that the minimum number of pads
// for reliable operation is 17. This gives 17.7ms between the end of one
// command and the beginning of the next, or 28ms between successive commands
// being completed. I've allowed 20 characters, which is almost 21ms.
const int numPadCharacters = 20; // Number of pad characters between commands
public:
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
@@ -83,8 +103,10 @@ protected:
addDevice(this);
}
void _begin() override {
_serial->begin(9600);
void _begin() override {
_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;
// Send a query to the device to see if it responds
@@ -94,10 +116,10 @@ protected:
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"
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
while (_serial->available()) {
int c = _serial->read();
if (c == 0x7E)
if (c == 0x7E && _inputIndex == 0)
_inputIndex = 1;
else if ((c==0xFF && _inputIndex==1)
|| (c==0x3D && _inputIndex==3)
@@ -124,8 +146,8 @@ protected:
} else
_inputIndex = 0; // Unrecognised character sequence, start again!
}
// Check if the initial prompt to device has timed out. Allow 1 second
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
// Check if the initial prompt to device has timed out. Allow 5 seconds
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
}
@@ -218,6 +240,7 @@ private:
void sendPacket(uint8_t command, uint16_t arg = 0)
{
unsigned long currentMillis = millis();
uint8_t out[] = { 0x7E,
0xFF,
06,
@@ -231,7 +254,19 @@ private:
setChecksum(out);
// Check how long since the last command was sent.
// Each character takes approx 1ms at 9600 baud
unsigned long minimumGap = numPadCharacters + sizeof(out);
if (currentMillis - _commandSendTime < minimumGap) {
// Output some pad characters to add an
// artificial delay between commands
for (int i=0; i<numPadCharacters; i++)
_serial->write(0);
}
// Now output the command
_serial->write(out, sizeof(out));
_commandSendTime = currentMillis;
}
uint16_t calcChecksum(uint8_t* packet)

128
IO_EXFastclock.h Normal file
View File

@@ -0,0 +1,128 @@
/*
* © 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(uint8_t I2CAddress){
_I2CAddress = I2CAddress;
addDevice(this);
}
static void create(uint8_t _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 x%x"), _I2CAddress);
new EXFastClock(_I2CAddress);
}
else {
FAST_CLOCK_EXISTS = false;
//DIAG(F("No Fast Clock found"));
LCD(6,F("CLOCK NOT FOUND"));
}
}
private:
uint8_t _I2CAddress;
// 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 %d"), _I2CAddress);
}
}
}
// 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() {
DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

263
IO_EXIOExpander.h Normal file
View File

@@ -0,0 +1,263 @@
/*
* © 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).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_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, uint8_t i2cAddress) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
}
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_i2cAddress = i2cAddress;
addDevice(this);
}
void _begin() {
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_i2cAddress)) {
_command4Buffer[0] = EXIOINIT;
_command4Buffer[1] = _nPins;
_command4Buffer[2] = _firstVpin & 0xFF;
_command4Buffer[3] = _firstVpin >> 8;
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4);
if (_receive3Buffer[0] == EXIOPINS) {
_numDigitalPins = _receive3Buffer[1];
_numAnaloguePins = _receive3Buffer[2];
_digitalPinBytes = (_numDigitalPins + 7)/8;
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
_analoguePinBytes = _numAnaloguePins * 2;
_analogueInputStates = (byte*) calloc(_analoguePinBytes, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
} else {
DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
return;
}
// We now need to retrieve the analogue pin map
_command1Buffer[0] = EXIOINITA;
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1);
// Attempt to get version, if we don't get it, we don't care, don't go offline
_command1Buffer[0] = EXIOVER;
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"),
_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
}
}
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (paramCount != 1) return false;
int pin = vpin - _firstVpin;
if (configType == CONFIGURE_INPUT) {
bool pullup = params[0];
_digitalOutBuffer[0] = EXIODPUP;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = pullup;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
if (_command1Buffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
return false;
}
} else {
return false;
}
}
// Analogue input pin configuration, used to enable on EX-IOExpander device
int _configureAnalogIn(VPIN vpin) override {
int pin = vpin - _firstVpin;
_command2Buffer[0] = EXIOENAN;
_command2Buffer[1] = pin;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2);
if (_command1Buffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin);
return false;
}
return true;
}
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
void _loop(unsigned long currentMicros) override {
(void)currentMicros; // remove warning
if (_deviceState == DEVSTATE_FAILED) return;
_command1Buffer[0] = EXIORDD;
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1);
_command1Buffer[0] = EXIORDAN;
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
}
// Obtain the correct analogue input value
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
uint8_t _pinLSBByte;
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
if (_analoguePinMap[aPin] == pin) {
_pinLSBByte = aPin * 2;
}
}
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
}
// 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;
}
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIOWRD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = value;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
if (_command1Buffer[0] != EXIORDY) {
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
}
}
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%d 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;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7);
if (_command1Buffer[0] != EXIORDY) {
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
}
}
void _display() override {
DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"),
_i2cAddress, _majorVer, _minorVer, _patchVer,
(int)_firstVpin, (int)_firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
uint8_t _i2cAddress;
uint8_t _numDigitalPins = 0;
uint8_t _numAnaloguePins = 0;
byte _digitalOutBuffer[3];
uint8_t _versionBuffer[3];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
byte* _digitalInputStates;
byte* _analogueInputStates;
uint8_t _digitalPinBytes = 0;
uint8_t _analoguePinBytes = 0;
byte _command1Buffer[1];
byte _command2Buffer[2];
byte _command4Buffer[4];
byte _receive3Buffer[3];
byte _servoBuffer[7];
uint8_t* _analoguePinMap;
// 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

View File

@@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
addDevice(this);
}
// Initialisation of TurntableEX
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
@@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress, stepsMSB, stepsLSB, activity);
@@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

127
IO_RotaryEncoder.h Normal file
View File

@@ -0,0 +1,127 @@
/*
* © 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
*
* Further to this, feedback can 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.
*
* 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:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
}
private:
// Initiate the device
void _begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
byte _getVersion[1] = {RE_VER};
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
_buffer[0] = RE_OP;
I2CManager.write(_I2CAddress, _buffer, 1);
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
I2CManager.read(_I2CAddress, _buffer, 1);
_position = _buffer[0];
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin,1);
} else {
RMFT2::changeEvent(_firstVpin,0);
}
#endif
delayUntil(currentMicros + 100000);
}
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
byte _feedbackBuffer[2] = {RE_OP, value};
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
void _display() override {
DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer,
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
uint8_t _I2CAddress;
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _buffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
enum {
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
RE_OP = 0xA1, // Flag for normal operation
};
};
#endif

172
IO_duinoNodes.h Normal file
View File

@@ -0,0 +1,172 @@
/*
* © 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();
}
// 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:%d-%d 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

@@ -43,7 +43,7 @@ void LCN::loop() {
while (stream->available()) {
int ch = stream->read();
if (ch >= 0 && ch <= '9') { // accumulate id value
if (ch >= '0' && ch <= '9') { // accumulate id value
id = 10 * id + ch - '0';
}
else if (ch == 't' || ch == 'T') { // Turnout opcodes

View File

@@ -182,7 +182,7 @@
#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, A3, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 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

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

@@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
}
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.
@@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// 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++) {
@@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert.
return read();
#else
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
return '\0';
#endif
}
byte RingStream::readRawByte() {
@@ -189,11 +195,6 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
{ char s[_count+2];
strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
s[_count]=0;
//DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
}
_ringClient = NO_CLIENT;
return true; // commit worked
}

View File

@@ -27,7 +27,7 @@ class RingStream : public Print {
public:
RingStream( const uint16_t len);
static const int THIS_IS_A_RINGSTREAM=77;
static const int THIS_IS_A_RINGSTREAM=777;
virtual size_t write(uint8_t b);
// This availableForWrite function is subverted from its original intention so that a caller

View File

@@ -32,7 +32,7 @@ class StringBuffer : public Print {
private:
static const int buffer_max=64; // enough for long text msgs to throttles
int16_t _pos_write;
char _buffer[buffer_max+1];
char _buffer[buffer_max+2];
};
#endif

View File

@@ -70,7 +70,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
char* flash=(char*)format;
for(int i=0; ; ++i) {
char c=GETFLASH(flash+i);
if (c=='\0') return;
if (c=='\0') break; // to va_end()
if(c!='%') { stream->print(c); continue; }
bool formatContinues=false;
@@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
{
const FSH* flash= (const FSH*)va_arg(args, char*);
#ifndef ARDUINO_ARCH_ESP32
// On ESP32 the reading flashstring from rinstream code
// crashes, so don't use the flashstream hack on ESP32
#if WIFI_ON | ETHERNET_ON
// RingStream has special logic to handle flash strings
// but is not implemented unless wifi or ethernet are enabled.
@@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
((RingStream *)stream)->printFlash(flash);
else
#endif
#endif
stream->print(flash);
break;
}
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
@@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
case '\r': stream->print(F("\\r")); break;
case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\")); break;
default: stream->print(c);
case '\\': stream->print(F("\\\\")); break;
default: stream->write(c);
}
}

View File

@@ -171,9 +171,14 @@ public:
// Save all turnout definitions
static void store();
#endif
static void printAll(Print *stream) {
static bool printAll(Print *stream) {
bool gotOne=false;
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
if (!tt->isHidden()) {
gotOne=true;
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
}
return gotOne;
}

View File

@@ -63,69 +63,6 @@
WiThrottle * WiThrottle::firstThrottle=NULL;
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
if (n == 0)
return 0;
do {
if (*s1 != *s2++)
return 1;
if (*s1++ == 0)
break;
} while (--n != 0);
return 0;
}
void WiThrottle::findUniqThrottle(int id, char *u) {
WiThrottle *wtmyid = NULL;
WiThrottle *wtmyuniq = NULL;
// search 1, look for clientid match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (wt->clientid == id) {
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
return;
wtmyid = wt;
break;
}
}
// search 2, look for string match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (xstrncmp(u, wt->uniq, 16) == 0) {
wtmyuniq = wt;
break;
}
}
// analyse result of the two for loops:
if (wtmyid == NULL) { // should not happen
DIAG(F("Did not find my own wiThrottle handle"));
return;
}
// wtmyuniq == wtmyid has already returned in for loop 1
if (wtmyuniq == NULL) { // register uniq in the found id
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
return;
}
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
// so we need to do the copy (all other options covered above)
for(int n=0; n < MAX_MY_LOCO; n++)
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
wtmyid->heartBeat = wtmyuniq->heartBeat;
wtmyid->initSent = wtmyuniq->initSent;
wtmyid->exRailSent = wtmyuniq->exRailSent;
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE)
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
forget(wtmyuniq->clientid); // do not use wtmyid after this
}
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt;
@@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt;
break;
}
@@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=0;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
}
@@ -187,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
// On first few commands, send turnout, roster and routes
if (introSent) {
if (!turnoutsSent) sendTurnouts(stream);
else if(!rosterSent) sendRoster(stream);
else if (!routesSent) sendRoutes(stream);
else if (!heartrateSent) {
heartrateSent=true;
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
if (initSent) {
// Send turnout list if changed since last sent (will replace list on client)
if (turnoutListHash != Turnout::turnoutlistHash) {
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
}
else if (!exRailSent) {
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
exRailSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
for (byte pass=0;pass<2;pass++) {
// first pass automations, second pass routes.
for (int ix=0;;ix++) {
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
pass?'A':'R',id,desc, pass?'4':'2');
}
}
StringFormatter::send(stream,F("\n"));
#endif
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
}
}
@@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
}
break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
break;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
stream->write('\n'); // end roster
#endif
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
initSent = true;
if (cmd[1] == 'U') {
sendIntro(stream);
}
break;
case 'Q': //
@@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this;
break;
}
@@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
}
//use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
sendFunctions(stream,loco);
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
}
}
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
@@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream);
}
void WiThrottle::checkHeartbeat(RingStream * stream) {
@@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
}
}
//haba no, not necessary the only throttle and it may come back
//delete this;
// if it does come back, the throttle should re-acquire
delete this;
return;
}
@@ -656,5 +493,123 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success"));
stashStream->commit();
CommandDistributor::broadcastPower();
}
void WiThrottle::sendIntro(Print* stream) {
introSent=true;
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
}
void WiThrottle::sendTurnouts(Print* stream) {
turnoutsSent=true;
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
}
void WiThrottle::sendRoster(Print* stream) {
rosterSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
StringFormatter::send(stream,F("\n"));
#else
(void)stream; // remove warning
#endif
}
void WiThrottle::sendRoutes(Print* stream) {
routesSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
StringFormatter::send(stream,F("\n"));
#else
(void)stream; // remove warning
#endif
}
void WiThrottle::sendFunctions(Print* stream, byte loco) {
int16_t locoid=myLocos[loco].cab;
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use non-exrail presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
}
}

View File

@@ -45,7 +45,8 @@ class WiThrottle {
~WiThrottle();
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle;
static int getInt(byte * cmd);
@@ -61,10 +62,12 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable;
unsigned long heartBeat;
bool initSent; // valid connection established
bool exRailSent; // valid connection established
bool introSent=false;
bool turnoutsSent=false;
bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed);
@@ -74,6 +77,11 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab);
void sendIntro(Print * stream);
void sendTurnouts(Print * stream);
void sendRoster(Print * stream);
void sendRoutes(Print * stream);
void sendFunctions(Print* stream, byte loco);
// callback stuff to support prog track acquire
static RingStream * stashStream;
static WiThrottle * stashInstance;

View File

@@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
}
if (pendingCipsend) {
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false;
@@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND;
break;
}

View File

@@ -68,7 +68,9 @@ class WifiInboundHandler {
Stream * wifiStream;
static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048;
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
RingStream * inboundRing;
RingStream * outboundRing;
@@ -79,5 +81,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
};
#endif

View File

@@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) {
int cx=stream->read();
// A newline followed by !!! is an exit
// A newline followed by ! is an exit
if (cx=='\n' || cx=='\r') startOfLine=true;
else if (startOfLine && cx=='!') break;
else startOfLine=false;
stream->write(cx);
wifiStream->write(cx);
}
}
@@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
char *locator = (char *)waitfor;
DIAG(F("Wifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
int ch = wifiStream->read();
int nextchar;
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
char ch = (char)nextchar;
if (echo) {
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else USB_SERIAL.print((char)ch);
else USB_SERIAL.print(ch);
}
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
if (ch == GETFLASH(locator)) {

View File

@@ -224,4 +224,8 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// FastClock Enabler
// To build the FastClock code into the CS please uncomment the line below
//#define USEFASTCLOCK
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -149,6 +149,12 @@
#define CPU_TYPE_ERROR
#endif
// replace board type if provided by compiler
#ifdef BOARD_NAME
#undef ARDUINO_TYPE
#define ARDUINO_TYPE BOARD_NAME
#endif
////////////////////////////////////////////////////////////////////////////////
//
// WIFI_ON: All prereqs for running with WIFI are met

View File

@@ -37,6 +37,10 @@ function need () {
need git
if test -d `basename "$DCCEXGITURL"` ; then
: assume we are almost there
cd `basename "$DCCEXGITURL"` || exit 255
fi
if test -d .git ; then
: assume we are right here
git pull
@@ -44,6 +48,21 @@ else
git clone "$DCCEXGITURL"
cd `basename "$DCCEXGITURL"` || exit 255
fi
# prepare versions
VERSIONS=/tmp/versions.$$
git tag --sort=v:refname | grep Prod | tail -1 > $VERSIONS
echo master >> $VERSIONS
git tag --sort=v:refname | grep Devel | tail -1 >> $VERSIONS
echo devel >> $VERSIONS
# ask user what version to use
echo "What version to use? (give line number) If in doubt, use 1"
cat -n $VERSIONS
echo -n "> "
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $VERSIONS`
git checkout $LINE
if test -f config.h ; then
: all well
else
@@ -63,7 +82,14 @@ $ACLI core update-index || exit 255
# Board discovery
BOARDS=/tmp/boards.$$
$ACLI board list | grep serial > $BOARDS
$ACLI board list > /dev/null # download missing components
$ACLI board list | grep serial > $BOARDS # real run
if test -s $BOARDS ; then
: all well
else
echo "$ACLI: No boards found"
exit 255
fi
if test x`< $BOARDS wc -l` = 'x1' ; then
LINE=`cat $BOARDS`
else
@@ -96,6 +122,6 @@ echo FQBN is $FQBN
# Install phase
$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package
$ACLI board attach -p $PORT --fqbn $FQBN $PWD
$ACLI compile --fqbn $FQBN $PWD
$ACLI upload -v -t -p $PORT $PWD
$ACLI board attach -p $PORT --fqbn $FQBN "$PWD"
$ACLI compile --fqbn $FQBN "$PWD"
$ACLI upload -v -t -p $PORT "$PWD"

View File

@@ -20,7 +20,8 @@
#include "IO_HCSR04.h" // Ultrasonic range sensor
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
#include "IO_DFPlayer.h" // MP3 sound player
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//#include "IO_EXFastClock.h" // FastClock driver
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.
@@ -160,6 +161,63 @@ void halSetup() {
// DFPlayer::create(10000, 10, Serial1);
//=======================================================================
// The following directive defines an EX-Turntable turntable instance.
//=======================================================================
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
//
// The parameters are:
// VPIN=600
// Number of VPINs=1 (Note there is no reason to change this)
// I2C address=0x60
//
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
//EXTurntable::create(600, 1, 0x60);
//=======================================================================
// The following directive defines an EX-IOExpander instance.
//=======================================================================
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address)
//
// The parameters are:
// VPIN=an available Vpin
// Number of VPINs=pin count (must match device in use as per documentation)
// I2C address=an available I2C address (default 0x65)
//
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
// The example is for an Arduino Nano.
//EXIOExpander::create(800, 18, 0x65);
//=======================================================================
// The following directive defines a rotary encoder instance.
//=======================================================================
// The parameters are:
// firstVpin = First available Vpin to allocate
// numPins= Number of Vpins to allocate, can be either 1 or 2
// i2cAddress = Available I2C address (default 0x70)
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
//RotaryEncoder::create(700, 1, 0x70);
//RotaryEncoder::create(701, 2, 0x71);
//=======================================================================
// The following directive defines an EX-FastClock instance.
//=======================================================================
// EXFastCLock::create(I2C Address)
//
// The parameters are:
//
// I2C address=0x55 (decimal 85)
//
// Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default.
// EXFastClock::create(0x55);
}
#endif

View File

@@ -18,7 +18,8 @@ default_envs =
samd21-dev-usb
samd21-zero-usb
ESP32
Nucleo-STM32F411RE
Nucleo-F411RE
Nucleo-F446RE
Teensy3.2
Teensy3.5
Teensy3.6
@@ -50,18 +51,6 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:samc21-firebox]
platform = atmelsam
board = firebox
framework = arduino
upload_protocol = atmel-ice
lib_deps =
${env.lib_deps}
SparkFun External EEPROM Arduino Library
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
board = megaatmega2560
@@ -108,6 +97,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -mcall-prologues
[env:mega328]
platform = atmelavr
@@ -155,6 +145,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -mcall-prologues
[env:nano]
platform = atmelavr
@@ -172,12 +163,21 @@ framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17
[env:Nucleo-STM32F411RE]
[env:Nucleo-F411RE]
platform = ststm32
board = nucleo_f411re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F446RE]
platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
@@ -185,7 +185,7 @@ monitor_echo = yes
platform = teensy
board = teensy31
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -193,7 +193,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy35
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -201,7 +201,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy36
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -209,7 +209,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy40
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@@ -217,6 +217,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy41
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

View File

@@ -4,7 +4,28 @@
#include "StringFormatter.h"
#define VERSION "4.2.4"
#define VERSION "4.2.17"
// 4.2.17 LCN bugfix
// 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software
// 4.2.15 Add basic experimental PWM support to EX-IOExpander
// EX-IOExpander 0.0.14 minimum required
// 4.2.14 STM32F4xx fast ADC read implementation
// 4.2.13 Broadcast power for <s> again
// 4.2.12 Bugfix for issue #299 TurnoutDescription NULL
// 4.2.11 Exrail IFLOCO feature added
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
// IO_EXIOExpander.h input speed optimisation
// 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements
// Report BOARD_TYPE if provided by compiler
// 4.2.7 FIX: Static IP addr
// FIX: Reuse WiThrottle list entries
// 4.2.6 FIX: Remove RAM thief
// FIX: ADC port 8-15 fix
// 4.2.5 Make GETFLASHW code more universal
// FIX: Withrottle roster index
// Ethernet start improvement and link detection
// 4.2.4 ESP32 experimental BT support
// More DC configurations possible and lower frequency
// Handle decoders that do not ack at write better