1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 18:33:44 +02:00

Compare commits

...

480 Commits

Author SHA1 Message Date
Harald Barth
102d6078a7 version 5.2.3 2023-11-16 08:38:39 +01:00
Harald Barth
8943f2da18 Merge branch 'devel-parsebug' into devel 2023-11-16 08:36:26 +01:00
Harald Barth
7bd2ba9b41 Bugfix: Catch stange input to parser 2023-11-16 00:27:23 +01:00
Colin Murdoch
b472230b47 Update version.h
Updated version.h
2023-11-15 19:41:56 +00:00
Colin Murdoch
6da3153dd5 Define scroll rows in config
Allow the definition of MAX_CHARACTER_ROWS in config.h
2023-11-15 19:29:24 +00:00
Asbelos
b5d9798144 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-14 19:41:09 +00:00
Asbelos
566ce1b7f8 Virtual LCD phase 1 2023-11-14 19:41:05 +00:00
Harald Barth
1af5132e6a version 5.2.1 timestamp 2023-11-14 11:16:54 +01:00
Harald Barth
763ef8be34 prettier MAX_TRACKS 2023-11-14 11:12:14 +01:00
Harald Barth
fd6e8705c8 Merge branch 'devel' into devel-esp32boost 2023-11-14 10:56:15 +01:00
Harald Barth
503378f1bb version 5.2.1 2023-11-14 00:06:53 +01:00
Harald Barth
582ff890f4 Trackmanager rework for simpler structure 2023-11-14 00:05:18 +01:00
Harald Barth
86ed8ff8a6 remove power state from <=> answer 2023-11-13 17:16:58 +01:00
Asbelos
148d4d30f8 Fix <jB active/inactive transposed 2023-11-11 17:31:38 +00:00
Harald Barth
b3ba647b09 Merge branch 'devel' into devel-esp32boost 2023-11-11 18:26:07 +01:00
Asbelos
4c89b26c79 fix <JA /<JR confusion 2023-11-11 09:12:08 +00:00
Harald Barth
e8b9f80c8c Reformat reply to <=> 2023-11-11 09:45:28 +01:00
Harald Barth
befcfebec7 version 5.2.0 2023-11-11 08:15:15 +01:00
Harald Barth
9ce95c07aa Booster mode configured by defined booster pin. New mode name output 2023-11-11 08:03:59 +01:00
Harald Barth
d8cc0c632a Merge branch 'devel' into devel-esp32boost2 2023-11-11 07:19:15 +01:00
Asbelos
d877fc315e Fix non-routestate code eliminator 2023-11-10 23:56:41 +00:00
Asbelos
6c18226cb5 Fix non-exrail crash 2023-11-10 23:46:17 +00:00
Asbelos
1c5f299b0e Fix ESP32 cast issue 2023-11-10 23:28:41 +00:00
Harald Barth
fb14fbd81b Merge branch 'devel' into devel-esp32boost2 2023-11-11 00:05:48 +01:00
Harald Barth
2f3d489f18 ESP32: autoreverse and booster prototype 2023-11-10 23:58:30 +01:00
Asbelos
d2d7a5cd16 EXRAIL multiple ON events 2023-11-10 20:13:33 +00:00
Harald Barth
337af77a03 booster test 2023-11-10 20:33:14 +01:00
Asbelos
670645db4b Version 20 2023-11-10 19:28:29 +00:00
Asbelos
a4eabf235e EXRAIL ROUTE_STATE and ROUTE_CAPTION 2023-11-10 19:25:24 +00:00
Asbelos
2cbcecf9e6 separate routes and sequences, handle state and captions. 2023-11-09 20:25:10 +00:00
Asbelos
26cf28dff7 fixups 2023-11-09 19:27:52 +00:00
Asbelos
44351b83ae Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-09 19:26:57 +00:00
Asbelos
4e08177b7b Route state management (part 1) 2023-11-07 16:27:26 +00:00
Harald Barth
f2ff1ba22a version 5.1.19 2023-11-06 22:14:39 +01:00
Harald Barth
043e6fdb26 Only flag 2.2.0.0-dev as broken, not 2.2.0.0 2023-11-06 22:13:03 +01:00
Asbelos
24e0f189e1 fix TURNOUTL 2023-11-01 20:19:59 +00:00
Harald Barth
33b2820095 Bugfix version detection logic and better message 2023-10-28 19:21:29 +02:00
Harald Barth
7b3b16b211 Divide out C for config and D for diag commands 2023-10-23 11:45:52 +02:00
peteGSX
27a5f76a8d Merge pull request #361 from DCC-EX:separate-hal-extt-turntable
Separate-hal-extt-turntable
2023-10-17 05:17:24 +10:00
peteGSX
754bd99381 Update version 2023-10-17 05:08:04 +10:00
peteGSX
650e411a4f Add vpin parameter 2023-10-17 05:06:35 +10:00
peteGSX
0978bb0c11 Changes made, but non-functional 2023-10-16 08:12:11 +10:00
Asbelos
6eb7051fd6 LCC and signal compile-out
LCC commands in EXRAIL for OpenMRN Adapter

FIrst use of compile-out of unused features.
2023-10-13 13:59:06 +01:00
peteGSX
5726844c83 Merge pull request #360 from DCC-EX:fix-ifttposition
Fixed
2023-10-13 04:46:05 +10:00
peteGSX
0214a55b23 Fixed 2023-10-13 04:37:38 +10:00
Asbelos
7db4a9575a Merge branch 'master' into devel 2023-10-12 11:07:39 +01:00
Asbelos
8b8e9e4919 clean result from invalid <JR n> 2023-10-12 11:07:05 +01:00
peteGSX
ce84974967 Missed one i 2023-10-12 13:42:14 +10:00
peteGSX
034c441c34 Merge pull request #359 from DCC-EX:turntable-broadcast-I
Change broadcast
2023-10-12 13:35:55 +10:00
peteGSX
d5978b1578 Change broadcast 2023-10-12 13:28:39 +10:00
Colin Murdoch
ea4f90d5fc Merged in Power changes
Merge in power changes and EXRAIL command & update to version.h
2023-10-11 17:06:56 +01:00
Colin Murdoch
1181fd855d Merge branch 'devel-power-chm' into devel 2023-10-11 16:58:17 +01:00
Colin Murdoch
a092e06a6f Update .gitignore
added UserAddin.txt to gitignore
2023-10-10 12:11:49 +01:00
Colin Murdoch
68fd56e7fc Added returnDCAddr
Added function to return DC address
2023-10-10 11:52:46 +01:00
Asbelos
370dae0ab8 Merge branch 'master' into devel 2023-10-09 18:15:36 +01:00
Asbelos
bef4b2ec35 fix <JR> default roster 2023-10-09 18:09:48 +01:00
Colin Murdoch
fe618d0b85 Add getModeName()
Add facility to get the name of the track mode
2023-10-06 19:11:11 +01:00
pmantoine
2ff1619ad1 STM32 reinstate 100% duty cycle PWM 2023-10-04 14:54:06 +08:00
pmantoine
7afd4443d6 STM32 revised I2C clock setup 2023-10-02 12:04:47 +08:00
Colin Murdoch
52cfc18754 Remove Diags not needed
Tidy up Diags and responses - use HASH_KEYWORD in place of 'A'
2023-09-28 15:02:30 +01:00
pmantoine
ed0cfee091 STM32 DCCEXanalogWrite for TrackManager PWM 2023-09-28 17:43:22 +08:00
Colin Murdoch
25bbfa4c68 Fix <1 JOIN>
Fixed <1 JOIN> issue in TrackManager
2023-09-27 14:46:48 +01:00
Colin Murdoch
2a46b96083 Updates to power
Updates to powere routines and EXRAIL
2023-09-26 18:02:39 +01:00
Colin Murdoch
17c004aecf Code corrections
code corrections
2023-09-25 14:32:54 +01:00
Colin Murdoch
9e3ae21bb8 Change to EXRAIL Set_Power
Change to EXRAIL SET_Power
2023-09-25 09:59:17 +01:00
Harald Barth
624656ebc9 date tag 2023-09-24 20:56:27 +02:00
Harald Barth
5a7f278b1e correct return when requesting D RAM 2023-09-24 20:55:16 +02:00
Harald Barth
9333beda49 correct return when requesting D RAM 2023-09-24 20:54:17 +02:00
Colin Murdoch
aacb980dc8 Power control plus EXRAIL
Power Control <0 A> etc plus EXRAIL SET_POWER
Not yet fully tested.
2023-09-24 15:40:42 +01:00
kempe63
11b9fd4ef5 Fixed IO_PCA9555.h to work with PCA9548 mux 2023-09-23 20:25:10 +01:00
kempe63
d07718be8c Revert "Update version.h to 5.1.7"
This reverts commit d57b5ba537.
2023-09-23 20:18:59 +01:00
kempe63
d57b5ba537 Update version.h to 5.1.7 2023-09-23 20:09:01 +01:00
kempe63
dab02ec659 Update IO_PCA9555.h
Now compiles and works on PCA9548 Mux. Tested with MCP23017 and PCA9555 on the same Subbus
2023-09-23 16:10:16 +01:00
kempe63
6ad5326f1d PCA9555 compiles and tested on PCA9548 mux
IO_PCA9555.h added changes that had been applied to IO_MCP23017 for support on PCA9548 Mux. Constructor now also private and type casting of variables made the same for IO_PCA9555. Tested with MCP23017 and PCA9555 simultaneous on the same Mux Subbus
2023-09-23 16:06:14 +01:00
kempe63
39e1363ce0 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-09-23 16:04:26 +01:00
kempe63
c9d4f5e94d Update IO_PCA9555.h 2023-09-23 15:56:09 +01:00
Colin Murdoch
8052090e0f Added Single Track Power On/Off
Added power On/Off <> commands
2023-09-22 17:03:40 +01:00
pmantoine
dfe3e9d42c STM32 ADCee extensions 2023-09-21 16:09:04 +08:00
pmantoine
5d810a620b STM32 variant support extension and tidy 2023-09-21 16:05:35 +08:00
pmantoine
550ad58c4d STM32 ADCee extended to support ADC2 and ADC3 2023-09-21 14:39:25 +08:00
peteGSX
7a305e179c Merge pull request #353 from DCC-EX:turntable-fix
Updated broadcasts
2023-09-15 06:32:30 +10:00
peteGSX
8437b0e7aa Updated broadcasts 2023-09-15 06:26:29 +10:00
Harald Barth
46289fa78c Check bad AT firmware version 2023-09-14 09:05:23 +02:00
pmantoine
ebbeea5fbb STM32F4xx native I2C driver merge 2023-09-13 16:46:36 +08:00
pmantoine
a8321fff42 Merge pull request #352 from DCC-EX/STM32_I2C_PMA_NEIL
Stm32 i2 c pma neil
2023-09-13 16:43:59 +08:00
peteGSX
a16790f585 Merge pull request #349 from DCC-EX:add-turntable-object
Add-turntable-object
2023-09-11 05:02:20 +10:00
peteGSX
da6a3c442f Remove redundant line 2023-09-10 18:41:14 +10:00
peteGSX
4fcd81a118 Update version 2023-09-10 07:18:54 +10:00
peteGSX
eb450dbd89 Merge branch 'devel' into add-turntable-object 2023-09-10 07:13:16 +10:00
peteGSX
a0562fdf5c Update defines to match changes in devel 2023-09-10 07:06:27 +10:00
peteGSX
7ee2c29a52 Include HAL create with EXTT_TURNTABLE 2023-09-10 05:30:48 +10:00
peteGSX
dba5d35aa2 Add response to create 2023-09-09 09:23:10 +10:00
peteGSX
be10be5a1a Added angles 2023-09-09 07:22:10 +10:00
Colin Murdoch
dca023ffd7 Update version.h
Added ONOVERLOAD and AFTEROVERLOAD as 5.1.4
2023-09-07 20:27:25 +01:00
Colin Murdoch
4eef9581fe Merge branch 'devel-plus-onoverload' into devel 2023-09-07 20:23:17 +01:00
peteGSX
bd02d1c15b WAITFORTT ready for testing 2023-09-07 07:58:19 +10:00
peteGSX
004d7b6631 JO and JP working 2023-09-07 07:32:54 +10:00
peteGSX
21ce87eb3e Descriptions available 2023-09-07 05:33:26 +10:00
Colin Murdoch
ab393047c1 Update MotorDriver.cpp
Replace duplicate call to EXRAIL with single in overload.
2023-09-06 11:20:23 +01:00
peteGSX
1f5f7754c1 Start on position description 2023-09-06 15:16:46 +10:00
peteGSX
6adff43f4b Update add position 2023-09-06 07:59:43 +10:00
Colin Murdoch
1ac104704e Update TrackManager.cpp
Reverse logic in TM::IisPowerOn()
2023-09-05 20:52:18 +01:00
Colin Murdoch
2f8e915b1e Added AFTEROVERLOAD
Added the AFTEROVERLOAD option - as yet untested.
2023-09-05 12:21:09 +01:00
peteGSX
152f9850bb Working 2023-09-05 19:05:18 +10:00
peteGSX
3094c52bf8 Ready to test 2023-09-05 08:38:37 +10:00
peteGSX
86f4567556 Revisiting logic 2023-09-04 18:46:28 +10:00
peteGSX
dd890e65bf Add move check 2023-09-04 07:38:26 +10:00
peteGSX
1e48c59cd8 Capture progress 2023-09-03 18:54:56 +10:00
peteGSX
004d10ee58 Fix build errors 2023-09-02 18:45:59 +10:00
peteGSX
e734661d1b EXRAIL ready for testing 2023-09-02 08:29:49 +10:00
peteGSX
bcb250bacf Broadcasts working 2023-09-01 18:30:02 +10:00
peteGSX
798241927f Really fix build errors 2023-09-01 13:28:24 +10:00
peteGSX
df2f09f4d2 Fix build errors 2023-09-01 09:04:48 +10:00
peteGSX
f40d57d8bd Add DCC type, EXTT broadcast from driver 2023-09-01 08:44:32 +10:00
peteGSX
9fa213e198 Undo callback 2023-08-31 13:51:25 +10:00
Harald Barth
44d8154223 version number update 2023-08-30 23:57:49 +02:00
Harald Barth
01919b33df Make parser more fool proof 2023-08-30 23:55:39 +02:00
Harald Barth
b3cafd126e sample file corrections 2023-08-30 23:26:20 +02:00
peteGSX
a0c1ad182c Start on callback 2023-08-30 19:48:30 +10:00
peteGSX
dbf053858b Undo max params 2023-08-30 13:40:09 +10:00
peteGSX
232ac993ec Separate add from create 2023-08-30 08:45:11 +10:00
Harald Barth
26ddd27ecf let user disable <D > command in favour for HAL on the Uno platform 2023-08-29 14:27:38 +02:00
peteGSX
6cad794411 Working with 15 positions 2023-08-29 19:04:45 +10:00
peteGSX
b0d8510127 Working but limited 2023-08-29 13:38:52 +10:00
peteGSX
3bfdd16288 Start on JO 2023-08-28 13:11:37 +10:00
peteGSX
df4a501e8a Writing to driver 2023-08-28 08:36:09 +10:00
peteGSX
2202cb0c5e Minor progress 2023-08-27 19:30:56 +10:00
peteGSX
1425da20b5 Correct order 2023-08-26 19:41:17 +10:00
peteGSX
b823a647ac Some progress 2023-08-26 10:26:01 +10:00
Harald Barth
2c64f10da8 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-08-25 19:14:25 +02:00
Harald Barth
25426d076d version number update 2023-08-25 19:14:03 +02:00
Harald Barth
3453da0671 Bugfix: ESP32 30ms off time 2023-08-25 19:12:47 +02:00
Harald Barth
c55fa9f9d2 version number update 2023-08-25 19:08:58 +02:00
Harald Barth
210d96a3e3 Bugfix: ESP32 30ms off time 2023-08-25 19:07:57 +02:00
Colin Murdoch
fb226311e5 Update myHal.cpp_example.txt
Added missing ::create to LiquidCrystal HAL definition
2023-08-24 14:54:33 +01:00
Colin Murdoch
6392c74ead Update myHal.cpp_example.txt
Added missing ::create to LiquidCrystal
2023-08-24 14:53:01 +01:00
Harald Barth
25f8852af6 call devel for 5.1.X version number update 2023-08-24 10:09:38 +02:00
Harald Barth
9842ea8a42 Bugfix: execute 30ms off time before rejoin 2023-08-24 10:07:25 +02:00
peteGSX
fa0aa27d46 Add OPCODE list to DCCEXParser.cpp 2023-08-24 10:07:15 +02:00
Harald Barth
42f3c7c128 version number update 2023-08-24 10:05:31 +02:00
Harald Barth
6cd7002e91 Bugfix: execute 30ms off time before rejoin 2023-08-24 10:03:29 +02:00
peteGSX
57d4655d54 Fix Uno/Nano build errors 2023-08-24 07:22:37 +10:00
peteGSX
ff9c558b61 Not much progress 2023-08-23 19:08:04 +10:00
peteGSX
b277d204f0 Progress! 2023-08-22 19:30:22 +10:00
peteGSX
c4febd1d0f More parser 2023-08-21 19:33:45 +10:00
peteGSX
98f8022268 Fix device driver, disable objects, start parser 2023-08-21 06:43:06 +10:00
peteGSX
1491da4813 Starting, very broken 2023-08-20 19:26:04 +10:00
kempe63
4b2c0702a4 Update version.h
Update version.h for IO_PCA9555 changes
2023-08-18 11:40:34 +01:00
kempe63
e27cceeb74 Update PCA9555.h inconsistencies to IO_MCP23017.h causing IO_PCA9555.h compile error when configure for Mux
Updated Class PCA9555 definition reflecting changes in IO_MCP23017.h to support PCA9548 mux. Checked with PCA9555 base board, compiles and run EXRAIL script with  output driver
2023-08-18 11:30:37 +01:00
peteGSX
085762e800 Add OPCODE list to DCCEXParser.cpp 2023-08-18 18:52:34 +10:00
Colin Murdoch
247763ac00 Code Corrections
Code corrections
2023-08-12 19:10:35 +01:00
Colin Murdoch
e327e0ae8d Added ONOVERLOAD
Added code changes to create ONOVERLOAD command in EXRAIL
2023-08-12 18:40:48 +01:00
Harald Barth
9f38dae8ba Check bad AT firmware version 2023-08-11 00:07:02 +02:00
Harald Barth
2db2b0ecc6 Committing a SHA 2023-08-07 20:27:22 +02:00
Harald Barth
fd58a749ef Committing a SHA 2023-08-07 20:25:14 +02:00
Harald Barth
3bddf4dfd1 Make 4.2.69 the 5.0.0 release 2023-08-07 19:45:45 +02:00
Harald Barth
e0e965f81c Merge branch 'master' into devel 2023-08-07 19:41:00 +02:00
Harald Barth
f2be3aeac3 Make <!> work in DC mode 2023-08-04 14:45:05 +02:00
Harald Barth
a74d85e895 Rename track mode OFF to NONE 2023-08-02 10:00:43 +02:00
Harald Barth
df2e651217 version, compile warning 2023-08-02 01:12:32 +02:00
Harald Barth
36d139268d AVR: Pin specific timer register seting for speed and mode when inrush throttling and for DC PWM 2023-08-02 01:05:31 +02:00
Harald Barth
e3ac3a8ddf Protect Uno user from choosing DC(X) 2023-08-02 01:02:46 +02:00
pmantoine
415e756020 More Nucleo variant defines 2023-07-31 16:51:25 +08:00
kempe63
f754fe2fbf GPIO PCA9555 / TCA9555 support
My 1st commit, be gentle
2023-07-29 20:34:39 +01:00
Harald Barth
399030d8ae make variable frequency a compile option 2023-07-25 12:51:23 +02:00
Harald Barth
4c7e11ddc1 version 2023-07-25 11:30:08 +02:00
Harald Barth
495bbf66bf better variable name 2023-07-25 11:23:36 +02:00
Harald Barth
2950ef010a diag 2023-07-18 01:25:38 +02:00
Harald Barth
c2eb5f23b4 restrict to relevant TRACK_MODE(s) 2023-07-17 09:42:39 +02:00
Harald Barth
94648ead28 versiontag 2023-07-17 02:31:00 +02:00
Harald Barth
ec0499e9da throttleInrush() (tested on ESP32) 2023-07-17 02:30:11 +02:00
Harald Barth
9b75026eef change from trackMode[t] to track[t]->{get,set}Mode 2023-07-17 02:26:29 +02:00
Harald Barth
6036ff9b15 ESP32: ledcSetup before ledcAttach 2023-07-17 02:22:35 +02:00
Harald Barth
6476a7aac2 version 2023-07-14 23:11:22 +02:00
Harald Barth
0edf34bfe2 inrush test ESP32 only 2023-07-14 23:10:50 +02:00
Harald Barth
aa1f25fc72 Set WIFI_FORCE_AP default as false 2023-07-09 12:04:40 +02:00
Harald Barth
b44bebc1c6 copyright, version and compile warnings fix 2023-07-08 08:58:00 +02:00
habazut
1a17cdb62f Merge pull request #340 from nathankellenicki/devel
[Feat] Added WIFI_FORCE_AP option to config
2023-07-08 08:46:34 +02:00
Harald Barth
7ce1618a9c Merge branch 'devel-overload' into devel 2023-07-07 21:52:55 +02:00
Harald Barth
4192c1f5a3 Do not invoke graphical install on Raspbian 2023-07-06 16:58:36 +02:00
Harald Barth
c2fcdddd1f ESP32 protect from race in RMT code 2023-07-06 15:19:44 +02:00
Harald Barth
f19db3aa5c DISABLE_PROG does count as less RAM as well 2023-07-04 16:25:15 +02:00
Harald Barth
e6a40e622c download graphic installer if DISPLAY 2023-07-03 23:43:21 +02:00
Nathan Kellenicki
b3251e89d7 Fixed Arduino 2023-07-02 19:51:29 -07:00
Nathan Kellenicki
ae2bbbf668 Added WIFI_FORCE_AP to force AP mode when specifying SSID/pass 2023-07-02 19:51:29 -07:00
Harald Barth
96a46f36c2 Adjust overcurrent timeouts 2023-07-03 00:21:52 +02:00
Harald Barth
10c59028e1 Add documentation 2023-07-02 20:33:29 +02:00
Harald Barth
ab1356d070 Change first join/unjoin and set power after that 2023-07-02 13:55:56 +02:00
Harald Barth
70d4c016ef completely new overcurrent detection 2023-07-02 01:33:41 +02:00
peteGSX
efe96d1d84 Merge pull request #339 from DCC-EX:fix-re-send
RotaryEnoder, EX-Turntable fixes
2023-06-30 12:24:30 -07:00
peteGSX
5d17f247de RotaryEnoder, EX-Turntable fixes 2023-07-01 05:18:45 +10:00
Harald Barth
7c41ec7c25 version tag 2023-06-30 02:06:12 +02:00
Harald Barth
9c5e48c3d5 test more tolerant alg 2023-06-30 02:05:10 +02:00
Fred
2eb0f48994 Update .gitignore
Updated .gitignore from the devel branch and used "my@.cpp" instead of listing the individual files so we can ignore anything that starts with "my"
2023-06-29 14:24:51 -04:00
pmantoine
1bdb05a471 ESP32 now sets hostname to dccex in STA mode 2023-06-29 11:00:14 +08:00
Harald Barth
c2fa76c76a version 2.4.61 2023-06-26 20:01:56 +02:00
Harald Barth
35fd912c60 MAX_CURRENT restriction (caps motor shield value) 2023-06-26 20:00:03 +02:00
Harald Barth
dfba6c6fc1 version tag 2023-06-23 13:55:34 +02:00
Harald Barth
f3cb263aaa convert mac addr hex chars to lower case to be compatible with AT software 2023-06-23 13:54:25 +02:00
pmantoine
ec6e730559 ESP32 mDNS registration for throttle autodiscovery 2023-06-23 18:08:05 +08:00
Fred
196a27a27a Update WifiESP32.cpp (#338)
* Update WifiESP32.cpp

Fix SSID for AP from DCC_ to DCCEX_

* Update version.h to 4.2.59
2023-06-22 19:47:20 -04:00
Harald Barth
99b6ca025a move ADCee begin as well 2023-06-22 23:30:28 +02:00
Harald Barth
db555e8820 Start motordriver as soon as possible but without waveform 2023-06-22 22:57:59 +02:00
Harald Barth
0d679ad993 version 2023-06-21 10:49:49 +02:00
Harald Barth
dd80260781 add common fault pin handling to new overload code 2023-06-21 10:44:57 +02:00
Harald Barth
08f41415dc format option to write microseconds 2023-06-21 10:43:41 +02:00
Harald Barth
2f65d4347e Merge branch 'devel-overcurrent' into devel 2023-06-20 21:17:28 +02:00
peteGSX
995c6f8ede Update version 2023-06-20 19:32:43 +10:00
peteGSX
4331ddfdf0 Merge pull request #337 from DCC-EX:rotary-encoder-send-position
Rotary-encoder-send-position
2023-06-20 02:30:40 -07:00
peteGSX
2b8b995307 Updated logic, sending move 2023-06-20 19:24:49 +10:00
peteGSX
2af01e3c42 Add ready flag 2023-06-20 12:48:13 +10:00
peteGSX
73b45ba9b8 Merge branch 'rotary-encoder-send-position' of https://github.com/DCC-EX/CommandStation-EX into rotary-encoder-send-position 2023-06-20 10:54:50 +10:00
peteGSX
247cea6dc1 Update some logic 2023-06-20 10:54:40 +10:00
peteGSX
c932325120 Add _writeAnalogue() 2023-06-20 10:54:40 +10:00
pmantoine
988011475c STM32 Serial port handling for WiFi 2023-06-20 08:34:27 +08:00
peteGSX
0be9af2270 Update some logic 2023-06-20 09:37:19 +10:00
pmantoine
c3eb6b8d8a STM32 ADCee highestPin 2023-06-19 17:47:42 +08:00
Harald Barth
08c00d275d Merge branch 'devel' into devel-overcurrent 2023-06-19 08:58:32 +02:00
Harald Barth
1888073dc2 set lastPowerChange we doing the power on retry after overload 2023-06-19 08:43:50 +02:00
Harald Barth
6dd175f63b fix power change timer micros overflow 2023-06-19 00:33:53 +02:00
peteGSX
ae54a747bb Merge branch 'rotary-encoder-send-position' of https://github.com/DCC-EX/CommandStation-EX into rotary-encoder-send-position 2023-06-19 08:26:08 +10:00
peteGSX
955ff4f96d Add _writeAnalogue() 2023-06-19 08:26:00 +10:00
peteGSX
0cf81d589e Add _writeAnalogue() 2023-06-19 08:25:20 +10:00
Harald Barth
d5dad767a4 version 4.2.55 2023-06-19 00:09:27 +02:00
Harald Barth
56fcb4e5f7 Optimize DCCTimerARV.cpp 2023-06-19 00:06:04 +02:00
Harald Barth
7783837545 Back out this as it is bigger and slower
This reverts commit efb2666060.
2023-06-18 21:08:52 +02:00
Harald Barth
0266936875 STM32: Use mask as loop variable 2023-06-18 20:22:53 +02:00
Harald Barth
befb41ce98 check ADCee::init() return value 2023-06-18 20:20:58 +02:00
Harald Barth
f83be05220 STM32: Use mask as loop variable 2023-06-18 19:26:38 +02:00
Harald Barth
cade89ba16 check ADCee::init() return value 2023-06-18 09:48:15 +02:00
Harald Barth
277825c530 versiontag 2023-06-18 09:01:32 +02:00
Harald Barth
95fe7aafe0 overload detection code cleanup 2023-06-18 08:59:37 +02:00
Harald Barth
f99deb4276 overload detection different timestamps and verbose diag 2023-06-14 22:57:28 +02:00
Harald Barth
f5d4dcb97c new overload detection 2023-06-14 00:58:02 +02:00
Harald Barth
8a69403dda devel release version 4.2.54 2023-06-03 22:01:14 +02:00
Harald Barth
e81d1cc93a Better warnings for pin number errors 2023-05-29 09:48:22 +02:00
Harald Barth
82929245ed char * / flashstring conflict 2023-05-25 14:02:28 +02:00
Harald Barth
db0e0cbf8b Send default function list in jR as well 2023-05-25 10:29:01 +02:00
Harald Barth
803b996e0b Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-05-25 09:29:47 +02:00
Harald Barth
5607ff7167 version 2023-05-25 09:29:42 +02:00
Harald Barth
58e62aaa81 Bug: Withrottle roster list returning empty string vs NULL 2023-05-25 09:27:41 +02:00
Harald Barth
5a9adea2b6 Bug: Withrottle roster list end was not detected 2023-05-25 08:05:07 +02:00
pmantoine
bf136d49e0 Fix Serial ports for Nucleo-144 boards 2023-05-25 08:34:20 +08:00
Harald Barth
50313ebbd2 cast to big enough type 2023-05-24 22:58:21 +02:00
Harald Barth
72bfc6abc7 INT16_MAX missing again 2023-05-24 22:57:43 +02:00
Harald Barth
342d9263d1 time stamp 2023-05-24 13:32:34 +02:00
Harald Barth
20d66fad4e Routes, automations and roster lists: Exclude ID 0 to be presented as available 2023-05-24 13:31:18 +02:00
Harald Barth
be4235e792 Arduino Mega2560: Use timer5 as timer4 for PWM DC 2023-05-24 13:29:20 +02:00
Harald Barth
c22d789513 INT16_MAX was missing at more places 2023-05-23 18:59:03 +02:00
Harald Barth
951a6637f0 INT16_MAX is a better end of array marker than -1 2023-05-23 10:57:45 +02:00
Harald Barth
fdbcbdf418 Do not send default roster entry on withrottle but on JR 2023-05-22 22:51:35 +02:00
Harald Barth
9478c3263d Try to find default roster entry 2023-05-22 16:39:24 +02:00
Harald Barth
16f94ecbdc Restict where what SerialX is used 2023-05-21 20:20:32 +02:00
Harald Barth
b80d7bd517 Pin handling supports pins up to 254 2023-05-21 11:54:46 +02:00
Harald Barth
8786285624 Assume that we have enough HW serials 2023-05-20 23:57:17 +02:00
Harald Barth
132b0773ef Fault pin handling made more straight forward 2023-05-20 23:15:15 +02:00
Harald Barth
99521f8a3f Support DCC-EX shield 2023-05-20 17:35:09 +02:00
pmantoine
1a3d295564 Nucleo-F446RE and other serial port updates. 2023-05-20 21:50:20 +08:00
Harald Barth
3b6789ef01 Merge branch 'devel-sabertooth' into devel 2023-05-20 14:59:07 +02:00
Harald Barth
c472f48d93 Experimental support for sabertooth motor controller on ESP32 2023-05-20 14:57:00 +02:00
pmantoine
94e9c2021b Fix config.example.h OLED_DRIVER #define 2023-05-15 11:31:44 +08:00
Harald Barth
9aad2e3206 save another 2 bytes in turnouts if eeprom is disabled 2023-05-09 14:17:30 +02:00
Harald Barth
ecc366cbd1 Merge branch '332-feature-request-add-a-no-programming-option-to-save-ram-on-uno' into devel 2023-05-09 14:11:18 +02:00
pmantoine
f4e3ca7c81 EX8874 entry for SAMD/STM32 2023-05-08 08:32:47 +08:00
Harald Barth
991bda63e0 update to production shield factors 2023-05-08 00:35:00 +02:00
Harald Barth
5164bd143c versions 2023-05-08 00:22:31 +02:00
Harald Barth
3759fc2a1a add checks for broken cab ID 2023-05-08 00:19:59 +02:00
Harald Barth
df7b890758 No EEPROM so you do not need this 2023-05-07 23:58:47 +02:00
Harald Barth
6d802f3a73 estop all locos in list, even last one 2023-05-05 16:14:44 +02:00
Harald Barth
9d953c70b8 use M1 and M2 instead of MD for motor control 2023-05-02 23:51:17 +02:00
Asbelos
6781e44fdd Fix EXRAIL speed issue 2023-05-02 22:02:52 +01:00
Asbelos
a3c9800aba Update version.h 2023-05-02 12:29:03 +01:00
Harald Barth
efdbfcb030 Add serial output for sabertooth controller 2023-05-01 20:18:32 +02:00
Asbelos
28d9843133 Broadcast changes in EXRAIlr 2023-05-01 14:25:45 +01:00
mstevetodd
fcf05206b4 Merge pull request #333 from mstevetodd/master
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:06:10 -04:00
stevet
cc3aba1feb Update WiThrottle.cpp
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:02:42 -04:00
Harald Barth
4eaad2d05b disable more PROG stuff (JOIN/UNJOIN from EXRAIL) 2023-04-23 22:45:47 +02:00
Harald Barth
72d131035e disable more PROG stuff (all hash keywords PROG etc) 2023-04-23 20:24:29 +02:00
peteGSX
2d1e695ac7 Disable <D ACK> 2023-04-20 08:26:17 +10:00
peteGSX
e780c40b34 Disable POM OPCODE 2023-04-20 08:16:32 +10:00
peteGSX
7addb13785 Disable <R> completely 2023-04-20 07:21:32 +10:00
peteGSX
b6f8889e8c Disable most programming functions 2023-04-20 07:08:11 +10:00
Colin Murdoch
33306219c8 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-04-19 19:46:12 +01:00
Colin Murdoch
d857c4f2e4 Added to Copyright notice
Added my name to copyright notice
2023-04-19 19:45:40 +01:00
Asbelos
70fae16ab3 Correct response to <JA 0> 2023-04-19 11:18:47 +01:00
Harald Barth
f465020e93 Support boards with inverted fault pin 2023-04-17 23:40:48 +02:00
Harald Barth
235bcc9ff0 Merge branch 'devel' into devel-invfault 2023-04-17 23:20:01 +02:00
pmantoine
d2cc60812d Merge ESP32-fixes into DCCTimerESP 2023-04-16 15:40:27 +08:00
pmantoine
75f274e3b7 STM32 unsupported board selection error reporting 2023-04-13 15:27:22 +08:00
Colin Murdoch
ff53b90034 Update version.h
Added ONCLOCKMINS for FastClock
2023-04-12 12:11:43 +01:00
Colin Murdoch
1daa0a9ba9 Merge branch 'devel-plus-fastclockaddons' into devel 2023-04-12 12:09:27 +01:00
Colin Murdoch
294b9693c5 Additions to FastClock
Added ONCLOCKMINS to fastclock to allow hourly repeated events.
2023-04-12 12:07:08 +01:00
Harald Barth
16e44eb11a Check for max 16 analog channels 2023-04-11 12:13:47 +02:00
pmantoine
e51f8e9c0a STM32 I2C Clock selection for 100/400KHz 2023-04-11 15:48:35 +08:00
peteGSX
4bad334875 Update version 2023-04-11 12:01:25 +10:00
peteGSX
32491854ff Merge pull request #330 from DCC-EX:powershell-installer
Powershell-installer
2023-04-11 11:59:28 +10:00
peteGSX
3e95372816 Rename installer exe 2023-04-11 11:48:35 +10:00
peteGSX
05b0fc3d2e Add exe version 2023-04-11 10:26:15 +10:00
peteGSX
bb7cdc5422 WiFi AP mode compiles 2023-04-11 09:42:54 +10:00
peteGSX
6199cecd42 0.0.7 Ready for testing 2023-04-11 08:55:04 +10:00
peteGSX
1aae0aed0a Working on config output 2023-04-11 05:31:44 +10:00
peteGSX
1d29be9de6 Working on 0.0.7 2023-04-10 19:52:31 +10:00
Harald Barth
bfa33a9df7 Fix STM32 set right port mode bits for analog port 2023-04-10 01:47:00 +02:00
Harald Barth
49c0a1a55a Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-04-08 23:35:48 +02:00
Harald Barth
9b7d1ae858 STM32: Use predefined function for pinnames 2023-04-08 23:33:33 +02:00
Colin Murdoch
c11d8f6359 Add TURNOUTL Macro to EXRAIL
Add TURNOUTL Macro to EXRAIL and retrospective update to version.h to record addition of EX-FastClock mods.
2023-04-08 17:22:39 +01:00
pmantoine
3868bb19ac More uint type fixes 2023-04-08 14:51:10 +08:00
pmantoine
7589917638 STM32 ADCee fix 2023-04-08 14:15:38 +08:00
peteGSX
e7d9626a72 Fix CLI directory name 2023-04-08 08:39:42 +10:00
peteGSX
fe035f4096 Move from temp to user home dir 2023-04-08 08:38:02 +10:00
peteGSX
4d236446b0 Add core index update 2023-04-08 06:57:09 +10:00
peteGSX
a100d709ce Install core library, add batch wrapper 2023-04-08 06:22:09 +10:00
Harald Barth
0d82370380 devel date 2023-04-07 20:46:20 +02:00
Harald Barth
ff6034dff2 curl only needed when downloading 2023-04-07 20:45:21 +02:00
Harald Barth
5d0de6b807 platformio wants this 2023-04-07 20:44:40 +02:00
peteGSX
2e518fcac2 Enable using existing configs 2023-04-07 19:30:15 +10:00
peteGSX
be273454bc Merge branch 'powershell-installer' of https://github.com/DCC-EX/CommandStation-EX into powershell-installer 2023-04-07 19:04:03 +10:00
peteGSX
751b46b6bb Initial test working! 2023-04-07 19:03:51 +10:00
peteGSX
91bc9df44e Working on CLI commands 2023-04-07 19:03:51 +10:00
peteGSX
72ceb63913 Start getting tag list 2023-04-07 19:03:50 +10:00
peteGSX
d2c7e7fb8d Working on logic 2023-04-07 19:03:50 +10:00
peteGSX
61db37c7ea Got tag version and URL 2023-04-07 19:03:50 +10:00
peteGSX
3a3071f35b Got tag list 2023-04-07 19:03:50 +10:00
peteGSX
ef3d36ae25 Figuring out commands 2023-04-07 19:03:50 +10:00
pmantoine
c3d2e5b222 Fix to PIO build target names for Teensy 2023-04-07 13:59:49 +10:00
peteGSX
9f212c27bf Initial test working! 2023-04-07 08:06:32 +10:00
peteGSX
de06c0ae3e Working on CLI commands 2023-04-06 15:06:22 +10:00
Asbelos
273f55b143 4.2.41 Hal setup and DNOU8 fix 2023-04-05 23:19:43 +01:00
peteGSX
86cb8f4666 Merge pull request #327 from DCC-EX:auto-disable-default-i2c
Added disable logic
2023-04-06 07:09:32 +10:00
peteGSX
9571088e1b Added disable logic 2023-04-06 07:03:59 +10:00
peteGSX
18a992bf08 Start getting tag list 2023-04-06 05:31:11 +10:00
peteGSX
de6e91a778 Working on logic 2023-04-05 15:54:48 +10:00
peteGSX
b18df1405c Got tag version and URL 2023-04-05 05:30:00 +10:00
peteGSX
305e0902f4 Got tag list 2023-04-04 19:30:59 +10:00
peteGSX
95c1b1da31 Figuring out commands 2023-04-04 15:48:11 +10:00
Neil McKechnie
1b4faa92cd Update IO_DFPlayer.h
Reinstate STOP command in place of PAUSE, as PAUSE was being reported differently to STOP in the status response.
2023-03-31 17:58:30 +01:00
Neil McKechnie
6fbaca7930 Update IO_DFPlayer.h
Ensure device goes off-line when not responding.
2023-03-31 16:50:18 +01:00
Neil McKechnie
6b535654f8 DFplayer driver now polls device to detect failures and errors.
Add cyclic (1-second) poll of DFplayer device to detect if it goes unresponsive.
2023-03-31 16:40:40 +01:00
Neil McKechnie
4f43a413b5 Update I2CManager_STM32.h
Remove debug code (writing to pin D2).  Update comments.  Restructure.
2023-03-30 18:30:38 +01:00
peteGSX
2c943d250e Merge pull request #326 from DCC-EX:287-to-do-clean-up-rotary-encoder-device-driver-compile-time-warning
Cleaned up warning
2023-03-30 07:01:40 +10:00
peteGSX
89664eff9d Cleaned up warning 2023-03-30 06:54:18 +10:00
Neil McKechnie
4f56837d28 Fixes to timeout handling (due to STM32 micros() difference). 2023-03-28 18:07:52 +01:00
f8b054cf6a [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing (#325)
* [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing while used in combination with the standard Motor Shield
* Update version.h changelog
2023-03-27 10:44:47 -04:00
Neil McKechnie
d2a8aebd0f Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-27 13:08:19 +01:00
Neil McKechnie
86c3020672 Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:08:14 +01:00
Neil McKechnie
60ea7f081a Correct display of high VPIN numbers in diagnostic output.
No functional change.
VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE).  Values above 32767 were erroneously being displayed as negative.  This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used.
2023-03-27 13:03:19 +01:00
Neil McKechnie
f348857ddb Add FLAGS device for EX-RAIL state communications. Improve VPIN display in messages.
FLAGS HAL device added to IODevice.h, which allows use of SET/RESET/<Z>/<T> to set and reset a VPIN state, and to allow <S>/IF/IFNOT/AT/WAITFOR/etc. to monitor the VPIN state.
Also, correct handling of VPINs above 32767 in DIAG calls within IODevice.cpp and IODevice.h.
2023-03-27 12:39:11 +01:00
Neil McKechnie
cc2846d932 STM32 Native I2C first working version
Working for reads and writes, needs more testing and perhaps a polish.
2023-03-27 00:20:59 +01:00
Harald Barth
bdd4bc9999 version 2023-03-25 22:26:57 +01:00
Harald Barth
8a425fe0ef do not broadcast a turnout state that has not changed 2 2023-03-25 19:28:37 +01:00
Harald Barth
1ec378281b do not broadcast a turnout state that has not changed 2023-03-25 12:14:58 +01:00
Asbelos
51a480dff3 doc typo only 2023-03-24 00:24:03 +00:00
Asbelos
f0ee8aeb85 z Commands 2023-03-23 19:52:49 +00:00
pmantoine
83325ebf78 Initial I2C native driver 2023-03-23 08:44:25 +11:00
peteGSX
94d0aa92d9 Merge pull request #324 from DCC-EX:fix-analogue-input-bug
Fix-analogue-input-bug
2023-03-21 07:09:52 +10:00
peteGSX
82c447e8a4 Merge branch 'fix-analogue-input-bug' of https://github.com/DCC-EX/CommandStation-EX into fix-analogue-input-bug 2023-03-21 07:04:19 +10:00
peteGSX
a3d03ac68c Fix validated, update version 2023-03-21 07:04:08 +10:00
peteGSX
b183439a5b Using correct size for memcpy 2023-03-21 07:03:23 +10:00
Harald Barth
d51281f1f2 github tag 2023-03-20 21:24:42 +01:00
Harald Barth
168368864c Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-20 21:23:02 +01:00
Harald Barth
a75ca00e3c exchange pin number to track letter part 2 2023-03-20 21:22:48 +01:00
peteGSX
7ab5f556d9 Using correct size for memcpy 2023-03-21 05:30:33 +10:00
peteGSX
337bd969a1 Merge pull request #323 from DCC-EX:ex-io-analogue-input-bug
Fixed non-working analogue inputs
2023-03-20 19:11:59 +10:00
Harald Barth
21c99c8694 improve Wifi connect messages 2023-03-20 10:11:14 +01:00
peteGSX
4087cd6e29 Fixed non-working analogue inputs 2023-03-20 19:05:53 +10:00
Harald Barth
2fb485847f exchange pin number to track letter 2023-03-20 10:03:02 +01:00
Neil McKechnie
43c7baf8f5 Fix display scrolling on LCD and OLED
Eliminate spurious blanking of screen in mode 1, duplicated lines of text in mode 2, and non-display of more than the first screen-full of lines in mode 0.
2023-03-19 22:06:02 +00:00
Neil McKechnie
2e1a2d38e3 Update IO_EXIOExpander.h
Reinstate byte-wise processing of analogue input values.
2023-03-19 01:20:20 +00:00
Asbelos
fe18341994 Update myAutomation.example.h
better example with power on
2023-03-18 18:53:48 +00:00
Asbelos
329dc41452 Remove implicit AUTOSTART 2023-03-18 18:52:01 +00:00
Neil McKechnie
c4b4e11a67 Update IO_EXIOExpander.h
Avoid repeated error messages for a single fault.
2023-03-18 15:30:14 +00:00
Neil McKechnie
e55dc51bdb EX-IOExpander updates 2023-03-18 15:05:21 +00:00
Neil McKechnie
5dd2770442 Update IO_HCSR04.h
Modify mode of measurement so that the driver doesn't loop for long periods waiting for the incoming pulse to complete.   Original loop behaviour can be reinstated by adding LOOP option in create call (see comment header in file).
2023-03-17 21:15:26 +00:00
Neil McKechnie
d67b07fe46 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-15 18:10:30 +00:00
Neil McKechnie
3073061596 Update IO_DFPlayer.h
Bugfix, volume not working correctly.
2023-03-15 18:10:27 +00:00
Harald Barth
278347756a Bugfix Scroll LCD without empty lines and consistent 2023-03-15 16:41:02 +01:00
Neil McKechnie
3d35e78533 Update version.h 2023-03-15 09:39:30 +00:00
Neil McKechnie
75b5806eb7 Shorten I2C error message 2023-03-15 09:39:20 +00:00
Neil McKechnie
4a1210fa64 Remove HA mode from STM32
In some pin configurations for DC track mode, the use of analogWrite will conflict with other timer uses including HA mode.
 Consequently, the HA mode support has been temporarily removed pending a suitable solution for this.  Original use of Timer11 has been reinstated.
2023-03-15 09:31:54 +00:00
Neil McKechnie
72c76391a5 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-15 09:21:24 +00:00
Neil McKechnie
325d4bce73 Update IO_DFPlayer.h
Rework delay between command: instead of sending null characters, which doesn't work on some chip sets, send commands from the _loop() function with a 100ms delay between consecutive commands.
2023-03-15 09:20:42 +00:00
Harald Barth
27ba551986 Bugfix LCD showed random characters in SCROLLMODE 2 2023-03-14 20:50:24 +01:00
peteGSX
ce12f3b6c5 Merge pull request #320 from DCC-EX:ex-io-driver-updates
Ex-io-driver-updates
2023-03-14 19:10:23 +10:00
peteGSX
48cd567bda Update version after testing 2023-03-14 19:04:08 +10:00
peteGSX
25676aab6b Update comments 2023-03-14 07:32:08 +10:00
peteGSX
42bddb587e Update .gitignore 2023-03-14 07:25:30 +10:00
peteGSX
a51cefbdeb Delete settings.json 2023-03-14 07:23:55 +10:00
peteGSX
5fc925bfc6 Delete extensions.json 2023-03-14 07:23:42 +10:00
peteGSX
ca2e5e6ce3 Undo vscode change 2023-03-14 07:21:55 +10:00
peteGSX
9728e15e0a Merge branch 'ex-io-driver-updates' of https://github.com/DCC-EX/CommandStation-EX into ex-io-driver-updates 2023-03-14 07:20:46 +10:00
peteGSX
c83741d2b4 Add read refresh delays 2023-03-14 07:20:27 +10:00
peteGSX
df3eb11eb9 Add read refresh delays 2023-03-14 07:19:20 +10:00
Asbelos
d89dd0d1fa command ref work in progress 2023-03-13 00:53:42 +00:00
peteGSX
95d0120204 Implement status checks 2023-03-13 08:38:28 +10:00
peteGSX
0cc07ed1df Starting on driver feedback 2023-03-13 05:29:22 +10:00
Asbelos
5e60fb4e27 SAMD21 odd byte boundary 2023-03-11 22:46:11 +00:00
Harald Barth
881463ada9 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-10 17:52:16 +01:00
Harald Barth
45cf610028 Bugfix Ethernet shield: Static IP now possible 2023-03-10 17:49:51 +01:00
Neil McKechnie
471b8ac8e1 Update I2CManager.cpp
Rearrange I2C short-circuit check to before I2C is initialised.
2023-03-09 16:28:07 +00:00
Neil McKechnie
3ae1859ec7 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-09 16:01:10 +00:00
Neil McKechnie
679e5885c4 Update IO_HALDisplay.h
Enable I2C address overlap check on HAL display.
2023-03-09 15:59:52 +00:00
Neil McKechnie
59c6c1e5af Update examples and comments. 2023-03-09 15:59:25 +00:00
Harald Barth
f94a5f971e Bugfix signalpin2 was not set up in shadow port 2023-03-07 16:20:00 +01:00
Asbelos
1d29436008 Compiler pedantics 2023-03-06 13:47:59 +00:00
Asbelos
bec8aea5a5 TM Broadcasts
TM changes will trigger TM state broadcasts
2023-03-06 11:57:14 +00:00
peteGSX
4f23dbc984 Merge pull request #315 from DCC-EX:47-to-do-update-device-driver-to-use-non-blocking-i2c
Non-blocking implemented
2023-03-04 19:02:11 +10:00
peteGSX
46070e2999 Non-blocking implemented 2023-03-04 18:55:13 +10:00
Fred
91d36ae909 Update ThrottleAssists.md 2023-03-03 21:59:18 -05:00
Fred
98af5c45ed Update ThrottleAssists.md 2023-03-03 21:46:07 -05:00
Fred
d3eceb6d6c Update ThrottleAssists.md 2023-03-03 21:41:22 -05:00
Fred
79eaaa85fa Update ThrottleAssists.md
Fixing formatting
2023-03-03 21:35:13 -05:00
Fred
0f5b8adb6b Update ThrottleAssists.md 2023-03-03 21:26:46 -05:00
Fred
da8faa808b Update ThrottleAssists.md 2023-03-03 21:07:58 -05:00
Harald Barth
31ecba08d8 faultPin can be inverted (from its inverted sense 2023-03-03 20:51:32 +01:00
Neil McKechnie
22c0bff697 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-03-02 23:01:47 +00:00
Neil McKechnie
19bbb186e7 Create IO_TouchKeypad.h
Driver for 16-pad capacitative key pad device (TTP229-B based).
2023-03-02 23:01:44 +00:00
Asbelos
a17a55d904 Implement <JG> <JI> 2023-03-02 13:03:05 +00:00
Asbelos
b969563d35 Squashed commit of the following:
commit 4d8efcdd05
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Mar 1 16:32:05 2023 +0000

    Reinstate obsolete <s>

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

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

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

    G and I commands
2023-03-02 12:56:30 +00:00
Asbelos
f0c1ea958c 4.2.19 sensorOffset bugfix 2023-03-02 10:45:39 +00:00
Neil McKechnie
ca4592dc3e Reinstate original #ifdef DIAG_IO tests in servo modules 2023-03-01 23:09:27 +00:00
Neil McKechnie
0663cc6138 Update IO_EXIOExpander.h
_I2CAddress or _i2cAddress - the checkOverlap function uses the former, and the driver uses the latter.  I incorrectly used the wrong one!
2023-02-28 23:49:51 +00:00
Neil McKechnie
5fb10fa6d0 Delete IO_ExternalEEPROM.h 2023-02-28 20:09:37 +00:00
Harald Barth
1d47dce473 update tag 2023-02-28 15:22:57 +01:00
Harald Barth
8a126906f3 Only need do anything if cab existed 2023-02-28 15:20:29 +01:00
Harald Barth
b01e4388ce Small typo in tone scale 2023-02-28 15:17:04 +01:00
Colin Murdoch
4adcdc1b0b Update config.example.h
Removed redundant define for FastClock no longer required.
2023-02-28 12:12:08 +00:00
Neil McKechnie
95b640686a Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-02-25 11:43:39 +00:00
Neil McKechnie
f56f5d9ebe Update I2CManager_Wire.h
Add alternative multiplexer support for I2C Wire subsystem.
2023-02-25 11:43:36 +00:00
Neil McKechnie
8c8a913678 Update IO_HALDisplay.h
Check for memory allocation errors.
2023-02-25 11:43:05 +00:00
Neil McKechnie
711ad6f030 Update SSD1306Ascii.cpp
Add remaining extended graphics characters (still no international characters though.
2023-02-25 11:42:43 +00:00
Neil McKechnie
c2983efebb Update to comments 2023-02-25 11:42:12 +00:00
Asbelos
02ee8ad080 Removed duplicate DIAG 2023-02-24 16:56:21 +00:00
Neil McKechnie
780c6ea162 Revert "Update DisplayInterface.cpp"
This reverts commit a23f5839b7.
2023-02-24 09:39:00 +00:00
Neil McKechnie
8582e51483 Change example EXRAIL DFplayer calls from SERVO to ANOUT. 2023-02-23 20:57:25 +00:00
Neil McKechnie
cdf3927aad Update I2CManager.h
Make getMuxCount() publicly visible.
2023-02-23 20:52:40 +00:00
Neil McKechnie
ddfc67d2e3 Remove files that were incorrectly included in the merge. 2023-02-23 20:52:13 +00:00
Neil McKechnie
a23f5839b7 Update DisplayInterface.cpp
Remove dummy display driver object - it's unnecessary now as a null pointer is benign in this context.
2023-02-23 20:24:42 +00:00
Neil McKechnie
eddd6382d9 Update IO_EXFastclock.h
Update to support extended I2C addresses.
2023-02-23 20:23:08 +00:00
Neil McKechnie
45e3e3d185 Update IO_RotaryEncoder.h
Update to support extended I2C addresses.
2023-02-23 20:17:15 +00:00
Neil McKechnie
5e9ae4a0ac Remove redundant commented out code. 2023-02-23 20:16:29 +00:00
Neil McKechnie
39d953ed29 Merge pull request #312 from DCC-EX/devel-nmck
Merge Devel-nmck:
I2C Multiplexer support through Extended Addresses added for Wire, 4209 and AVR I2C drivers.
I2C retries when an operation fails.
I2C timeout handling and recovery completed.
I2C SAMD Driver Read code completed.
PCF8575 I2C GPIO driver added.
EX-RAIL ANOUT function for triggering analogue HAL drivers (e.g. analogue outputs, DFPlayer, PWM).
EX-RAIL SCREEN function for writing to screens other than the primary one.
Installable HALDisplay Driver, with support for multiple displays.
Layered HAL Drivers PCA9685pwm and Servo added for native PWM on PCA9685 module and for animations of servo movement via PCA9685pwm. This is intended to support EXIOExpander and also replace the existing PCA9685 driver.
Add <D HAL RESET> to reinitialise failed drivers (it calls the _begin method of all HAL drivers to reinitialise them).
Add UserAddin facility to allow a user-written C++ function to be declared in myHal.cpp, to be called at a user-specified frequency.
Add ability to configure clock speed of PCA9685 drivers (to allow flicker-free LED control).
Improve stability of VL53L0X driver when XSHUT pin connected.
Enable DCC high accuracy mode for STM32 on standard motor shield (pins D12/D13).
Incorporate improvements to ADC scanning performance (courtesy of HABA).
2023-02-23 11:20:17 +00:00
Harald Barth
7311f2ce64 LCN bugfix 2023-02-12 20:38:03 +01:00
Harald Barth
7e4f9eb0e1 jT answer should contain empty string 2023-01-29 11:33:28 +01:00
Harald Barth
1f5eafbcca Bugfix for issue #299 TurnoutDescription NULL 2023-01-29 11:32:54 +01:00
peteGSX
7e16ec7088 Fix support request issue template 2022-11-05 05:17:03 +10:00
Harald Barth
912646f8ff Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX into HEAD 2022-11-04 15:41:05 +01:00
Harald Barth
dd309a3705 Ethernet init order 2022-11-04 15:39:35 +01:00
peteGSX
5376c9f410 Update project workflow for forks 2022-11-04 06:54:49 +10:00
peteGSX
b1d110ecbf Fix project workflow 2022-11-03 14:06:43 +10:00
Fred
5b7801ca6c Update version.h 2022-10-28 14:05:35 -04:00
Fred
aca9c9c941 Update release_notes.md 2022-10-28 10:52:12 -04:00
Fred
6f94cd71ab Update release_notes_v4.1.2.md 2022-10-28 10:50:35 -04:00
Fred
1827a11f83 Update release_notes_v4.1.1.md 2022-10-28 10:49:32 -04:00
Fred
0023ce3356 Create release_notes_v4.1.2.md 2022-10-28 10:48:40 -04:00
Fred
7b9f3ae08d Update release_notes.md 2022-10-28 10:39:13 -04:00
Fred
5e50731a78 Update version.h
Fix version number in notes from 4.2.1 to 4.1.2
2022-10-28 10:28:54 -04:00
Harald Barth
df6c511d1d Fix for W5100 ethernet shield which does not report as the W5200 or W5500 2022-10-28 13:24:12 +02:00
peteGSX
4bfd4b1a12 Add templates and project workflow (#258)
* Add templates and project workflow

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

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

* Update release_notes_v4.1.1.md

added <t cab> back in

* Update release_notes_v4.1.1.md

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

Edited Intro and Rearranged EXRAIL Enhancements

* Update release_notes_v4.1.1.md

edited indents

* Update release_notes_v4.1.1.md

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

10
.gitignore vendored
View File

@@ -7,15 +7,11 @@ Release/*
.pio/
.vscode/
config.h
.vscode/*
# mySetup.h
mySetup.cpp
myHal.cpp
# myAutomation.h
myFilter.cpp
# myAutomation.h
# myLayout.h
my*.h
!my*.example.h
.vscode/extensions.json
.vscode/extensions.json
compile_commands.json
newcode.txt.old
UserAddin.txt

View File

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

12
.vscode/settings.json vendored
View File

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

View File

@@ -2,6 +2,7 @@
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -104,6 +105,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
void CommandDistributor::forget(byte clientId) {
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE;
if (virtualLCDClient==clientId) virtualLCDClient=RingStream::NO_CLIENT;
}
#endif
@@ -160,6 +162,10 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
#endif
}
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
}
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
@@ -167,7 +173,7 @@ void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// 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);
broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate);
#endif
}
@@ -179,10 +185,7 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte
{
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);
}
// CAH. DIAG removed because LCD does it anyway.
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time
RMFT2::clockEvent(clocktime,1);
@@ -207,12 +210,50 @@ int16_t CommandDistributor::retClockTime() {
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);
#ifdef SABERTOOTH
if (Serial2 && sp->loco == SABERTOOTH) {
static uint8_t rampingmode = 0;
bool direction = (sp->speedCode & 0x80) !=0; // true for forward
int32_t speed = sp->speedCode & 0x7f;
if (speed == 1) { // emergency stop
if (rampingmode != 1) {
rampingmode = 1;
Serial2.print("R1: 0\r\n");
Serial2.print("R2: 0\r\n");
}
Serial2.print("MD: 0\r\n");
} else {
if (speed != 0) {
// speed is here 2 to 127
speed = (speed - 1) * 1625 / 100;
speed = speed * (direction ? 1 : -1);
// speed is here -2047 to 2047
}
if (rampingmode != 2) {
rampingmode = 2;
Serial2.print("R1: 2047\r\n");
Serial2.print("R2: 2047\r\n");
}
Serial2.print("M1: ");
Serial2.print(speed);
Serial2.print("\r\n");
Serial2.print("M2: ");
Serial2.print(speed);
Serial2.print("\r\n");
}
}
#endif
#ifdef CD_HANDLE_RING
WiThrottle::markForBroadcast(sp->loco);
#endif
}
void CommandDistributor::broadcastPower() {
char pstr[] = "? x";
for(byte t=0; t<TrackManager::MAX_TRACKS; t++)
if (TrackManager::getPower(t, pstr))
broadcastReply(COMMAND_TYPE, F("<p%s>\n"),pstr);
bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
bool join=TrackManager::isJoined();
@@ -230,9 +271,66 @@ void CommandDistributor::broadcastPower() {
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
}
void CommandDistributor::broadcastText(const FSH * msg) {
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
#endif
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(type, F("%s"),msg);
}
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr);
}
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
}
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
}
Print * CommandDistributor::getVirtualLCDSerial(byte screen, byte row) {
Print * stream=virtualLCDSerial;
#ifdef CD_HANDLE_RING
rememberVLCDClient=RingStream::NO_CLIENT;
if (!stream && virtualLCDClient!=RingStream::NO_CLIENT) {
// If we are broadcasting from a wifi/eth process we need to complete its output
// before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client.
if ((rememberVLCDClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
ring->commit();
}
ring->mark(virtualLCDClient);
stream=ring;
}
#endif
if (stream) StringFormatter::send(stream,F("<@ %d %d \""), screen,row);
return stream;
}
void CommandDistributor::commitVirtualLCDSerial() {
#ifdef CD_HANDLE_RING
if (virtualLCDClient!=RingStream::NO_CLIENT) {
StringFormatter::send(ring,F("\">\n"));
ring->commit();
if (rememberVLCDClient!=RingStream::NO_CLIENT) ring->mark(rememberVLCDClient);
return;
}
#endif
StringFormatter::send(virtualLCDSerial,F("\">\n"));
}
void CommandDistributor::setVirtualLCDSerial(Print * stream) {
#ifdef CD_HANDLE_RING
virtualLCDClient=RingStream::NO_CLIENT;
if (stream && stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) {
virtualLCDClient=((RingStream *) stream)->peekTargetMark();
virtualLCDSerial=nullptr;
return;
}
#endif
virtualLCDSerial=stream;
}
Print* CommandDistributor::virtualLCDSerial=nullptr;
byte CommandDistributor::virtualLCDClient=0xFF;
byte CommandDistributor::rememberVLCDClient=0;

View File

@@ -2,6 +2,8 @@
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* © 2022 Colin Murdoch
*
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -33,8 +35,9 @@
#endif
class CommandDistributor {
private:
public:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
private:
static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter;
#ifdef CD_HANDLE_RING
@@ -46,14 +49,26 @@ public :
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastTurntable(int16_t id, uint8_t position, bool moving);
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);
static void broadcastRaw(clientType type,char * msg);
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
static void broadcastRouteState(uint16_t routeId,byte state);
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
// Handling code for virtual LCD receiver.
static Print * getVirtualLCDSerial(byte screen, byte row);
static void commitVirtualLCDSerial();
static void setVirtualLCDSerial(Print * stream);
private:
static Print * virtualLCDSerial;
static byte virtualLCDClient;
static byte rememberVLCDClient;
};
#endif

View File

@@ -30,6 +30,7 @@
* © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -75,6 +76,15 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
// 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();
// Set up MotorDrivers early to initialize all pins
TrackManager::Setup(MOTOR_SHIELD_TYPE);
DISPLAY_START (
// This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION));
@@ -86,29 +96,19 @@ void setup()
// Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // WIFI_ON
#else
// ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// 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
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
TrackManager::Setup(MOTOR_SHIELD_TYPE);
DCC::begin();
// Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin();

13
DCC.cpp
View File

@@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
byte DCC::globalSpeedsteps=128;
void DCC::begin(const FSH * motorShieldName) {
shieldName=(FSH *)motorShieldName;
void DCC::begin() {
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
#ifndef DISABLE_EEPROM
// Load stuff from EEprom
@@ -576,9 +575,11 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track
int reg=lookupSpeedTable(cab, false);
if (reg>=0) {
speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track
}
}
void DCC::forgetAllLocos() { // removes all speed reminders
setThrottle2(0,1); // ESTOP all locos still on track
@@ -691,7 +692,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// broadcast stop/estop but dont change direction
for (int reg = 0; reg < highestUsedReg; reg++) {
for (int reg = 0; reg <= highestUsedReg; reg++) {
if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {

5
DCC.h
View File

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

View File

@@ -152,7 +152,7 @@ byte DCCACK::getAck() {
return(0); // pending set off but not detected means no ACK.
}
#ifndef DISABLE_PROG
void DCCACK::loop() {
while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg);
@@ -351,7 +351,7 @@ void DCCACK::callback(int value) {
switch (callbackState) {
case AFTER_READ:
if (ackManagerRejoin && autoPowerOff) {
if (ackManagerRejoin && !autoPowerOff) {
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
@@ -414,7 +414,7 @@ void DCCACK::callback(int value) {
(ackManagerCallback)( value);
}
}
#endif
void DCCACK::checkAck(byte sentResetsSincePacket) {
if (!ackPending) return;

View File

@@ -3,10 +3,11 @@
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Herb Morton
* © 2020-2022 Harald Barth
* © 2020-2023 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -24,6 +25,79 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
List of single character OPCODEs in use for reference.
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
Once a new OPCODE is decided upon, update this list.
Character, Usage
/, |EX-R| interactive commands
-, Remove from reminder table
=, |TM| configuration
!, Emergency stop
@, Reserved for future use - LCD messages to JMRI
#, Request number of supported cabs/locos; heartbeat
+, WiFi AT commands
?, Reserved for future use
0, Track power off
1, Track power on
a, DCC accessory control
A,
b, Write CV bit on main
B, Write CV bit
c, Request current command
C, configure the CS
d,
D, Diagnostic commands
e, Erase EEPROM
E, Store configuration in EEPROM
f, Loco decoder function control (deprecated)
F, Loco decoder function control
g,
G,
h,
H, Turnout state broadcast
i, Server details string
I, Turntable object command, control, and broadcast
j, Throttle responses
J, Throttle queries
k, Reserved for future use - Potentially Railcom
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L, Reserved for LCC interface (implemented in EXRAIL)
m,
M, Write DCC packet
n,
N,
o,
O, Output broadcast
p, Broadcast power state
P, Write DCC packet
q, Sensor deactivated
Q, Sensor activated
r, Broadcast address read on programming track
R, Read CVs
s, Display status
S, Sensor configuration
t, Cab/loco update command
T, Turnout configuration/control
u, Reserved for user commands
U, Reserved for user commands
v,
V, Verify CVs
w, Write CV on main
W, Write CV
x,
X, Invalid command
y,
Y, Output broadcast
z,
Z, Output configuration/control
*/
#include "StringFormatter.h"
#include "DCCEXParser.h"
#include "DCC.h"
@@ -40,22 +114,21 @@
#include "TrackManager.h"
#include "DCCTimer.h"
#include "EXRAIL2.h"
#include "Turntables.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); \
if (value==INT16_MAX) 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.
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_CABS = -11981;
const int16_t HASH_KEYWORD_RAM = 25982;
const int16_t HASH_KEYWORD_CMD = 9962;
@@ -63,7 +136,11 @@ const int16_t HASH_KEYWORD_ACK = 3113;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#endif
#ifndef DISABLE_EEPROM
const int16_t HASH_KEYWORD_EEPROM = -7168;
#endif
@@ -79,6 +156,11 @@ const int16_t HASH_KEYWORD_TT=2688;
const int16_t HASH_KEYWORD_VPIN=-415;
const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_G='G';
const int16_t HASH_KEYWORD_H='H';
const int16_t HASH_KEYWORD_I='I';
const int16_t HASH_KEYWORD_O='O';
const int16_t HASH_KEYWORD_P='P';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
@@ -90,6 +172,8 @@ const int16_t HASH_KEYWORD_ANOUT = -26399;
const int16_t HASH_KEYWORD_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594;
const int16_t HASH_KEYWORD_EXTT = 8573;
const int16_t HASH_KEYWORD_ADD = 3201;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
@@ -126,8 +210,10 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
if (hot == '\0')
return -1;
if (hot == '>')
return parameterCount;
state = 2;
continue;
@@ -214,17 +300,25 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{
#ifdef DISABLE_PROG
(void)ringStream;
#endif
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
#endif
byte params = 0;
if (Diag::CMD)
DIAG(F("PARSING:%s"), com);
int16_t p[MAX_COMMAND_PARAMS];
while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces
byte opcode = com[0];
byte params = splitValues(p, com, opcode=='M' || opcode=='P');
int16_t splitnum = splitValues(p, com, opcode=='M' || opcode=='P');
if (splitnum < 0 || splitnum >= MAX_COMMAND_PARAMS) // if arguments are broken, leave but via printing <X>
goto out;
// Because of check above we are now inside byte size
params = splitnum;
if (filterCallback)
filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0')
@@ -283,6 +377,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (direction < 0 || direction > 1)
break; // invalid direction code
if (cab > 10239 || cab < 0)
break; // beyond DCC range
DCC::setThrottle(cab, tspeed, direction);
if (params == 4) // send obsolete format T response
@@ -342,6 +438,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
break;
case 'z': // direct pin manipulation
if (p[0]==0) break;
if (params==1) { // <z vpin | -vpin>
if (p[0]>0) IODevice::write(p[0],HIGH);
else IODevice::write(-p[0],LOW);
return;
}
if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
// unused params default to 0
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
return;
}
break;
case 'Z': // OUTPUT <Z ...>
if (parseZ(stream, params, p))
return;
@@ -352,16 +462,24 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
break;
#ifndef DISABLE_PROG
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
if (params != 3)
break;
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
if (params != 4)
break;
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
#endif
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
#ifndef DISABLE_PROG
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
#endif
// NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG
if (params<1) break;
@@ -376,15 +494,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
return;
#ifndef DISABLE_PROG
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream))
break;
if (!stashCallback(stream, p, ringStream))
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco);
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W4);
else // WRITE CV ON PROG <W CV VALUE>
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
DCC::writeCVByte(p[0], p[1], callback_W);
else
break;
return;
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
@@ -404,9 +525,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
break;
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> or <B CV BIT VALUE>
if (params != 3 && params != 5)
break;
if (!stashCallback(stream, p, ringStream))
break;
break;
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
return;
@@ -433,88 +556,90 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
}
break;
#endif
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
bool main=false;
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
if (params > 1) break;
if (params==0) { // All
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
}
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
TrackManager::setJoin(true);
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
}
#endif
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
TrackManager::setTrackPower(POWERMODE::ON, t);
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
}
else break; // will reply <X>
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
else break; // will reply <X>
CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL,t);
return;
}
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
CommandDistributor::broadcastPower();
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
if (params > 1) break;
if (params==0) { // All
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
}
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
}
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
}
#endif
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
TrackManager::setJoin(false);
TrackManager::setTrackPower(POWERMODE::OFF, t);
//StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
}
else break; // will reply <X>
}
else break; // will reply <X>
CommandDistributor::broadcastPower();
return;
}
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
return;
}
case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
case 'c': // SEND METER RESPONSES <c>
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
break;
// No longer useful because of multiple tracks See <JG> and <JI>
if (params>0) break;
TrackManager::reportObsoleteCurrent(stream);
return;
case 'Q': // SENSORS <Q>
Sensor::printAll(stream);
return;
case 's': // <s>
case 's': // STATUS <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
// TODO Send stats of speed reminders table
return;
#ifndef DISABLE_EEPROM
@@ -531,13 +656,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
case 'D': // < >
case 'C': // CONFIG <C [params]>
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG
case 'D': // DIAG <D [params]>
if (parseD(stream, params, p))
return;
return;
case '=': // <= Track manager control >
break;
#endif
case '=': // TRACK MANAGER CONTROL <= [params]>
if (TrackManager::parseJ(stream, params, p))
return;
break;
@@ -583,36 +712,38 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
CommandDistributor::setClockTime(p[1], p[2], 1);
return;
case HASH_KEYWORD_G: // <JG> current gauge limits
if (params>1) break;
TrackManager::reportGauges(stream); // <g limit...limit>
return;
case HASH_KEYWORD_I: // <JI> current values
if (params>1) break;
TrackManager::reportCurrent(stream); // <g limit...limit>
return;
case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
StringFormatter::send(stream,F(" %d %c \"%S\""),
id,
#ifdef EXRAIL_ACTIVE
RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id)
#else
'X',F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
return;
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
if (params!=1) break; // <JA>
StringFormatter::send(stream, F("<jA>\n"));
return;
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
else {
auto rosterName= RMFT2::getRosterName(id);
if (!rosterName) rosterName=F("");
auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames);
}
#endif
StringFormatter::send(stream, F(">\n"));
return;
@@ -641,20 +772,90 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
StringFormatter::send(stream, F(">\n"));
return;
// No turntables without HAL support
#ifndef IO_NO_HAL
case HASH_KEYWORD_O: // <JO returns turntable list
StringFormatter::send(stream, F("<jO"));
if (params==1) { // <JO>
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
if (tto->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),tto->getId());
}
StringFormatter::send(stream, F(">\n"));
} else { // <JO id>
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t pos = tto->getPosition();
uint8_t type = tto->isEXTT();
uint8_t posCount = tto->getPositionCount();
const FSH *todesc = NULL;
#ifdef EXRAIL_ACTIVE
todesc = RMFT2::getTurntableDescription(id);
#endif
if (todesc == NULL) todesc = F("");
StringFormatter::send(stream, F(" %d %d %d %d \"%S\">\n"), id, type, pos, posCount, todesc);
}
}
return;
case HASH_KEYWORD_P: // <JP id> returns turntable position list for the turntable id
if (params==2) { // <JP id>
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t posCount = tto->getPositionCount();
const FSH *tpdesc = NULL;
for (uint8_t p = 0; p < posCount; p++) {
StringFormatter::send(stream, F("<jP"));
int16_t angle = tto->getPositionAngle(p);
#ifdef EXRAIL_ACTIVE
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
#endif
if (tpdesc == NULL) tpdesc = F("");
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
StringFormatter::send(stream, F(">\n"));
}
}
} else {
StringFormatter::send(stream, F("<jP X>\n"));
}
return;
#endif
default: break;
} // switch(p[1])
break; // case J
}
// No turntables without HAL support
#ifndef IO_NO_HAL
case 'I': // TURNTABLE <I ...>
if (parseI(stream, params, p))
return;
break;
#endif
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL
case '@': // JMRI saying "give me virtual LCD msgs"
CommandDistributor::setVirtualLCDSerial(stream);
return;
default: //anything else will diagnose and drop out to <X>
if (opcode >= ' ' && opcode <= '~') {
DIAG(F("Opcode=%c params=%d"), opcode, params);
for (int i = 0; i < params; i++)
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
break;
} else {
DIAG(F("Unprintable %x"), opcode);
}
break;
} // end of opcode switch
// Any fallout here sends an <X>
out:// Any fallout here sends an <X>
StringFormatter::send(stream, F("<X>\n"));
}
@@ -851,21 +1052,30 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
break;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
#ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
@@ -882,10 +1092,34 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
}
} else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
#endif
default: // invalid/unknown
break;
}
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff;
@@ -908,40 +1142,20 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::LCN = onOff;
return true;
#endif
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
StringFormatter::send(stream, F("28 Speedsteps"));
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break;
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
break;
#if !defined(IO_NO_HAL)
@@ -958,11 +1172,104 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break;
default: // invalid/unknown
break;
return parseC(stream, params, p);
}
return false;
}
// ==========================
// Turntable - no support if no HAL
// <I> - list all
// <I id> - broadcast type and current position
// <I id DCC> - create DCC - This is TBA
// <I id steps> - operate (DCC)
// <I id steps activity> - operate (EXTT)
// <I id ADD position value> - add position
// <I id EXTT i2caddress vpin home> - create EXTT
#ifndef IO_NO_HAL
bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 0: // <I> list turntable objects
return Turntable::printAll(stream);
case 1: // <I id> broadcast type and current position
{
Turntable *tto = Turntable::get(p[0]);
if (tto) {
bool type = tto->isEXTT();
uint8_t position = tto->getPosition();
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
} else {
return false;
}
}
return true;
case 2: // <I id position> - rotate a DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (tto && !tto->isEXTT()) {
if (!tto->setPosition(p[0], p[1])) return false;
} else {
return false;
}
}
return true;
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_DCC) {
if (tto || p[2] < 0 || p[2] > 3600) return false;
if (!DCCTurntable::create(p[0])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[2]);
StringFormatter::send(stream, F("<I>\n"));
} else {
if (!tto) return false;
if (!tto->isEXTT()) return false;
if (!tto->setPosition(p[0], p[1], p[2])) return false;
}
}
return true;
case 4: // <I id EXTT vpin home> create an EXTT turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_EXTT) {
if (tto || p[3] < 0 || p[3] > 3600) return false;
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[3]);
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}
}
return true;
case 5: // <I id ADD position value angle> add a position
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_ADD) {
// tto must exist, no more than 48 positions, angle 0 - 3600
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
tto->addPosition(p[2], p[3], p[4]);
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}
}
return true;
default: // Anything else is invalid
return false;
}
}
#endif
// CALLBACKS must be static
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
{

View File

@@ -24,6 +24,7 @@
#include <Arduino.h>
#include "FSH.h"
#include "RingStream.h"
#include "defines.h"
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
@@ -45,13 +46,17 @@ struct DCCEXParser
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
static bool parseT(Print * stream, int16_t params, int16_t p[]);
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(Print * stream, int16_t params, int16_t p[]);
static bool parsef(Print * stream, int16_t params, int16_t p[]);
static bool parseD(Print * stream, int16_t params, int16_t p[]);
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(Print * stream, int16_t params, int16_t p[]);
static bool parsef(Print * stream, int16_t params, int16_t p[]);
static bool parseC(Print * stream, int16_t params, int16_t p[]);
static bool parseD(Print * stream, int16_t params, int16_t p[]);
#ifndef IO_NO_HAL
static bool parseI(Print * stream, int16_t params, int16_t p[]);
#endif
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
static bool stashBusy;
static byte stashTarget;

View File

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

View File

@@ -1,7 +1,7 @@
/*
* © 2022 Paul M. Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021-2023 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
@@ -62,6 +62,9 @@ class DCCTimer {
static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high);
static void clearPWM();
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
static void DCCEXanalogWrite(uint8_t pin, int value);
// Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other
// called subroutines. Must be called with interrupts disabled.
@@ -102,9 +105,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
@@ -113,19 +121,20 @@ public:
static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return
static int16_t ADCmax();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
#if defined (ARDUINO_ARCH_STM32)
// bit array of used pins (max 32)
static uint32_t usedpins;
#else
// bit array of used pins (max 16)
static uint16_t usedpins;
#endif
static uint8_t highestPin;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;
// ids to scan (new way)
static byte *idarr;
// friend so that we can call scan() and begin()
friend class DCCWaveform;
};

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
/*
* © 2023 Neil McKechnie
* © 2022 Paul M. Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
@@ -30,37 +30,152 @@
#ifdef ARDUINO_ARCH_STM32
#include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 defined by default
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE
// Nucleo-64 boards don't have additional serial ports defined by default
// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins
// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// On the F446RE, Serial3 and Serial5 are easy to use:
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || 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
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
#else
#warning Serial1 not defined
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// Experimental code for High Accuracy (HA) DCC Signal mode
// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins,
// which is used by the DC motor types.
///////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT_CALLBACK interruptHandler=0;
// // Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
// // Also, timer #3 will do hardware pulses on pin D12. This gives
// // accurate timing, independent of the latency of interrupt handling.
// // We only need to interrupt on one of these (TIM2), the other will just generate
// // pulses.
// HardwareTimer timer(TIM2);
// HardwareTimer timerAux(TIM3);
// static bool tim2ModeHA = false;
// static bool tim3ModeHA = false;
// // Timer IRQ handler
// void Timer_Handler() {
// interruptHandler();
// }
// void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
// interruptHandler=callback;
// noInterrupts();
// // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
// timer.pause();
// timerAux.pause();
// timer.setPrescaleFactor(1);
// timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// timer.attachInterrupt(Timer_Handler);
// timer.refresh();
// timerAux.setPrescaleFactor(1);
// timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// timerAux.refresh();
// timer.resume();
// timerAux.resume();
// interrupts();
// }
// bool DCCTimer::isPWMPin(byte pin) {
// // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
// // Enable the appropriate timer channel.
// switch (pin) {
// case 12:
// return true;
// case 13:
// return true;
// default:
// return false;
// }
// }
// void DCCTimer::setPWM(byte pin, bool high) {
// // Set the timer so that, at the next counter overflow, the requested
// // pin state is activated automatically before the interrupt code runs.
// // TIM2 is timer, TIM3 is timerAux.
// switch (pin) {
// case 12:
// if (!tim3ModeHA) {
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
// tim3ModeHA = true;
// }
// if (high)
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
// else
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
// break;
// case 13:
// if (!tim2ModeHA) {
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
// tim2ModeHA = true;
// }
// if (high)
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
// else
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
// break;
// }
// }
// void DCCTimer::clearPWM() {
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
// tim2ModeHA = false;
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
// tim3ModeHA = false;
// }
///////////////////////////////////////////////////////////////////////////////////////////////
INTERRUPT_CALLBACK interruptHandler=0;
// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
// Also, timer #3 will do hardware pulses on pin D12. This gives
// accurate timing, independent of the latency of interrupt handling.
// We only need to interrupt on one of these (TIM2), the other will just generate
// pulses.
HardwareTimer timer(TIM2);
HardwareTimer timerAux(TIM3);
// On STM32F4xx models that have them, Timers 6 and 7 have no PWM output capability,
// so are good choices for general timer duties - they are used for tone and servo
// in stm32duino so we shall usurp those as DCC-EX doesn't use tone or servo libs.
// NB: the F401, F410 and F411 do **not** have Timer 6 or 7, so we use Timer 11
#ifndef DCC_EX_TIMER
#if defined(TIM6)
#define DCC_EX_TIMER TIM6
#elif defined(TIM7)
#define DCC_EX_TIMER TIM7
#elif defined(TIM11)
#define DCC_EX_TIMER TIM11
#else
#warning This STM32F4XX variant does not have Timers 6,7 or 11!!
#endif
#endif // ifndef DCC_EX_TIMER
HardwareTimer dcctimer(DCC_EX_TIMER);
void DCCTimer_Handler() __attribute__((interrupt));
// Timer IRQ handler
void Timer_Handler() {
void DCCTimer_Handler() {
interruptHandler();
}
@@ -68,61 +183,35 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
timer.pause();
timerAux.pause();
timer.setPrescaleFactor(1);
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timer.attachInterrupt(Timer_Handler);
timer.refresh();
timerAux.setPrescaleFactor(1);
timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timerAux.refresh();
timer.resume();
timerAux.resume();
dcctimer.pause();
dcctimer.setPrescaleFactor(1);
// timer.setOverflow(CLOCK_CYCLES * 2);
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// dcctimer.attachInterrupt(Timer11_Handler);
dcctimer.attachInterrupt(DCCTimer_Handler);
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
dcctimer.refresh();
dcctimer.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
// Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
// Enable the appropriate timer channel.
switch (pin) {
case 12:
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
return true;
case 13:
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
return true;
default:
return false;
}
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
(void) pin;
return false;
}
void DCCTimer::setPWM(byte pin, bool high) {
// Set the timer so that, at the next counter overflow, the requested
// pin state is activated automatically before the interrupt code runs.
// TIM2 is timer, TIM3 is timerAux.
switch (pin) {
case 12:
if (high)
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
else
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
break;
case 13:
if (high)
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
else
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
break;
}
// TODO: High Accuracy mode is not supported as yet, and may never need to be
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
return;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
@@ -163,28 +252,128 @@ void DCCTimer::reset() {
while(true) {};
}
// TODO: rationalise the size of these... could really use sparse arrays etc.
static HardwareTimer * pin_timer[100] = {0};
static uint32_t channel_frequency[100] = {0};
static uint32_t pin_channel[100] = {0};
// Using the HardwareTimer library API included in stm32duino core to handle PWM duties
// TODO: in order to use the HA code above which Neil kindly wrote, we may have to do something more
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
// work well... then work backwards to integrate with HA mode if we can.
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
{
if (pin_timer[pin] == NULL) {
// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
if (Instance == NULL) {
// We shouldn't get here (famous last words) as it ought to have been caught by brakeCanPWM()!
DIAG(F("DCCEXanalogWriteFrequency::Pin %d has no PWM function!"), pin);
return;
}
pin_channel[pin] = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
// HardwareTimer is not destructed when setup function is finished.
pin_timer[pin] = new HardwareTimer(Instance);
// Configure and start PWM
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
if (pin_timer[pin] != NULL)
{
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency);
}
else
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
}
else
{
// Frequency change request
if (frequency != channel_frequency[pin])
{
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
}
}
channel_frequency[pin] = frequency;
return;
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
// Calculate percentage duty cycle from value given
uint32_t duty_cycle = (value * 100 / 256) + 1;
if (pin_timer[pin] != NULL) {
// if (duty_cycle == 100)
// {
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
// }
// else
// {
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
// }
}
else
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
}
// Now we can handle more ADCs, maybe this works!
#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;
uint32_t ADCee::usedpins = 0; // Max of 32 ADC input channels!
uint8_t ADCee::highestPin = 0; // Highest pin to scan
int * ADCee::analogvals = NULL; // Array of analog values last captured
uint32_t * analogchans = NULL; // Array of channel numbers to be scanned
// bool adc1configured = false;
ADC_TypeDef * * adcchans = NULL; // Array to capture which ADC is each input channel on
int16_t ADCee::ADCmax() {
return 4095;
int16_t ADCee::ADCmax()
{
return 4095;
}
int ADCee::init(uint8_t 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) {
int value = 0;
PinName stmpin = analogInputToPinName(pin);
if (stmpin == NC) // do not continue if this is not an analog pin at all
return -1024; // some silly value as error
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC input channel
ADC_TypeDef *adc = (ADC_TypeDef *)pinmap_find_peripheral(stmpin, PinMap_ADC); // find which ADC this pin is on ADC1/2/3 etc.
int adcnum = 1;
if (adc == ADC1)
DIAG(F("ADCee::init(): found pin %d on ADC1"), pin);
// Checking for ADC2 and ADC3 being defined helps cater for more variants later
#if defined(ADC2)
else if (adc == ADC2)
{
DIAG(F("ADCee::init(): found pin %d on ADC2"), pin);
adcnum = 2;
}
#endif
#if defined(ADC3)
else if (adc == ADC3)
{
DIAG(F("ADCee::init(): found pin %d on ADC3"), pin);
adcnum = 3;
}
#endif
else DIAG(F("ADCee::init(): found pin %d on unknown ADC!"), pin);
// Port config - find which port we're on and power it up
GPIO_TypeDef *gpioBase;
switch (stmgpio)
{
case 0x00:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
gpioBase = GPIOA;
@@ -197,31 +386,62 @@ int ADCee::init(uint8_t pin) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC;
break;
case 0x03:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Power up PORTD
gpioBase = GPIOD;
break;
case 0x04:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; //Power up PORTE
gpioBase = GPIOE;
break;
#if defined(GPIOF)
case 0x05:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Power up PORTF
gpioBase = GPIOF;
break;
#endif
default:
return -1023; // some silly value as error
}
// Set pin mux mode to analog input
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
// Set the sampling rate for that analog input
// This is F411x specific! Different on for example F334
// STM32F11xC/E Reference manual
// 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18)
// 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9)
if (adcchan > 18)
return -1022; // silly value as error
if (adcchan < 10)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
adc->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
adc->SQR3 = adcchan; // 1st conversion in regular sequence
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
value = adc->DR; // Read value from register
if (analogvals == NULL)
{
uint8_t id = pin - PNUM_ANALOG_BASE;
// if (id > 15) { // today we have not enough bits in the mask to support more
// return -1021;
// }
if (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
}
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
adcchans[id] = adc; // Keep track of which ADC this channel is on
usedpins |= (1 << id); // This pin is now ready
if (id > highestPin) highestPin = id; // Store our highest pin in use
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
return value;
}
@@ -230,7 +450,7 @@ int ADCee::init(uint8_t pin) {
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
uint8_t id = pin - PNUM_ANALOG_BASE;
// Was this pin initialised yet?
if ((usedpins & (1<<id) ) == 0)
return -1023;
@@ -245,22 +465,27 @@ int ADCee::read(uint8_t pin, bool fromISR) {
#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 uint8_t id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
static ADC_TypeDef *adc;
if (waiting) {
adc = adcchans[id];
if (waiting)
{
// look if we have a result
if (!(ADC1->SR & (1 << 1)))
if (!(adc->SR & (1 << 1)))
return; // no result, continue to wait
// found value
analogvals[id] = ADC1->DR;
analogvals[id] = adc->DR;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
if (id > highestPin) { // the 1 has been shifted out
id = 0;
mask = 1;
}
@@ -271,18 +496,21 @@ void ADCee::scan() {
// 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;
// start new ADC aquire on id
adc = adcchans[id];
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
}
@@ -292,19 +520,83 @@ void ADCee::scan() {
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)
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
// 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
// Disable the DMA controller for ADC1
ADC1->CR2 &= ~ADC_CR2_DMA;
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
// Wait for ADC1 to become ready (calibration complete)
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
}
#if defined(ADC2)
// Enable the ADC2 clock
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
// Initialize ADC2
ADC2->CR1 = 0; // Disable all channels
ADC2->CR2 = 0; // Clear CR2 register
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
ADC2->CR2 &= ~(1 << 1); //Single conversion
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
// Enable the ADC
ADC2->CR2 |= ADC_CR2_ADON;
// Wait for ADC2 to become ready (calibration complete)
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
}
// Perform ADC3 calibration (optional)
// ADC3->CR2 |= ADC_CR2_CAL;
// while (ADC3->CR2 & ADC_CR2_CAL) {
// }
#endif
#if defined(ADC3)
// Enable the ADC3 clock
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
// Initialize ADC3
ADC3->CR1 = 0; // Disable all channels
ADC3->CR2 = 0; // Clear CR2 register
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
ADC3->CR2 &= ~(1 << 1); //Single conversion
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
// Enable the ADC
ADC3->CR2 |= ADC_CR2_ADON;
// Wait for ADC3 to become ready (calibration complete)
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
}
// Perform ADC3 calibration (optional)
// ADC3->CR2 |= ADC_CR2_CAL;
// while (ADC3->CR2 & ADC_CR2_CAL) {
// }
#endif
interrupts();
}
#endif
#endif

View File

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

View File

@@ -36,7 +36,7 @@
* not held up significantly. The exception to this is when
* the loop2() function is called with force=true, where
* a screen update is executed to completion. This is normally
* only noMoreRowsToDisplay during start-up.
* only done during start-up.
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
* in the config.h.
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
@@ -51,11 +51,10 @@
Display::Display(DisplayDevice *deviceDriver) {
_deviceDriver = deviceDriver;
// Get device dimensions in characters (e.g. 16x2).
numCharacterColumns = _deviceDriver->getNumCols();
numCharacterRows = _deviceDriver->getNumRows();;
numScreenColumns = _deviceDriver->getNumCols();
numScreenRows = _deviceDriver->getNumRows();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
topRow = ROW_INITIAL; // loop2 will fill from row 0
addDisplay(0); // Add this display as display number 0
};
@@ -69,20 +68,19 @@ void Display::_clear() {
_deviceDriver->clearNative();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
topRow = ROW_INITIAL; // loop2 will fill from row 0
}
void Display::_setRow(uint8_t line) {
hotRow = line;
hotCol = 0;
rowBuffer[hotRow][0] = 0; // Clear existing text
rowBuffer[hotRow][0] = '\0'; // Clear existing text
}
size_t Display::_write(uint8_t b) {
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
rowBuffer[hotRow][hotCol] = b;
hotCol++;
rowBuffer[hotRow][hotCol] = 0;
rowBuffer[hotRow][hotCol] = '\0';
return 1;
}
@@ -109,8 +107,8 @@ Display *Display::loop2(bool force) {
return NULL;
} else {
// force full screen update from the beginning.
rowFirst = ROW_INITIAL;
rowNext = ROW_INITIAL;
rowFirst = 0;
rowCurrent = 0;
bufferPointer = 0;
noMoreRowsToDisplay = false;
slot = 0;
@@ -118,15 +116,20 @@ Display *Display::loop2(bool force) {
do {
if (bufferPointer == 0) {
// Find a line of data to write to the screen.
if (rowFirst == ROW_INITIAL) rowFirst = rowNext;
if (findNextNonBlankRow()) {
// Search for non-blank row
while (!noMoreRowsToDisplay) {
if (!isCurrentRowBlank()) break;
moveToNextRow();
if (rowCurrent == rowFirst) noMoreRowsToDisplay = true;
}
if (noMoreRowsToDisplay) {
// No non-blank lines left, so draw blank line
buffer[0] = '\0';
} else {
// Non-blank line found, so copy it (including terminator)
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
buffer[i] = rowBuffer[rowNext][i];
} else {
// No non-blank lines left, so draw a blank line
buffer[0] = 0;
buffer[i] = rowBuffer[rowCurrent][i];
}
_deviceDriver->setRowNative(slot); // Set position for display
charIndex = 0;
@@ -142,21 +145,49 @@ Display *Display::loop2(bool force) {
}
if (++charIndex >= MAX_CHARACTER_COLS) {
// Screen slot completed, move to next slot on screen
// Screen slot completed, move to next nonblank row
bufferPointer = 0;
for (;;) {
moveToNextRow();
if (rowCurrent == rowFirst) {
noMoreRowsToDisplay = true;
break;
}
if (!isCurrentRowBlank()) break;
}
// Move to next screen slot, if available
slot++;
if (slot >= numCharacterRows) {
// Last slot on screen written, reset ready for next screen update.
#if SCROLLMODE==2
if (!noMoreRowsToDisplay) {
// On next refresh, restart one row on from previous start.
rowNext = rowFirst;
findNextNonBlankRow();
if (slot >= numScreenRows) {
// Last slot on screen written, so get ready for next screen update.
#if SCROLLMODE==0
// Scrollmode 0 scrolls continuously. If the rows fit on the screen,
// then restart at row 0, but otherwise continue with the row
// after the last one displayed.
if (countNonBlankRows() <= numScreenRows)
rowCurrent = 0;
rowFirst = rowCurrent;
#elif SCROLLMODE==1
// Scrollmode 1 scrolls by page, so if the last page has just completed then
// next time restart with row 0.
if (noMoreRowsToDisplay)
rowFirst = rowCurrent = 0;
#else
// Scrollmode 2 scrolls by row. If the rows don't fit on the screen,
// then start one row further on next time. If they do fit, then
// show them in order and start next page at row 0.
if (countNonBlankRows() <= numScreenRows) {
rowFirst = rowCurrent = 0;
} else {
// Find first non-blank row after the previous first row
rowCurrent = rowFirst;
do {
moveToNextRow();
} while (isCurrentRowBlank());
rowFirst = rowCurrent;
}
#endif
noMoreRowsToDisplay = false;
slot = 0;
rowFirst = ROW_INITIAL;
lastScrollTime = currentMillis;
return NULL;
}
@@ -167,30 +198,22 @@ Display *Display::loop2(bool force) {
return NULL;
}
bool Display::findNextNonBlankRow() {
while (!noMoreRowsToDisplay) {
if (rowNext == ROW_INITIAL)
rowNext = 0;
else
rowNext = rowNext + 1;
if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL;
#if SCROLLMODE == 1
// Finished if we've looped back to start
if (rowNext == ROW_INITIAL) {
noMoreRowsToDisplay = true;
return false;
}
#else
// Finished if we're back to the first one shown
if (rowNext == rowFirst) {
noMoreRowsToDisplay = true;
return false;
}
#endif
if (rowBuffer[rowNext][0] != 0) {
// Found non-blank row
return true;
}
bool Display::isCurrentRowBlank() {
return (rowBuffer[rowCurrent][0] == '\0');
}
void Display::moveToNextRow() {
// Skip blank rows
if (++rowCurrent >= MAX_CHARACTER_ROWS)
rowCurrent = 0;
}
uint8_t Display::countNonBlankRows() {
uint8_t count = 0;
for (uint8_t rowNumber=0; rowNumber<MAX_CHARACTER_ROWS; rowNumber++) {
if (rowBuffer[rowNumber][0] != '\0')
count++;
}
return false;
}
return count;
}

View File

@@ -37,10 +37,11 @@
class Display : public DisplayInterface {
public:
Display(DisplayDevice *deviceDriver);
#if !defined (MAX_CHARACTER_ROWS)
static const int MAX_CHARACTER_ROWS = 8;
#endif
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
static const uint8_t ROW_INITIAL = 255;
private:
DisplayDevice *_deviceDriver;
@@ -48,16 +49,15 @@ private:
unsigned long lastScrollTime = 0;
uint8_t hotRow = 0;
uint8_t hotCol = 0;
uint8_t topRow = 0;
uint8_t slot = 0;
uint8_t rowFirst = ROW_INITIAL;
uint8_t rowNext = ROW_INITIAL;
uint8_t rowFirst = 0;
uint8_t rowCurrent = 0;
uint8_t charIndex = 0;
char buffer[MAX_CHARACTER_COLS + 1];
char* bufferPointer = 0;
bool noMoreRowsToDisplay = false;
uint16_t numCharacterRows;
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
uint16_t numScreenRows;
uint16_t numScreenColumns = MAX_CHARACTER_COLS;
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
@@ -69,7 +69,10 @@ public:
void _refresh() override;
void _displayLoop() override;
Display *loop2(bool force);
bool findNextNonBlankRow();
bool findNonBlankRow();
bool isCurrentRowBlank();
void moveToNextRow();
uint8_t countNonBlankRows();
};

View File

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

View File

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

Binary file not shown.

View File

@@ -1,7 +1,8 @@
/*
* © 2021 Neil McKechnie
* © 2021-2023 Harald Barth
* © 2020-2022 Chris Harlow
* © 2020-2023 Chris Harlow
* © 2022-2023 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -24,8 +25,8 @@
F1. [DONE] DCC accessory packet opcodes (short and long form)
F2. [DONE] ONAccessory catchers
F3. [DONE] Turnout descriptions for Withrottle
F4. Oled announcements (depends on HAL)
F5. Withrottle roster info
F4. [DONE] Oled announcements (depends on HAL)
F5. [DONE] Withrottle roster info
F6. Multi-occupancy semaphore
F7. [DONE see AUTOSTART] Self starting sequences
F8. Park/unpark
@@ -51,23 +52,9 @@
#include "Turnouts.h"
#include "CommandDistributor.h"
#include "TrackManager.h"
#include "Turntables.h"
#include "IODevice.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_START=23232;
const int16_t HASH_KEYWORD_RESERVE=11392;
const int16_t HASH_KEYWORD_FREE=-23052;
const int16_t HASH_KEYWORD_LATCH=1618;
const int16_t HASH_KEYWORD_UNLATCH=1353;
const int16_t HASH_KEYWORD_PAUSE=-4142;
const int16_t HASH_KEYWORD_RESUME=27609;
const int16_t HASH_KEYWORD_KILL=5218;
const int16_t HASH_KEYWORD_ALL=3457;
const int16_t HASH_KEYWORD_ROUTES=-3702;
const int16_t HASH_KEYWORD_RED=26099;
const int16_t HASH_KEYWORD_AMBER=18713;
const int16_t HASH_KEYWORD_GREEN=-31493;
// One instance of RMFT clas is used for each "thread" in the automation.
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
@@ -82,8 +69,8 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
// when pausingTask is set, that is the ONLY task that gets any service,
// and all others will have their locos stopped, then resumed after the pausing task resumes.
byte RMFT2::flags[MAX_FLAGS];
LookList * RMFT2::sequenceLookup=NULL;
Print * RMFT2::LCCSerial=0;
LookList * RMFT2::routeLookup=NULL;
LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL;
LookList * RMFT2::onActivateLookup=NULL;
@@ -93,9 +80,12 @@ LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
LookList * RMFT2::onChangeLookup=NULL;
LookList * RMFT2::onClockLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
#ifndef IO_NO_HAL
LookList * RMFT2::onRotateLookup=NULL;
#endif
LookList * RMFT2::onOverloadLookup=NULL;
byte * RMFT2::routeStateArray=nullptr;
const FSH * * RMFT2::routeCaptionArray=nullptr;
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
@@ -105,17 +95,15 @@ uint16_t RMFT2::getOperand(byte 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);
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
LookList::LookList(int16_t size) {
m_size=size;
m_loaded=0;
m_chain=nullptr;
if (size) {
m_lookupArray=new int16_t[size];
m_resultArray=new int16_t[size];
@@ -133,8 +121,35 @@ int16_t LookList::find(int16_t value) {
for (int16_t i=0;i<m_size;i++) {
if (m_lookupArray[i]==value) return m_resultArray[i];
}
return m_chain ? m_chain->find(value) :-1;
}
void LookList::chain(LookList * chain) {
m_chain=chain;
}
void LookList::handleEvent(const FSH* reason,int16_t id) {
// New feature... create multiple ONhandlers
for (int i=0;i<m_size;i++)
if (m_lookupArray[i]==id)
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
}
void LookList::stream(Print * _stream) {
for (int16_t i=0;i<m_size;i++) {
_stream->print(" ");
_stream->print(m_lookupArray[i]);
}
}
int16_t LookList::findPosition(int16_t value) {
for (int16_t i=0;i<m_size;i++) {
if (m_lookupArray[i]==value) return i;
}
return -1;
}
int16_t LookList::size() {
return m_size;
}
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
int progCounter;
@@ -167,25 +182,36 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
// create lookups
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION);
routeLookup->chain(LookListLoader(OPCODE_SEQUENCE));
if (compileFeatures && FEATURE_ROUTESTATE) {
routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte));
routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *));
}
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
#ifndef IO_NO_HAL
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
#endif
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
// onLCCLookup is not the same so not loaded here.
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
if (compileFeatures & FEATURE_SIGNAL) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
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);
}
}
int progCounter;
for (progCounter=0;; SKIPOP){
@@ -197,11 +223,12 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_AT:
case OPCODE_ATTIMEOUT2:
case OPCODE_AFTER:
case OPCODE_AFTEROVERLOAD:
case OPCODE_IF:
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
DIAG(F("EXRAIL input VPIN %u"),pin);
IODevice::configureInput((VPIN)pin,true);
break;
}
@@ -211,7 +238,7 @@ 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);
DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
@@ -240,11 +267,43 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: {
VPIN id=operand;
int home=getOperand(progCounter,1);
setTurntableHiddenState(DCCTurntable::create(id));
Turntable *tto=Turntable::get(id);
tto->addPosition(0,0,home);
break;
}
case OPCODE_EXTTTURNTABLE: {
VPIN id=operand;
VPIN pin=getOperand(progCounter,1);
int home=getOperand(progCounter,3);
setTurntableHiddenState(EXTTTurntable::create(id,pin));
Turntable *tto=Turntable::get(id);
tto->addPosition(0,0,home);
break;
}
case OPCODE_TTADDPOSITION: {
VPIN id=operand;
int position=getOperand(progCounter,1);
int value=getOperand(progCounter,2);
int angle=getOperand(progCounter,3);
Turntable *tto=Turntable::get(id);
tto->addPosition(position,value,angle);
break;
}
#endif
case OPCODE_AUTOSTART:
// automatically create a task from here at startup.
// but we will do one at 0 anyway by default.
if (progCounter>0) new RMFT2(progCounter);
// Removed if (progCounter>0) check 4.2.31 because
// default start it top of file is now removed. .
new RMFT2(progCounter);
break;
default: // Ignore
@@ -255,7 +314,7 @@ 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
// Removed for 4.2.31 new RMFT2(0); // add the startup route
diag=saved_diag;
}
@@ -264,184 +323,22 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
}
#ifndef IO_NO_HAL
void RMFT2::setTurntableHiddenState(Turntable * tto) {
tto->setHidden(GETFLASH(getTurntableDescription(tto->getId()))==0x01);
}
#endif
char RMFT2::getRouteType(int16_t id) {
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+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A';
if (rid==0) break;
int16_t progCounter=routeLookup->find(id);
if (progCounter>=0) {
byte type=GET_OPCODE;
if (type==OPCODE_ROUTE) return 'R';
if (type==OPCODE_AUTOMATION) return 'A';
}
return 'X';
}
// This filter intercepts <> commands to do the following:
// - Implement RMFT specific commands/diagnostics
// - Reject/modify JMRI commands that would interfere with RMFT processing
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
bool reject=false;
switch(opcode) {
case 'D':
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
opcode=0;
}
break;
case '/': // New EXRAIL command
reject=!parseSlash(stream,paramCount,p);
opcode=0;
break;
default: // other commands pass through
break;
}
if (reject) {
opcode=0;
StringFormatter::send(stream,F("<X>"));
}
}
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (paramCount==0) { // STATUS
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
RMFT2 * task=loopTask;
while(task) {
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
(int)(task->taskId),task->progCounter,task->loco,
task->invert?'I':' ',
task->speedo,
task->forward?'F':'R'
);
task=task->next;
if (task==loopTask) break;
}
// Now stream the flags
for (int id=0;id<MAX_FLAGS; id++) {
byte flag=flags[id];
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
StringFormatter::send(stream,F("\nflags[%d] "),id);
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
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]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case HASH_KEYWORD_PAUSE: // </ PAUSE>
if (paramCount!=1) return false;
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=(RMFT2 *)1; // Impossible task address
return true;
case HASH_KEYWORD_RESUME: // </ RESUME>
if (paramCount!=1) return false;
pausingTask=NULL;
{
RMFT2 * task=loopTask;
while(task) {
if (task->loco) task->driveLoco(task->speedo);
task=task->next;
if (task==loopTask) break;
}
}
return true;
case HASH_KEYWORD_START: // </ START [cab] route >
if (paramCount<2 || paramCount>3) return false;
{
int route=(paramCount==2) ? p[1] : p[2];
uint16_t cab=(paramCount==2)? 0 : p[1];
int pc=sequenceLookup->find(route);
if (pc<0) return false;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
}
return true;
default:
break;
}
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
return true;
}
// all other / commands take 1 parameter
if (paramCount!=2 ) return false;
switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid|ALL
{
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
RMFT2 * task=loopTask;
while(task) {
if (task->taskId==p[1]) {
task->kill(F("KILL"));
return true;
}
task=task->next;
if (task==loopTask) break;
}
}
return false;
case HASH_KEYWORD_RESERVE: // force reserve a section
return setFlag(p[1],SECTION_FLAG);
case HASH_KEYWORD_FREE: // force free a section
return setFlag(p[1],0,SECTION_FLAG);
case HASH_KEYWORD_LATCH:
return setFlag(p[1], LATCH_FLAG);
case HASH_KEYWORD_UNLATCH:
return setFlag(p[1], 0, LATCH_FLAG);
case HASH_KEYWORD_RED:
doSignal(p[1],SIGNAL_RED);
return true;
case HASH_KEYWORD_AMBER:
doSignal(p[1],SIGNAL_AMBER);
return true;
case HASH_KEYWORD_GREEN:
doSignal(p[1],SIGNAL_GREEN);
return true;
default:
return false;
}
}
// This emits Routes and Automations to Withrottle
// Automations are given a state to set the button to "handoff" which implies
// handing over the loco to the automation.
// Routes are given "Set" buttons and do not cause the loco to be handed over.
RMFT2::RMFT2(int progCtr) {
progCounter=progCtr;
@@ -490,7 +387,7 @@ RMFT2::~RMFT2() {
}
void RMFT2::createNewTask(int route, uint16_t cab) {
int pc=sequenceLookup->find(route);
int pc=routeLookup->find(route);
if (pc<0) return;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
@@ -599,6 +496,14 @@ void RMFT2::loop2() {
Turnout::setClosed(operand, true);
break;
#ifndef IO_NO_HAL
case OPCODE_ROTATE:
uint8_t activity;
activity=getOperand(2);
Turntable::setPosition(operand,getOperand(1),activity);
break;
#endif
case OPCODE_REV:
forward = false;
driveLoco(operand);
@@ -610,6 +515,7 @@ void RMFT2::loop2() {
break;
case OPCODE_SPEED:
forward=DCC::getThrottleDirection(loco)^invert;
driveLoco(operand);
break;
@@ -683,7 +589,17 @@ void RMFT2::loop2() {
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_AFTEROVERLOAD: // waits for the power to be turned back on - either by power routine or button
if (!TrackManager::isPowerOn(operand)) {
// reset timer to half a second and keep waiting
waitAfter=millis();
delayMe(50);
return;
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_LATCH:
setFlag(operand,LATCH_FLAG);
break;
@@ -704,23 +620,37 @@ void RMFT2::loop2() {
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=this;
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
TrackManager::setPower(POWERMODE::OFF);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_SET_POWER:
// operand is TRACK_POWER , trackid
//byte thistrack=getOperand(1);
switch (operand) {
case TRACK_POWER_0:
TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1));
break;
case TRACK_POWER_1:
TrackManager::setTrackPower(POWERMODE::ON, getOperand(1));
break;
}
break;
case OPCODE_SET_TRACK:
// operand is trackmode<<8 | track id
// If DC/DCX use my loco for DC address
{
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
int16_t cab=(mode & TRACK_MODE_DC) ? loco : 0;
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
}
break;
@@ -787,7 +717,13 @@ void RMFT2::loop2() {
case OPCODE_IFCLOSED:
skipIf=Turnout::isThrown(operand);
break;
#ifndef IO_NO_HAL
case OPCODE_IFTTPOSITION: // do block if turntable at this position
skipIf=Turntable::getPosition(operand)!=(int)getOperand(1);
break;
#endif
case OPCODE_ENDIF:
break;
@@ -852,7 +788,7 @@ void RMFT2::loop2() {
}
case OPCODE_FOLLOW:
progCounter=sequenceLookup->find(operand);
progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
return;
@@ -862,7 +798,7 @@ void RMFT2::loop2() {
return;
}
callStack[stackDepth++]=progCounter+3;
progCounter=sequenceLookup->find(operand);
progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("CALL unknown"),operand);
return;
@@ -883,23 +819,18 @@ void RMFT2::loop2() {
while(loopTask) loopTask->kill(F("KILLALL"));
return;
#ifndef DISABLE_PROG
case OPCODE_JOIN:
TrackManager::setPower(POWERMODE::ON);
TrackManager::setJoin(true);
CommandDistributor::broadcastPower();
break;
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_UNJOIN:
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet
DCC::getLocoId(readLocoCallback);
@@ -920,10 +851,17 @@ void RMFT2::loop2() {
forward=true;
invert=false;
break;
#endif
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_START:
{
int newPc=sequenceLookup->find(operand);
int newPc=routeLookup->find(operand);
if (newPc<0) break;
new RMFT2(newPc);
}
@@ -931,7 +869,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(getOperand(1));
int newPc=routeLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@@ -946,7 +884,21 @@ void RMFT2::loop2() {
invert=false;
}
break;
case OPCODE_LCC: // short form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
break;
case OPCODE_LCCX: // long form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
break;
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
@@ -958,11 +910,29 @@ void RMFT2::loop2() {
return;
}
break;
#ifndef IO_NO_HAL
case OPCODE_WAITFORTT: // OPCODE_WAITFOR,V(turntable_id)
if (Turntable::ttMoving(operand)) {
delayMe(100);
return;
}
break;
#endif
case OPCODE_PRINT:
printMessage(operand);
break;
case OPCODE_ROUTE_HIDDEN:
manageRouteState(operand,2);
break;
case OPCODE_ROUTE_INACTIVE:
manageRouteState(operand,0);
break;
case OPCODE_ROUTE_ACTIVE:
manageRouteState(operand,1);
break;
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
@@ -975,6 +945,7 @@ void RMFT2::loop2() {
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
case OPCODE_ONLCC: // LCC event catchers ignored here
case OPCODE_ONTHROW:
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
case OPCODE_ONDEACTIVATE:
@@ -983,6 +954,13 @@ void RMFT2::loop2() {
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_TTADDPOSITION: // Turntable position definition ignored at runtime
case OPCODE_ONROTATE:
#endif
case OPCODE_ONOVERLOAD:
break;
@@ -1037,13 +1015,14 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
// Thjis will work even without a signal definition.
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
else handleEvent(F("AMBER"), onAmberLookup,id);
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
else onAmberLookup->handleEvent(F("AMBER"),id);
int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return;
@@ -1104,6 +1083,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return false;
return (flags[sigslot] & SIGNAL_MASK) == rag;
@@ -1111,33 +1091,49 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
// Hunt for an ONTHROW/ONCLOSE for this turnout
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
else onThrowLookup->handleEvent(F("THROW"),turnoutId);
}
void RMFT2::activateEvent(int16_t addr, bool activate) {
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
}
#ifndef IO_NO_HAL
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
// Hunt or an ONROTATE for this turntable
if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
}
#endif
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);
if (change) {
onClockLookup->handleEvent(F("CLOCK"),clocktime);
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
}
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;
void RMFT2::powerEvent(int16_t track, bool overload) {
// Hunt for an ONOVERLOAD for this item
if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track);
if (overload) {
onOverloadLookup->handleEvent(F("POWER"),track);
}
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;
while(task) {
@@ -1239,7 +1235,10 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString());
CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString());
break;
case thrunge_withrottle:
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
@@ -1250,3 +1249,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
break;
}
}
void RMFT2::manageRouteState(uint16_t id, byte state) {
if (compileFeatures && FEATURE_ROUTESTATE) {
// Route state must be maintained for when new throttles connect.
// locate route id in the Routes lookup
int16_t position=routeLookup->findPosition(id);
if (position<0) return;
// set state beside it
if (routeStateArray[position]==state) return;
routeStateArray[position]=state;
CommandDistributor::broadcastRouteState(id,state);
}
}
void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) {
if (compileFeatures && FEATURE_ROUTESTATE) {
// Route state must be maintained for when new throttles connect.
// locate route id in the Routes lookup
int16_t position=routeLookup->findPosition(id);
if (position<0) return;
// set state beside it
if (routeCaptionArray[position]==caption) return;
routeCaptionArray[position]=caption;
CommandDistributor::broadcastRouteCaption(id,caption);
}
}

View File

@@ -1,6 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022-2023 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -24,6 +25,7 @@
#include "FSH.h"
#include "IODevice.h"
#include "Turnouts.h"
#include "Turntables.h"
// The following are the operation codes (or instructions) for a kind of virtual machine.
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
@@ -34,7 +36,8 @@
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
OPCODE_AT,OPCODE_AFTER,
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
OPCODE_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
@@ -44,7 +47,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
#ifndef DISABLE_PROG
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
#endif
OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
@@ -53,11 +59,16 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ROSTER,OPCODE_KILLALL,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_SET_TRACK,OPCODE_SET_POWER,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ONOVERLOAD,
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@@ -70,20 +81,27 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
OPCODE_IFLOCO,
OPCODE_IFTTPOSITION
};
// Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd.
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_print, thrunge_broadcast, thrunge_withrottle,
thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcn,
thrunge_lcd, // Must be last!!
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20;
static const byte FEATURE_ROUTESTATE= 0x10;
// Flag bits for status of hardware and TPL
static const byte SECTION_FLAG = 0x80;
@@ -103,13 +121,20 @@ enum thrunger: byte {
class LookList {
public:
LookList(int16_t size);
void chain(LookList* chainTo);
void add(int16_t lookup, int16_t result);
int16_t find(int16_t value);
int16_t find(int16_t value); // finds result value
int16_t findPosition(int16_t value); // finds index
int16_t size();
void stream(Print * _stream);
void handleEvent(const FSH* reason,int16_t id);
private:
int16_t m_size;
int16_t m_loaded;
int16_t * m_lookupArray;
int16_t * m_resultArray;
int16_t * m_resultArray;
LookList* m_chain;
};
class RMFT2 {
@@ -125,6 +150,8 @@ class LookList {
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 void rotateEvent(int16_t id, bool change);
static void powerEvent(int16_t track, bool overload);
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;
@@ -139,7 +166,10 @@ class LookList {
static const FSH * getTurnoutDescription(int16_t id);
static const FSH * getRosterName(int16_t id);
static const FSH * getRosterFunctions(int16_t id);
static const FSH * getTurntableDescription(int16_t id);
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
@@ -151,9 +181,11 @@ private:
static bool isSignal(int16_t id,char rag);
static int16_t getSignalSlot(int16_t id);
static void setTurnoutHiddenState(Turnout * t);
#ifndef IO_NO_HAL
static void setTurntableHiddenState(Turntable * tto);
#endif
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;
@@ -173,7 +205,8 @@ private:
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static Print * LCCSerial;
static LookList * routeLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
static LookList * onActivateLookup;
@@ -183,6 +216,18 @@ private:
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
#ifndef IO_NO_HAL
static LookList * onRotateLookup;
#endif
static LookList * onOverloadLookup;
static const int countLCCLookup;
static int onLCCLookup[];
static const byte compileFeatures;
static void manageRouteState(uint16_t id, byte state);
static void manageRouteCaption(uint16_t id, const FSH* caption);
static byte * routeStateArray;
static const FSH ** routeCaptionArray;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain
@@ -204,4 +249,8 @@ private:
byte stackDepth;
int callStack[MAX_STACK_DEPTH];
};
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
#endif

View File

@@ -1,5 +1,6 @@
/*
* © 2020-2022 Chris Harlow. All rights reserved.
* © 2022-2023 Colin Murdoch
* © 2023 Harald Barth
*
* This file is part of CommandStation-EX
@@ -26,6 +27,7 @@
#undef ACTIVATE
#undef ACTIVATEL
#undef AFTER
#undef AFTEROVERLOAD
#undef ALIAS
#undef AMBER
#undef ANOUT
@@ -39,6 +41,7 @@
#undef CALL
#undef CLOSE
#undef DCC_SIGNAL
#undef DCC_TURNTABLE
#undef DEACTIVATE
#undef DEACTIVATEL
#undef DELAY
@@ -50,8 +53,9 @@
#undef ENDEXRAIL
#undef ENDIF
#undef ENDTASK
#undef ESTOP
#undef EXRAIL
#undef ESTOP
#undef EXRAIL
#undef EXTT_TURNTABLE
#undef FADE
#undef FOFF
#undef FOLLOW
@@ -74,6 +78,7 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFTTPOSITION
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
@@ -81,6 +86,8 @@
#undef LATCH
#undef LCD
#undef SCREEN
#undef LCC
#undef LCCX
#undef LCN
#undef MOVETT
#undef ONACTIVATE
@@ -89,17 +96,23 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONLCC
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
#undef ONOVERLOAD
#undef ONGREEN
#undef ONRED
#undef ONROTATE
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
#undef PRINT
#ifndef DISABLE_PROG
#undef POM
#endif
#undef POWEROFF
#undef POWERON
#undef READ_LOCO
@@ -109,8 +122,14 @@
#undef RESUME
#undef RETURN
#undef REV
#undef ROSTER
#undef ROSTER
#undef ROTATE
#undef ROTATE_DCC
#undef ROUTE
#undef ROUTE_ACTIVE
#undef ROUTE_INACTIVE
#undef ROUTE_HIDDEN
#undef ROUTE_CAPTION
#undef SENDLOCO
#undef SEQUENCE
#undef SERIAL
@@ -126,19 +145,26 @@
#undef SERVO_SIGNAL
#undef SET
#undef SET_TRACK
#undef SET_POWER
#undef SETLOCO
#undef SIGNAL
#undef SIGNALH
#undef SPEED
#undef START
#undef STOP
#undef THROW
#undef THROW
#undef TT_ADDPOSITION
#undef TURNOUT
#undef TURNOUTL
#undef UNJOIN
#undef UNLATCH
#undef VIRTUAL_SIGNAL
#undef VIRTUAL_TURNOUT
#undef WAITFOR
#ifndef IO_NO_HAL
#undef WAITFORTT
#endif
#undef WITHROTTLE
#undef XFOFF
#undef XFON
@@ -146,6 +172,7 @@
#define ACTIVATE(addr,subaddr)
#define ACTIVATEL(addr)
#define AFTER(sensor_id)
#define AFTEROVERLOAD(track_id)
#define ALIAS(name,value...)
#define AMBER(signal_id)
#define ANOUT(vpin,value,param1,param2)
@@ -159,6 +186,7 @@
#define CALL(route)
#define CLOSE(id)
#define DCC_SIGNAL(id,add,subaddr)
#define DCC_TURNTABLE(id,home,description)
#define DEACTIVATE(addr,subaddr)
#define DEACTIVATEL(addr)
#define DELAY(mindelay)
@@ -171,7 +199,8 @@
#define ENDIF
#define ENDTASK
#define ESTOP
#define EXRAIL
#define EXRAIL
#define EXTT_TURNTABLE(id,vpin,home,description)
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
@@ -194,11 +223,14 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFTTPOSITION(turntable_id,position)
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
#define LATCH(sensor_id)
#define LATCH(sensor_id)
#define LCC(eventid)
#define LCCX(senderid,eventid)
#define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg)
@@ -208,18 +240,24 @@
#define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONCLOCKMINS(mins)
#define ONOVERLOAD(track_id)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONLCC(sender,event)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONRED(signal_id)
#define ONROTATE(turntable_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
#define PARSE(msg)
#ifndef DISABLE_PROG
#define POM(cv,value)
#endif
#define POWEROFF
#define POWERON
#define READ_LOCO
@@ -229,8 +267,14 @@
#define RESUME
#define RETURN
#define REV(speed)
#define ROUTE(id,description)
#define ROTATE(turntable_id,position,activity)
#define ROTATE_DCC(turntable_id,position)
#define ROSTER(cab,name,funcmap...)
#define ROUTE(id,description)
#define ROUTE_ACTIVE(id)
#define ROUTE_INACTIVE(id)
#define ROUTE_HIDDEN(id)
#define ROUTE_CAPTION(id,caption)
#define SENDLOCO(cab,route)
#define SEQUENCE(id)
#define SERIAL(msg)
@@ -246,19 +290,26 @@
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin)
#define SET_TRACK(track,mode)
#define SET_POWER(track,onoff)
#define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed)
#define START(route)
#define STOP
#define THROW(id)
#define THROW(id)
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
#define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
#define UNJOIN
#define UNLATCH(sensor_id)
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin)
#ifndef IO_NO_HAL
#define WAITFORTT(turntable_id)
#endif
#define WITHROTTLE(msg)
#define XFOFF(cab,func)
#define XFON(cab,func)
#endif

291
EXRAIL2Parser.cpp Normal file
View File

@@ -0,0 +1,291 @@
/*
* © 2021 Neil McKechnie
* © 2021-2023 Harald Barth
* © 2020-2023 Chris Harlow
* © 2022-2023 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/>.
*/
// THIS file is an extension of the RMFT2 class
// normally found in EXRAIL2.cpp
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_START=23232;
const int16_t HASH_KEYWORD_RESERVE=11392;
const int16_t HASH_KEYWORD_FREE=-23052;
const int16_t HASH_KEYWORD_LATCH=1618;
const int16_t HASH_KEYWORD_UNLATCH=1353;
const int16_t HASH_KEYWORD_PAUSE=-4142;
const int16_t HASH_KEYWORD_RESUME=27609;
const int16_t HASH_KEYWORD_KILL=5218;
const int16_t HASH_KEYWORD_ALL=3457;
const int16_t HASH_KEYWORD_ROUTES=-3702;
const int16_t HASH_KEYWORD_RED=26099;
const int16_t HASH_KEYWORD_AMBER=18713;
const int16_t HASH_KEYWORD_GREEN=-31493;
const int16_t HASH_KEYWORD_A='A';
// This filter intercepts <> commands to do the following:
// - Implement RMFT specific commands/diagnostics
// - Reject/modify JMRI commands that would interfere with RMFT processing
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
bool reject=false;
switch(opcode) {
case 'D':
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
opcode=0;
}
break;
case '/': // New EXRAIL command
reject=!parseSlash(stream,paramCount,p);
opcode=0;
break;
case 'L':
// This entire code block is compiled out if LLC macros not used
if (!(compileFeatures & FEATURE_LCC)) return;
if (paramCount==0) { //<L> LCC adapter introducing self
LCCSerial=stream; // now we know where to send events we raise
// loop through all possible sent events
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
if (opcode==OPCODE_LCCX) { // long form LCC
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
}}
// we stream the hex events we wish to listen to
// and at the same time build the event index looku.
int eventIndex=0;
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_ONLCC) {
onLCCLookup[eventIndex]=progCounter; // TODO skip...
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
eventIndex,
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
eventIndex++;
}
}
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
opcode=0;
break;
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
reject=eventid<0 || eventid>=countLCCLookup;
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
}
break;
case 'J': // throttle info commands
if (paramCount<1) return;
switch(p[0]) {
case HASH_KEYWORD_A: // <JA> returns automations/routes
if (paramCount==1) {// <JA>
StringFormatter::send(stream, F("<jA"));
routeLookup->stream(stream);
StringFormatter::send(stream, F(">\n"));
opcode=0;
return;
}
if (paramCount==2) { // <JA id>
uint16_t id=p[1];
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
id, getRouteType(id), getRouteDescription(id));
if (compileFeatures & FEATURE_ROUTESTATE) {
// Send any non-default button states or captions
int16_t statePos=routeLookup->findPosition(id);
if (statePos>=0) {
if (routeStateArray[statePos])
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
if (routeCaptionArray[statePos])
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
}
}
opcode=0;
return;
}
break;
default:
break;
}
default: // other commands pass through
break;
}
}
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (paramCount==0) { // STATUS
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
RMFT2 * task=loopTask;
while(task) {
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
(int)(task->taskId),task->progCounter,task->loco,
task->invert?'I':' ',
task->speedo,
task->forward?'F':'R'
);
task=task->next;
if (task==loopTask) break;
}
// Now stream the flags
for (int id=0;id<MAX_FLAGS; id++) {
byte flag=flags[id];
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
StringFormatter::send(stream,F("\nflags[%d] "),id);
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
if (compileFeatures & FEATURE_SIGNAL) {
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
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]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case HASH_KEYWORD_PAUSE: // </ PAUSE>
if (paramCount!=1) return false;
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=(RMFT2 *)1; // Impossible task address
return true;
case HASH_KEYWORD_RESUME: // </ RESUME>
if (paramCount!=1) return false;
pausingTask=NULL;
{
RMFT2 * task=loopTask;
while(task) {
if (task->loco) task->driveLoco(task->speedo);
task=task->next;
if (task==loopTask) break;
}
}
return true;
case HASH_KEYWORD_START: // </ START [cab] route >
if (paramCount<2 || paramCount>3) return false;
{
int route=(paramCount==2) ? p[1] : p[2];
uint16_t cab=(paramCount==2)? 0 : p[1];
int pc=routeLookup->find(route);
if (pc<0) return false;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
}
return true;
default:
break;
}
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
return true;
}
// all other / commands take 1 parameter
if (paramCount!=2 ) return false;
switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid|ALL
{
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
RMFT2 * task=loopTask;
while(task) {
if (task->taskId==p[1]) {
task->kill(F("KILL"));
return true;
}
task=task->next;
if (task==loopTask) break;
}
}
return false;
case HASH_KEYWORD_RESERVE: // force reserve a section
return setFlag(p[1],SECTION_FLAG);
case HASH_KEYWORD_FREE: // force free a section
return setFlag(p[1],0,SECTION_FLAG);
case HASH_KEYWORD_LATCH:
return setFlag(p[1], LATCH_FLAG);
case HASH_KEYWORD_UNLATCH:
return setFlag(p[1], 0, LATCH_FLAG);
case HASH_KEYWORD_RED:
doSignal(p[1],SIGNAL_RED);
return true;
case HASH_KEYWORD_AMBER:
doSignal(p[1],SIGNAL_AMBER);
return true;
case HASH_KEYWORD_GREEN:
doSignal(p[1],SIGNAL_GREEN);
return true;
default:
return false;
}
}

View File

@@ -1,6 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022-2023 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -53,6 +54,8 @@
// helper macro for turnout descriptions, creates NULL for missing description
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
// helper macro for turntable descriptions, creates NULL for missing description
#define T_DESC(tid,pid,desc) if(turntableId==tid && positionId==pid) return ("" desc)[0]?F("" desc):NULL;
// helper macro for turnout description as HIDDEN
#define HIDDEN "\x01"
@@ -60,13 +63,19 @@
// (10#mins)%100)
#define STRIP_ZERO(value) 10##value%100
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
// Pass 1h Implements HAL macro by creating exrailHalSetup function
// Also allows creating EXTurntable object
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
@@ -74,20 +83,52 @@ void exrailHalSetup() {
#include "myAutomation.h"
}
// Pass 1c detect compile time featurtes
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
#undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
#undef LCC
#define LCC(eventid) | FEATURE_LCC
#undef LCCX
#define LCCX(senderid,eventid) | FEATURE_LCC
#undef ONLCC
#define ONLCC(senderid,eventid) | FEATURE_LCC
#undef ROUTE_ACTIVE
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
#undef ROUTE_INACTIVE
#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE
#undef ROUTE_HIDDEN
#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE
#undef ROUTE_CAPTION
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
const byte RMFT2::compileFeatures = 0
#include "myAutomation.h"
;
// Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
INT16_MAX};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
INT16_MAX};
// Pass 3 Create route descriptions:
#undef ROUTE
@@ -120,6 +161,12 @@ const int StringMacroTracker1=__COUNTER__;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef ROUTE_CAPTION
#define ROUTE_CAPTION(id,caption) \
case (__COUNTER__ - StringMacroTracker1) : {\
manageRouteCaption(id,F(caption));\
return;\
}
#undef SERIAL
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
@@ -152,6 +199,8 @@ const int StringMacroTracker1=__COUNTER__;
lcdid=id;\
break;\
}
#undef WITHROTTLE
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
@@ -169,6 +218,8 @@ void RMFT2::printMessage(uint16_t id) {
#include "EXRAIL2MacroReset.h"
#undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef TURNOUTL
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
@@ -184,10 +235,35 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
return NULL;
}
// Pass to get turntable descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef DCC_TURNTABLE
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
switch (turntableId) {
#include "myAutomation.h"
default:break;
}
return NULL;
}
// Pass to get turntable position descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef TT_ADDPOSITION
#define TT_ADDPOSITION(turntable_id,position,value,home,description...) T_DESC(turntable_id,position,description)
const FSH * RMFT2::getTurntablePositionDescription(int16_t turntableId, uint8_t positionId) {
#include "myAutomation.h"
return NULL;
}
// Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) +1
#define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1)
const byte RMFT2::rosterNameCount=0
#include "myAutomation.h"
;
@@ -198,7 +274,7 @@ const byte RMFT2::rosterNameCount=0
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
INT16_MAX};
// Pass 7: Roster names getter
#include "EXRAIL2MacroReset.h"
@@ -220,7 +296,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#include "myAutomation.h"
default: break;
}
return F("");
return NULL;
}
// Pass 8 Signal definitions
@@ -240,6 +316,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
// Pass 9 ONLCC counter and lookup array
#include "EXRAIL2MacroReset.h"
#undef ONLCC
#define ONLCC(sender,event) +1
const int RMFT2::countLCCLookup=0
#include "myAutomation.h"
;
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
@@ -253,6 +339,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id),
#define ALIAS(name,value...)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
@@ -265,6 +352,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#ifndef IO_NO_HAL
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
#endif
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
@@ -278,7 +368,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ENDIF OPCODE_ENDIF,0,0,
#define ENDTASK OPCODE_ENDTASK,0,0,
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#define EXRAIL
#ifndef IO_NO_HAL
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
#endif
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func),
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
@@ -301,11 +394,19 @@ const HIGHFLASH 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,
#ifndef IO_NO_HAL
#define IFTTPOSITION(id,position) OPCODE_IFTTPOSITION,V(id),OPCODE_PAD,V(position),
#endif
#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,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCC(eventid) OPCODE_LCC,V(eventid),
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
@@ -314,17 +415,28 @@ const HIGHFLASH 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 ONLCC(sender,event) OPCODE_ONLCC,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(TRACK_NUMBER_##track_id),
#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),
#ifndef IO_NO_HAL
#define ONROTATE(id) OPCODE_ONROTATE,V(id),
#endif
#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 PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#ifndef DISABLE_PROG
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#endif
#define POWEROFF OPCODE_POWEROFF,0,0,
#define POWERON OPCODE_POWERON,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
@@ -337,7 +449,15 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define RETURN OPCODE_RETURN,0,0,
#define REV(speed) OPCODE_REV,V(speed),
#define ROSTER(cabid,name,funcmap...)
#ifndef IO_NO_HAL
#define ROTATE(id,position,activity) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(EXTurntable::activity),
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
#endif
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id),
#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id),
#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id),
#define ROUTE_CAPTION(id,caption) PRINT(caption)
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define SERIAL(msg) PRINT(msg)
@@ -353,19 +473,28 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define SET(pin) OPCODE_SET,V(pin),
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route),
#define START(route) OPCODE_START,V(route),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#ifndef IO_NO_HAL
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
#endif
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
#define UNJOIN OPCODE_UNJOIN,0,0,
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#define WITHROTTLE(msg) PRINT(msg)
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#ifndef IO_NO_HAL
#define WAITFORTT(turntable_id) OPCODE_WAITFORTT,V(turntable_id),
#endif
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),

View File

@@ -136,7 +136,7 @@ bool EthernetInterface::checkLink() {
DIAG(F("Ethernet cable connected"));
connected=true;
#ifdef IP_ADDRESS
setLocalIP(IP_ADDRESS); // for static IP, set it again
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
#endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT

View File

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

View File

@@ -72,22 +72,27 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
void I2CManagerClass::begin(void) {
if (!_beginCompleted) {
_beginCompleted = true;
// Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C
const FSH *message = F("WARNING: Check I2C %S line for short/pullup");
pinMode(SDA, INPUT);
if (!digitalRead(SDA))
DIAG(message, F("SDA"));
pinMode(SCL, INPUT);
if (!digitalRead(SCL))
DIAG(message, F("SCL"));
// Now initialise I2C
_initialise();
#if defined(I2C_USE_WIRE)
DIAG(F("I2CManager: Using Wire library"));
#endif
// Check for short-circuits on I2C
if (!digitalRead(SDA))
DIAG(F("WARNING: Possible short-circuit on I2C SDA line"));
if (!digitalRead(SCL))
DIAG(F("WARNING: Possible short-circuit on I2C SCL line"));
// Probe and list devices. Use standard mode
// (clock speed 100kHz) for best device compatibility.
_setClock(100000);
unsigned long originalTimeout = _timeout;
uint32_t originalTimeout = _timeout;
setTimeout(1000); // use 1ms timeout for probes
#if defined(I2C_EXTENDED_ADDRESS)

View File

@@ -84,8 +84,6 @@
*/
/*
* Future enhancement possibility:
*
* I2C Multiplexer (e.g. TCA9547, TCA9548)
*
* A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27
@@ -98,11 +96,6 @@
* Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be
* put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions.
*
* Changes required: Increase the size of the I2CAddress field in the IODevice class from uint8_t to uint16_t.
* The most significant byte would contain a '1' bit flag, the multiplexer number (0-7) and bus number (0-7). Then, when performing
* an I2C operation, the I2CManager would check this byte and, if zero, do what it currently does. If the byte is non-zero, then
* that means the device is connected via a multiplexer so the I2C transaction should be preceded by a select command issued to the
* relevant multiplexer.
*
* Non-interrupting I2C:
*
@@ -138,13 +131,9 @@
// may be extended to include multiple buses, and other features.
// Uncomment to enable extended address.
//
// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc,
// it should be cast to (int) to ensure that the address value is passed rather than
// the struct.
//#define I2C_EXTENDED_ADDRESS
/////////////////////////////////////////////////////////////////////////////////////
// Extended I2C Address type to facilitate extended I2C addresses including
// I2C multiplexer support.
@@ -184,7 +173,7 @@ enum I2CSubBus : uint8_t {
#endif
SubBus_No, // Number of subbuses (highest + 1)
SubBus_None = 254, // Disable all sub-buses on selected mux
SubBus_All = 255, // Enable all sub-buses
SubBus_All = 255, // Enable all sub-buses (not supported by some multiplexers)
};
// Type to hold I2C address
@@ -496,7 +485,7 @@ private:
// When retries are enabled, the timeout applies to each
// try, and failure from timeout does not get retried.
// A value of 0 means disable timeout monitoring.
unsigned long _timeout = 100000UL;
uint32_t _timeout = 100000UL;
// Finish off request block by waiting for completion and posting status.
uint8_t finishRB(I2CRB *rb, uint8_t status);
@@ -506,10 +495,12 @@ private:
#if defined(I2C_EXTENDED_ADDRESS)
// Count of I2C multiplexers found when initialising. If there is only one
// MUX then the subbus does not de-selecting after use; however, if there
// MUX then the subbus does not need de-selecting after use; however, if there
// are two or more, then the subbus must be deselected to avoid multiple
// sub-bus legs on different multiplexers being accessible simultaneously.
private:
uint8_t _muxCount = 0;
public:
uint8_t getMuxCount() { return _muxCount; }
#endif
@@ -522,6 +513,7 @@ private:
// Within the queue, each request's nextRequest field points to the
// next request, or NULL.
// Mark volatile as they are updated by IRC and read/written elsewhere.
private:
I2CRB * volatile queueHead = NULL;
I2CRB * volatile queueTail = NULL;
@@ -540,14 +532,15 @@ private:
uint8_t bytesToSend = 0;
uint8_t bytesToReceive = 0;
uint8_t operation = 0;
unsigned long startTime = 0;
uint32_t startTime = 0;
uint8_t muxPhase = 0;
uint8_t muxAddress = 0;
uint8_t muxData[1];
uint8_t deviceAddress;
const uint8_t *sendBuffer;
uint8_t *receiveBuffer;
uint8_t transactionState = 0;
volatile uint32_t pendingClockSpeed = 0;
void startTransaction();

View File

@@ -223,10 +223,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
#if defined(I2C_USE_INTERRUPTS)
ISR(TWI_vect) {
// pinMode(2,OUTPUT);
// digitalWrite(2,1);
I2CManager.handleInterrupt();
// digitalWrite(2,0);
}
#endif

View File

@@ -172,6 +172,10 @@ void I2CManagerClass::startTransaction() {
* Function to queue a request block and initiate operations.
***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) {
if (((req->operation & OPERATION_MASK) == OPERATION_READ) && req->readLen == 0)
return; // Ignore null read
req->status = I2C_STATUS_PENDING;
req->nextRequest = NULL;
ATOMIC_BLOCK() {
@@ -184,6 +188,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
}
/***************************************************************************
* Initiate a write to an I2C device (non-blocking operation)
***************************************************************************/
@@ -240,8 +245,8 @@ void I2CManagerClass::checkForTimeout() {
I2CRB *t = queueHead;
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
// Check for timeout
unsigned long elapsed = micros() - startTime;
if (elapsed > _timeout) {
int32_t elapsed = micros() - startTime;
if (elapsed > (int32_t)_timeout) {
#ifdef DIAG_IO
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
#endif
@@ -300,12 +305,12 @@ void I2CManagerClass::handleInterrupt() {
// Check if current request has completed. If there's a current request
// and state isn't active then state contains the completion status of the request.
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
if (state == I2C_STATE_COMPLETED && currentRequest != NULL && currentRequest == queueHead) {
// Operation has completed.
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|| currentRequest->operation & OPERATION_NORETRY)
{
// Status is OK, or has failed and retry count exceeded, or retries disabled.
// Status is OK, or has failed and retry count exceeded, or failed and retries disabled.
#if defined(I2C_EXTENDED_ADDRESS)
if (muxPhase == MuxPhase_PROLOG ) {
overallStatus = completionStatus;

View File

@@ -26,27 +26,44 @@
#include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
#include "stm32f4xx_hal_rcc.h"
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
/*****************************************************************************
* STM32F4xx I2C native driver support
*
* Nucleo-64 and Nucleo-144 boards all use I2C1 as the default I2C peripheral
* Later we may wish to support other STM32 boards, allow use of an alternate
* I2C bus, or more than one I2C bus on the STM32 architecture
*****************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
void I2C1_IRQHandler() {
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
// and Nucleo-144 variants
I2C_TypeDef *s = I2C1;
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
uint32_t APB1clk1; // Peripheral Input Clock speed in Hz.
uint32_t i2c_MHz; // Peripheral Input Clock speed in MHz.
// IRQ handler for I2C1, replacing the weak definition in the STM32 HAL
extern "C" void I2C1_EV_IRQHandler(void) {
I2CManager.handleInterrupt();
}
extern "C" void I2C1_ER_IRQHandler(void) {
I2CManager.handleInterrupt();
}
#else
#warning STM32 board selected is not yet supported - so I2C1 peripheral is not defined
#endif
#endif
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
I2C_TypeDef *s = I2C1;
#define I2C_IRQn I2C1_EV_IRQn
#define I2C_BUSFREQ 16
// Peripheral Input Clock speed in MHz.
// For STM32F446RE, the speed is 45MHz. Ideally, this should be determined
// at run-time from the APB1 clock, as it can vary from STM32 family to family.
// #define I2C_PERIPH_CLK 45
// I2C SR1 Status Register #1 bit definitions for convenience
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
@@ -80,52 +97,66 @@ I2C_TypeDef *s = I2C1;
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
// States of the STM32 I2C driver state machine
enum {TS_IDLE,TS_START,TS_W_ADDR,TS_W_DATA,TS_W_STOP,TS_R_ADDR,TS_R_DATA,TS_R_STOP};
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
// Use 10x the rise time spec to enable integer divide of 50ns clock period
uint16_t t_rise;
uint32_t ccr_freq;
if (i2cClockSpeed < 200000L) {
// i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
else if (i2cClockSpeed < 800000L)
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
// writes to CR1 while STOP is being executed!
// Disable the I2C device, as TRISE can only be programmed whilst disabled
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
asm("nop"); // wait a bit... suggestion from online!
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
if (i2cClockSpeed > 100000UL)
{
i2cClockSpeed = 400000L;
t_rise = 0x06; // (300ns / 62.5ns) + 1;
// } else if (i2cClockSpeed < 1200000L) {
// i2cClockSpeed = 1000000L;
// t_rise = 120;
// if (i2cClockSpeed > 400000L)
// i2cClockSpeed = 400000L;
t_rise = 300; // nanoseconds
}
else
{
i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
// i2cClockSpeed = 100000L;
t_rise = 1000; // nanoseconds
}
// Enable the I2C master mode
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
// Software reset the I2C peripheral
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
// Release reset
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Calculate baudrate - using a rise time appropriate for the speed
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
// Configure the rise time register - max allowed tRISE is 1000ns,
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = (uint16_t)ccr_freq;
// Bit 14: Duty, fast mode duty cycle (use 2:1)
// Bit 11-0: FREQR
// if (i2cClockSpeed > 400000UL) {
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
// } else {
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
// if (i2cClockSpeed > 100000UL)
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
// }
// Configure the rise time register
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
@@ -136,32 +167,54 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
***************************************************************************/
void I2CManagerClass::I2C_init()
{
//Setting up the clocks
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
// Query the clockspeed from the STM32 HAL layer
APB1clk1 = HAL_RCC_GetPCLK1Freq();
i2c_MHz = APB1clk1 / 1000000UL;
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
// Enable clocks
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
// Reset the I2C1 peripheral to initial state
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
// Standard I2C pins are SCL on PB8 and SDA on PB9
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
GPIOB->MODER &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all MODER bits for PB8 and PB9
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
// Alt Function High register routing pins PB8 and PB9 for I2C1:
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
GPIOB->AFR[1] &= ~((15<<0) | (15<<4)); // Clear all AFR bits for PB8 on low nibble, PB9 on next nibble up
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
// Software reset the I2C peripheral
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
asm("nop"); // wait a bit... suggestion from online!
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Program the peripheral input clock in CR2 Register in order to generate correct timings
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
// Clear all bits in I2C CR2 register except reserved bits
s->CR2 &= 0xE000;
// Set I2C peripheral clock frequency
// s->CR2 |= I2C_PERIPH_CLK;
s->CR2 |= i2c_MHz;
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
// set own address to 00 - not used in master mode
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C_IRQn);
NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C1_EV_IRQn);
NVIC_SetPriority(I2C1_ER_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C1_ER_IRQn);
// CR2 Interrupt Settings
// Bit 15-13: reserved
@@ -172,23 +225,28 @@ void I2CManagerClass::I2C_init()
// Bit 8: ITERREN - Error interrupt enable
// Bit 7-6: reserved
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
s->CR2 |= 0x0300; // Enable Event and Error interrupts
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
#endif
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
// Calculate baudrate and set default rate for now
// Configure the Clock Control Register for 100KHz SCL frequency
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = 0x0050;
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
// s->CCR = I2C_PERIPH_CLK * 5;
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
// Configure the rise time register - max allowed in 1000ns
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
// Setting bus idle mode and wait for sync
}
/***************************************************************************
@@ -198,49 +256,30 @@ void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
rxCount = txCount = 0;
uint8_t temp;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
//while (s->SR2 & I2C_SR2_BUSY) {}
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start for read operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
// Special case for 1 byte reads!
if (bytesToReceive == 1)
{
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
else
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
else {
// Send start for write operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
// Check there's no STOP still in progress. If we OR the START bit into CR1
// and the STOP bit is already set, we could output multiple STOP conditions.
while (s->CR1 & I2C_CR1_STOP) {} // Wait for STOP bit to reset
s->CR2 |= (I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable interrupts
s->CR2 &= ~I2C_CR2_ITBUFEN; // Don't enable buffer interupts yet.
s->CR1 &= ~I2C_CR1_POS; // Clear the POS bit
s->CR1 |= (I2C_CR1_ACK | I2C_CR1_START); // Enable the ACK and generate START
transactionState = TS_START;
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->CR1 |= I2C_CR1_STOP; // Stop I2C
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
/***************************************************************************
@@ -252,9 +291,11 @@ void I2CManagerClass::I2C_close() {
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
// Should never happen, but wait for up to 500us only.
unsigned long startTime = micros();
while ((s->CR1 && I2C_CR1_PE) != 0) {
if (micros() - startTime >= 500UL) break;
while ((s->CR1 & I2C_CR1_PE) != 0) {
if ((int32_t)(micros() - startTime) >= 500) break;
}
NVIC_DisableIRQ(I2C1_EV_IRQn);
NVIC_DisableIRQ(I2C1_ER_IRQn);
}
/***************************************************************************
@@ -263,50 +304,217 @@ void I2CManagerClass::I2C_close() {
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
volatile uint16_t temp_sr1, temp_sr2;
if (s->SR1 && I2C_SR1_ARLO) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->SR1 && I2C_SR1_BERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
} else if (s->SR1 && I2C_SR1_TXE) {
// Master write completed
if (s->SR1 && (1<<10)) {
// Nacked, send stop.
I2C_sendStop();
temp_sr1 = s->SR1;
// Check for errors first
if (temp_sr1 & (I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR)) {
// Check which error flag is set
if (temp_sr1 & I2C_SR1_AF)
{
s->SR1 &= ~(I2C_SR1_AF); // Clear AF
I2C_sendStop(); // Clear the bus
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) {
// Acked, so send next byte
s->DR = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// Check both TxE/BTF == 1 before generating stop
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
// No more data to send/receive. Initiate a STOP condition and finish
I2C_sendStop();
}
else if (temp_sr1 & I2C_SR1_ARLO)
{
// Arbitration lost, restart
s->SR1 &= ~(I2C_SR1_ARLO); // Clear ARLO
I2C_sendStart(); // Reinitiate request
transactionState = TS_START;
}
else if (temp_sr1 & I2C_SR1_BERR)
{
// Bus error
s->SR1 &= ~(I2C_SR1_BERR); // Clear BERR
I2C_sendStop(); // Clear the bus
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
}
} else if (s->SR1 && I2C_SR1_RXNE) {
// Master read completed without errors
if (bytesToReceive == 1) {
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED;
} else if (bytesToReceive) {
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
else {
// No error flags, so process event according to current state.
switch (transactionState) {
case TS_START:
if (temp_sr1 & I2C_SR1_SB) {
// Event EV5
// Start bit has been sent successfully and we have the bus.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) {
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
transactionState = TS_R_ADDR;
} else {
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
transactionState = TS_W_ADDR;
}
}
// SB bit is cleared by writing to DR (already done).
break;
case TS_W_ADDR:
if (temp_sr1 & I2C_SR1_ADDR) {
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
// Event EV6
// Address sent successfully, device has ack'd in response.
if (!bytesToSend) {
I2C_sendStop();
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
} else {
// Put one byte into DR to load shift register.
s->DR = sendBuffer[txCount++];
bytesToSend--;
if (bytesToSend) {
// Put another byte to load DR
s->DR = sendBuffer[txCount++];
bytesToSend--;
}
if (!bytesToSend) {
// No more bytes to send.
// The TXE interrupt occurs when the DR is empty, and the BTF interrupt
// occurs when the shift register is also empty (one character later).
// To avoid repeated TXE interrupts during this time, we disable TXE interrupt.
s->CR2 &= ~I2C_CR2_ITBUFEN; // Wait for BTF interrupt, disable TXE interrupt
transactionState = TS_W_STOP;
} else {
// More data remaining to send after this interrupt, enable TXE interrupt.
s->CR2 |= I2C_CR2_ITBUFEN;
transactionState = TS_W_DATA;
}
}
}
break;
case TS_W_DATA:
if (temp_sr1 & I2C_SR1_TXE) {
// Event EV8_1/EV8
// Transmitter empty, write a byte to it.
if (bytesToSend) {
s->DR = sendBuffer[txCount++];
bytesToSend--;
if (!bytesToSend) {
s->CR2 &= ~I2C_CR2_ITBUFEN; // Disable TXE interrupt
transactionState = TS_W_STOP;
}
}
}
break;
case TS_W_STOP:
if (temp_sr1 & I2C_SR1_BTF) {
// Event EV8_2
// Done, last character sent. Anything to receive?
if (bytesToReceive) {
I2C_sendStart();
// NOTE: Three redundant BTF interrupts take place between the
// first BTF interrupt and the START interrupt. I've tried all sorts
// of ways to eliminate them, and the only thing that worked for
// me was to loop until the BTF bit becomes reset. Either way,
// it's a waste of processor time. Anyone got a solution?
//while (s->SR1 && I2C_SR1_BTF) {}
transactionState = TS_START;
} else {
I2C_sendStop();
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
}
s->SR1 &= I2C_SR1_BTF; // Clear BTF interrupt
}
break;
case TS_R_ADDR:
if (temp_sr1 & I2C_SR1_ADDR) {
// Event EV6
// Address sent for receive.
// The next bit is different depending on whether there are
// 1 byte, 2 bytes or >2 bytes to be received, in accordance with the
// Programmers Reference RM0390.
if (bytesToReceive == 1) {
// Receive 1 byte
s->CR1 &= ~I2C_CR1_ACK; // Disable ack
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
// Next step will occur after a RXNE interrupt, so enable it
s->CR2 |= I2C_CR2_ITBUFEN;
transactionState = TS_R_STOP;
} else if (bytesToReceive == 2) {
// Receive 2 bytes
s->CR1 &= ~I2C_CR1_ACK; // Disable ACK for final byte
s->CR1 |= I2C_CR1_POS; // set POS flag to delay effect of ACK flag
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
s->CR2 &= ~I2C_CR2_ITBUFEN;
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
transactionState = TS_R_STOP;
} else {
// >2 bytes, just wait for bytes to come in and ack them for the time being
// (ack flag has already been set).
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
s->CR2 &= ~I2C_CR2_ITBUFEN;
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
transactionState = TS_R_DATA;
}
}
break;
case TS_R_DATA:
// Event EV7/EV7_1
if (temp_sr1 & I2C_SR1_BTF) {
// Byte received in receiver - read next byte
if (bytesToReceive == 3) {
// Getting close to the last byte, so a specific sequence is recommended.
s->CR1 &= ~I2C_CR1_ACK; // Reset ack for next byte received.
transactionState = TS_R_STOP;
}
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
break;
case TS_R_STOP:
if (temp_sr1 & I2C_SR1_BTF) {
// Event EV7 (last one)
// When we've got here, the receiver has got the last two bytes
// (or one byte, if only one byte is being received),
// and NAK has already been sent, so we need to read from the receiver.
if (bytesToReceive) {
if (bytesToReceive > 1)
I2C_sendStop();
while(bytesToReceive) {
receiveBuffer[rxCount++] = s->DR; // Store received byte(s)
bytesToReceive--;
}
// Finish.
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
}
} else if (temp_sr1 & I2C_SR1_RXNE) {
if (bytesToReceive == 1) {
// One byte on a single-byte transfer. Ack has already been set.
I2C_sendStop();
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
// Finish.
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
} else
s->SR1 &= I2C_SR1_RXNE; // Acknowledge interrupt
}
break;
}
// If we've received an interrupt at any other time, we're not interested so clear it
// to prevent it recurring ad infinitum.
s->SR1 = 0;
}
}
#endif /* I2CMANAGER_STM32_H */

View File

@@ -77,7 +77,15 @@ static uint8_t muxSelect(I2CAddress address) {
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
uint8_t data = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
(1 << subBus);
#if defined(I2CMUX_PCA9547)
0x08 | subBus;
#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544)
0x04 | subBus; // NB Only 2 or 4 subbuses respectively
#else
// Default behaviour for most MUXs is to use a mask
// with a bit set for the subBus to be enabled
1 << subBus;
#endif
Wire.write(&data, 1);
return Wire.endTransmission(true); // have to release I2C bus for it to work
}

View File

@@ -63,15 +63,31 @@ void IODevice::begin() {
if (exrailHalSetup)
exrailHalSetup();
// Predefine two PCA9685 modules 0x40-0x41
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
// Allocates 32 pins 100-131
PCA9685::create(100, 16, 0x40);
PCA9685::create(116, 16, 0x41);
if (checkNoOverlap(100, 16, 0x40)) {
PCA9685::create(100, 16, 0x40);
} else {
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
}
if (checkNoOverlap(116, 16, 0x41)) {
PCA9685::create(116, 16, 0x41);
} else {
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
}
// Predefine two MCP23017 module 0x20/0x21
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
// Allocates 32 pins 164-195
MCP23017::create(164, 16, 0x20);
MCP23017::create(180, 16, 0x21);
if (checkNoOverlap(164, 16, 0x20)) {
MCP23017::create(164, 16, 0x20);
} else {
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
}
if (checkNoOverlap(180, 16, 0x21)) {
MCP23017::create(180, 16, 0x21);
} else {
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
}
}
// reset() function to reinitialise all devices
@@ -160,6 +176,13 @@ bool IODevice::exists(VPIN vpin) {
return findDevice(vpin) != NULL;
}
// Return the status of the device att vpin.
uint8_t IODevice::getStatus(VPIN vpin) {
IODevice *dev = findDevice(vpin);
if (!dev) return false;
return dev->_deviceState;
}
// check whether the pin supports notification. If so, then regular _read calls are not required.
bool IODevice::hasCallback(VPIN vpin) {
IODevice *dev = findDevice(vpin);
@@ -169,7 +192,7 @@ bool IODevice::hasCallback(VPIN vpin) {
// Display (to diagnostics) details of the device.
void IODevice::_display() {
DIAG(F("Unknown device Vpins:%d-%d %S"),
DIAG(F("Unknown device Vpins:%u-%u %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
@@ -179,7 +202,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
IODevice *dev = findDevice(vpin);
if (dev) return dev->_configure(vpin, configType, paramCount, params);
#ifdef DIAG_IO
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin);
DIAG(F("IODevice::configure(): VPIN %u not found!"), (int)vpin);
#endif
return false;
}
@@ -191,7 +214,7 @@ int IODevice::read(VPIN vpin) {
return dev->_read(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
DIAG(F("IODevice::read(): VPIN %u not found!"), (int)vpin);
#endif
return false;
}
@@ -203,7 +226,7 @@ int IODevice::readAnalogue(VPIN vpin) {
return dev->_readAnalogue(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
DIAG(F("IODevice::readAnalogue(): VPIN %u not found!"), (int)vpin);
#endif
return -1023;
}
@@ -213,7 +236,7 @@ int IODevice::configureAnalogIn(VPIN vpin) {
return dev->_configureAnalogIn(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin);
#endif
return -1023;
}
@@ -227,7 +250,7 @@ void IODevice::write(VPIN vpin, int value) {
return;
}
#ifdef DIAG_IO
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
DIAG(F("IODevice::write(): VPIN %u not found!"), (int)vpin);
#endif
}
@@ -246,7 +269,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
return;
}
#ifdef DIAG_IO
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
DIAG(F("IODevice::writeAnalogue(): VPIN %u not found!"), (int)vpin);
#endif
}
@@ -314,9 +337,11 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
// Private helper function to check for vpin overlap. Run during setup only.
// returns true if pins DONT overlap with existing device
// TODO: Move the I2C address reservation and checks into the I2CManager code.
// That will enable non-HAL devices to reserve I2C addresses too.
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
#ifdef DIAG_IO
DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString());
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
#endif
VPIN lastPin=firstPin+nPins-1;
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
@@ -327,7 +352,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
if (!noOverlap) {
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
firstPin, lastPin);
return false;
}
@@ -374,7 +399,7 @@ void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
#ifdef DIAG_IO
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
DIAG(F("Arduino _configurePullup pin:%d Val:%d"), pin, p[0]);
#endif
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
return true;
@@ -528,7 +553,7 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) {
}
void ArduinoPins::_display() {
DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
DIAG(F("Arduino Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -27,12 +27,6 @@
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
//#define DIAG_LOOPTIMES
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#define IO_NO_HAL
#endif
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
// animation has completed. This switches off the servo motor, preventing
// the continuous buzz sometimes found on servos, and reducing the
@@ -160,6 +154,9 @@ public:
// exists checks whether there is a device owning the specified vpin
static bool exists(VPIN vpin);
// getStatus returns the state of the device at the specified vpin
static uint8_t getStatus(VPIN vpin);
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
// Without the shared interrupt, input states are scanned periodically to detect changes on
@@ -383,6 +380,7 @@ private:
uint8_t *_pinInUse;
};
#ifndef IO_NO_HAL
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-Turntable.
@@ -411,10 +409,14 @@ private:
void _begin() override;
void _loop(unsigned long currentMicros) override;
int _read(VPIN vpin) override;
void _broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity);
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
void _display() override;
uint8_t _stepperStatus;
uint8_t _previousStatus;
uint8_t _currentActivity;
};
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -467,6 +469,75 @@ protected:
}
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
// This HAL device driver is intended for communication in automation
// sequences. A VPIN can be SET or RESET within a sequence, and its
// current state checked elsewhere using IF, IFNOT, AT etc. or monitored
// from JMRI using a Sensor object (DCC-EX <S ...> command).
// Alternatively, the flag can be set from JMRI and other interfaces
// using the <Z ...> command, to enable or disable actions within a sequence.
//
// Example of configuration in halSetup.h:
//
// FLAGS::create(32000, 128);
//
// or in myAutomation.h:
//
// HAL(FLAGS, 32000, 128);
//
// Both create 128 flags numbered with VPINs 32000-32127.
//
//
class FLAGS : IODevice {
private:
uint8_t *_states = NULL;
public:
static void create(VPIN firstVpin, unsigned int nPins) {
if (checkNoOverlap(firstVpin, nPins))
new FLAGS(firstVpin, nPins);
}
protected:
// Constructor performs static initialisation of the device object
FLAGS (VPIN firstVpin, int nPins) {
_firstVpin = firstVpin;
_nPins = nPins;
_states = (uint8_t *)calloc(1, (_nPins+7)/8);
if (!_states) {
DIAG(F("FLAGS: ERROR Memory Allocation Failure"));
return;
}
addDevice(this);
}
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
if (pin >= _nPins || pin < 0) return 0;
uint8_t mask = 1 << (pin & 7);
return (_states[pin>>3] & mask) ? 1 : 0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
if (pin >= _nPins || pin < 0) return;
uint8_t mask = 1 << (pin & 7);
if (value)
_states[pin>>3] |= mask;
else
_states[pin>>3] &= ~mask;
}
void _display() override {
DIAG(F("FLAGS configured on VPINs %u-%u"),
_firstVpin, _firstVpin+_nPins-1);
}
};
#include "IO_MCP23008.h"
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"

View File

@@ -119,7 +119,7 @@ private:
case STATE_GETVALUE:
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
#ifdef IO_ANALOGUE_SLOW
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
DIAG(F("ADS111x VPIN:%u value:%d"), _currentPin, _value[_currentPin]);
#endif
// Move to next pin
@@ -142,7 +142,7 @@ private:
}
void _display() override {
DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
DIAG(F("ADS111x I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}

View File

@@ -62,7 +62,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
void DCCAccessoryDecoder::_display() {
int endAddress = _packedAddress + _nPins - 1;
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
}

View File

@@ -1,5 +1,5 @@
/*
* © 2022, Neil McKechnie. All rights reserved.
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
@@ -33,10 +33,13 @@
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
*
* Example:
* In mySetup function within mySetup.cpp:
* In halSetup function within myHal.cpp:
* DFPlayer::create(3500, 5, Serial1);
* or in myAutomation.h:
* HAL(DFPlayer, 3500, 5, Serial1)
*
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the
* SD card; e.g. a value of 1 will play the first file, 2 for the second file etc.
* 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.
@@ -61,6 +64,10 @@
* card (as listed by the DIR command in Windows). This may not match the order of the files
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
* files be copied into an empty SDcard in the desired order, one at a time.
*
* The driver now polls the device for its current status every second. Should the device
* fail to respond it will be marked off-line and its busy indicator cleared, to avoid
* lock-ups in automation scripts that are executing for a WAITFOR().
*/
#ifndef IO_DFPlayer_h
@@ -74,21 +81,13 @@ private:
HardwareSerial *_serial;
bool _playing = false;
uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing
uint8_t _lastVolumeLevel = MAXVOLUME;
// 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
unsigned long _commandSendTime; // Time (us) that last transmit took place.
unsigned long _timeoutTime;
uint8_t _recvCMD; // Last received command code byte
bool _awaitingResponse = false;
uint8_t _requestedVolumeLevel = MAXVOLUME;
uint8_t _currentVolume = MAXVOLUME;
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
public:
@@ -113,66 +112,151 @@ protected:
// Send a query to the device to see if it responds
sendPacket(0x42);
_commandSendTime = micros();
_timeoutTime = micros() + 5000000UL; // 5 second timeout
_awaitingResponse = true;
}
void _loop(unsigned long currentMicros) override {
// Check for incoming data on _serial, and update busy flag accordingly.
// 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 && _inputIndex == 0)
_inputIndex = 1;
else if ((c==0xFF && _inputIndex==1)
|| (c==0x3D && _inputIndex==3)
|| (_inputIndex >=4 && _inputIndex <= 8))
_inputIndex++;
else if (c==0x06 && _inputIndex==2) {
// Valid message prefix, so consider the device online
if (_deviceState==DEVSTATE_INITIALISING) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
}
_inputIndex++;
} else if (c==0xEF && _inputIndex==9) {
// End of play
if (_playing) {
#ifdef DIAG_IO
DIAG(F("DFPlayer: Finished"));
#endif
_playing = false;
}
_inputIndex = 0;
} else
_inputIndex = 0; // Unrecognised character sequence, start again!
}
// Check if the initial prompt to device has timed out. Allow 5 seconds
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
// Read responses from device
processIncoming();
// Check if a command sent to device has timed out. Allow 0.5 second for response
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
_awaitingResponse = false;
_playing = false;
}
// Send any commands that need to go.
processOutgoing(currentMicros);
delayUntil(currentMicros + 10000); // Only enter every 10ms
}
// Check for incoming data on _serial, and update busy flag and other state accordingly
void processIncoming() {
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
bool ok = false;
while (_serial->available()) {
int c = _serial->read();
switch (_inputIndex) {
case 0:
if (c == 0x7E) ok = true;
break;
case 1:
if (c == 0xFF) ok = true;
break;
case 2:
if (c== 0x06) ok = true;
break;
case 3:
_recvCMD = c; // CMD byte
ok = true;
break;
case 6:
switch (_recvCMD) {
case 0x42:
// Response to status query
_playing = (c != 0);
// Mark the device online and cancel timeout
if (_deviceState==DEVSTATE_INITIALISING) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
}
_awaitingResponse = false;
break;
case 0x3d:
// End of play
if (_playing) {
#ifdef DIAG_IO
DIAG(F("DFPlayer: Finished"));
#endif
_playing = false;
}
break;
case 0x40:
// Error code
DIAG(F("DFPlayer: Error %d returned from device"), c);
_playing = false;
break;
}
ok = true;
break;
case 4: case 5: case 7: case 8:
ok = true; // Skip over these bytes in message.
break;
case 9:
if (c==0xef) {
// Message finished
}
break;
default:
break;
}
if (ok)
_inputIndex++; // character as expected, so increment index
else
_inputIndex = 0; // otherwise reset.
}
}
// Send any commands that need to be sent
void processOutgoing(unsigned long currentMicros) {
// When two commands are sent in quick succession, the device will often fail to
// execute one. Testing has indicated that a delay of 100ms or more is required
// between successive commands to get reliable operation.
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
if (_currentVolume > _requestedVolumeLevel) {
// Change volume before changing song if volume is reducing.
_currentVolume = _requestedVolumeLevel;
sendPacket(0x06, _currentVolume);
} else if (_requestedSong > 0) {
// Change song
sendPacket(0x03, _requestedSong);
_requestedSong = -1;
} else if (_requestedSong == 0) {
sendPacket(0x16); // Stop playing
_requestedSong = -1;
} else if (_currentVolume < _requestedVolumeLevel) {
// Change volume after changing song if volume is increasing.
_currentVolume = _requestedVolumeLevel;
sendPacket(0x06, _currentVolume);
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
// Poll device every second that other commands aren't being sent,
// to check if it's still connected and responding.
sendPacket(0x42);
if (!_awaitingResponse) {
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
_awaitingResponse = true;
}
}
}
}
// Write with value 1 starts playing a song. The relative pin number is the file number.
// Write with value 0 stops playing.
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value) {
// Value 1, start playing
#ifdef DIAG_IO
DIAG(F("DFPlayer: Play %d"), pin+1);
#endif
sendPacket(0x03, pin+1);
_requestedSong = pin+1;
_playing = true;
} else {
// Value 0, stop playing
#ifdef DIAG_IO
DIAG(F("DFPlayer: Stop"));
#endif
sendPacket(0x16);
_requestedSong = 0; // No song
_playing = false;
}
}
@@ -181,16 +265,13 @@ protected:
// Volume may be specified as second parameter to writeAnalogue.
// If value is zero, the player stops playing.
// WriteAnalogue on second pin sets the output volume.
// If starting a new file and setting volume, then avoid a short burst of loud noise by
// the following strategy:
// - If the volume is increasing, start playing the song before setting the volume,
// - If the volume is decreasing, decrease it and then start playing.
//
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
if (_deviceState == DEVSTATE_FAILED) return;
uint8_t pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume);
DIAG(F("DFPlayer: VPIN:%u FileNo:%d Volume:%d"), vpin, value, volume);
#endif
// Validate parameter.
@@ -199,37 +280,28 @@ protected:
if (pin == 0) {
// Play track
if (value > 0) {
if (volume != 0) {
if (volume <= _lastVolumeLevel)
sendPacket(0x06, volume); // Set volume before starting
sendPacket(0x03, value); // Play track
_playing = true;
if (volume > _lastVolumeLevel)
sendPacket(0x06, volume); // Set volume after starting
_lastVolumeLevel = volume;
} else {
// Volume not changed, just play
sendPacket(0x03, value);
_playing = true;
}
if (volume > 0)
_requestedVolumeLevel = volume;
_requestedSong = value;
_playing = true;
} else {
sendPacket(0x16); // Stop play
_requestedSong = 0; // stop playing
_playing = false;
}
} else if (pin == 1) {
// Set volume (0-30)
sendPacket(0x06, value);
_lastVolumeLevel = volume;
_requestedVolumeLevel = value;
}
}
// A read on any pin indicates whether the player is still playing.
int _read(VPIN) override {
if (_deviceState == DEVSTATE_FAILED) return false;
return _playing;
}
void _display() override {
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
DIAG(F("DFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
@@ -246,7 +318,6 @@ private:
void sendPacket(uint8_t command, uint16_t arg = 0)
{
unsigned long currentMillis = millis();
uint8_t out[] = { 0x7E,
0xFF,
06,
@@ -260,19 +331,10 @@ 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((uint8_t)0);
}
// Now output the command
// Output the command
_serial->write(out, sizeof(out));
_commandSendTime = currentMillis;
_commandSendTime = micros();
}
uint16_t calcChecksum(uint8_t* packet)

View File

@@ -40,24 +40,24 @@ bool FAST_CLOCK_EXISTS = true;
class EXFastClock : public IODevice {
public:
// Constructor
EXFastClock(uint8_t I2CAddress){
_I2CAddress = I2CAddress;
EXFastClock(I2CAddress i2cAddress){
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(uint8_t _I2CAddress) {
static void create(I2CAddress i2cAddress) {
DIAG(F("Checking for Clock"));
// Start by assuming we will find the clock
// Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code.
uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress);
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
DIAG(F("Clock check result - %d"), _checkforclock);
// XXXX change thistosave2 bytes
if (_checkforclock == 0) {
FAST_CLOCK_EXISTS = true;
//DIAG(F("I2C Fast Clock found at %s"), _I2CAddress.toString());
new EXFastClock(_I2CAddress);
//DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
new EXFastClock(i2cAddress);
}
else {
FAST_CLOCK_EXISTS = false;

View File

@@ -34,11 +34,16 @@
* device in use. There is no way for the device driver to sanity check pins are used for the
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
*
* The total number of pins cannot exceed 256 because of the communications packet format.
* The number of analogue inputs cannot exceed 16 because of a limit on the maximum
* I2C packet size of 32 bytes (in the Wire library).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
@@ -64,116 +69,209 @@ public:
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
}
private:
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
_firstVpin = firstVpin;
// Number of pins cannot exceed 256 (1 byte) because of I2C message structure.
if (nPins > 256) nPins = 256;
_nPins = nPins;
_i2cAddress = i2cAddress;
_I2CAddress = i2cAddress;
addDevice(this);
}
void _begin() {
uint8_t status;
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_i2cAddress)) {
_command4Buffer[0] = EXIOINIT;
_command4Buffer[1] = _nPins;
_command4Buffer[2] = _firstVpin & 0xFF;
_command4Buffer[3] = _firstVpin >> 8;
if (I2CManager.exists(_I2CAddress)) {
// 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:%s"), _i2cAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
// NB The I2C calls here are done as blocking calls, as they're not time-critical
// during initialisation and the reads require waiting for a response anyway.
// Hence we can allocate I/O buffers from the stack.
uint8_t receiveBuffer[3];
uint8_t commandBuffer[4] = {EXIOINIT, (uint8_t)_nPins, (uint8_t)(_firstVpin & 0xFF), (uint8_t)(_firstVpin >> 8)};
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
if (status == I2C_STATUS_OK) {
if (receiveBuffer[0] == EXIOPINS) {
_numDigitalPins = receiveBuffer[1];
_numAnaloguePins = receiveBuffer[2];
// See if we already have suitable buffers assigned
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
if (_digitalPinBytes < digitalBytesNeeded) {
// Not enough space, free any existing buffer and allocate a new one
if (_digitalPinBytes > 0) free(_digitalInputStates);
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
_digitalPinBytes = digitalBytesNeeded;
}
size_t analogueBytesNeeded = _numAnaloguePins * 2;
if (_analoguePinBytes < analogueBytesNeeded) {
// Free any existing buffers and allocate new ones.
if (_analoguePinBytes > 0) {
free(_analogueInputBuffer);
free(_analogueInputStates);
free(_analoguePinMap);
}
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
_analoguePinBytes = analogueBytesNeeded;
}
} else {
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
}
// We now need to retrieve the analogue pin map
_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:%s, Version v%d.%d.%d"),
_I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
if (status == I2C_STATUS_OK) {
commandBuffer[0] = EXIOINITA;
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
}
if (status == I2C_STATUS_OK) {
// Attempt to get version, if we don't get it, we don't care, don't go offline
uint8_t versionBuffer[3];
commandBuffer[0] = EXIOVER;
if (I2CManager.read(_I2CAddress, versionBuffer, sizeof(versionBuffer), commandBuffer, 1) == I2C_STATUS_OK) {
_majorVer = versionBuffer[0];
_minorVer = versionBuffer[1];
_patchVer = versionBuffer[2];
}
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer);
#ifdef DIAG_IO
_display();
_display();
#endif
}
if (status != I2C_STATUS_OK)
reportError(status);
} else {
DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString());
DIAG(F("EX-IOExpander I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested.
// Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can
// be allocated from the stack to reduce RAM allocation.
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (paramCount != 1) return false;
int pin = vpin - _firstVpin;
if (configType == CONFIGURE_INPUT) {
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 {
uint8_t pullup = params[0];
uint8_t outBuffer[] = {EXIODPUP, (uint8_t)pin, pullup};
uint8_t responseBuffer[1];
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
outBuffer, sizeof(outBuffer));
if (status == I2C_STATUS_OK) {
if (responseBuffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("EXIOVpin %u cannot be used as a digital input pin"), (int)vpin);
}
} else
reportError(status);
} else if (configType == CONFIGURE_ANALOGINPUT) {
// TODO: Consider moving code from _configureAnalogIn() to here and remove _configureAnalogIn
// from IODevice class definition. Not urgent, but each virtual function defined
// means increasing the RAM requirement of every HAL device driver, whether it's relevant
// to the driver or not.
return false;
}
return false;
}
// Analogue input pin configuration, used to enable on EX-IOExpander device
// Analogue input pin configuration, used to enable an EX-IOExpander device.
// Use I2C blocking calls and allocate buffers from stack to save RAM.
int _configureAnalogIn(VPIN vpin) override {
int pin = vpin - _firstVpin;
_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;
uint8_t commandBuffer[] = {EXIOENAN, (uint8_t)pin};
uint8_t responseBuffer[1];
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
commandBuffer, sizeof(commandBuffer));
if (status == I2C_STATUS_OK) {
if (responseBuffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("EX-IOExpander: Vpin %u cannot be used as an analogue input pin"), (int)vpin);
}
} else
reportError(status);
return false;
}
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
void _loop(unsigned long currentMicros) override {
(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);
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
// Request block is used for analogue and digital reads from the IOExpander, which are performed
// on a cyclic basis. Writes are performed synchronously as and when requested.
if (_readState != RDS_IDLE) {
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
uint8_t status = _i2crb.status;
if (status == I2C_STATUS_OK) { // If device request ok, read input data
// First check if we need to process received data
if (_readState == RDS_ANALOGUE) {
// Read of analogue values was in progress, so process received values
// Here we need to copy the values from input buffer to the analogue value array. We need to
// do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed
// while the value is being read).
memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states
} else if (_readState == RDS_DIGITAL) {
// Read of digital states was in progress, so process received values
// The received digital states are placed directly into the digital buffer on receipt,
// so don't need any further processing at this point (unless we want to check for
// changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h).
}
} else
reportError(status, false); // report eror but don't go offline.
_readState = RDS_IDLE;
}
// If we're not doing anything now, check to see if a new input transfer is due.
if (_readState == RDS_IDLE) {
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
// be allocated from heap (object state).
_readCommandBuffer[0] = EXIORDD;
I2CManager.read(_I2CAddress, _digitalInputStates, (_numDigitalPins+7)/8, _readCommandBuffer, 1, &_i2crb);
// non-blocking read
_lastDigitalRead = currentMicros;
_readState = RDS_DIGITAL;
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
// Issue new read for analogue input states
_readCommandBuffer[0] = EXIORDAN;
I2CManager.read(_I2CAddress, _analogueInputBuffer,
_numAnaloguePins * 2, _readCommandBuffer, 1, &_i2crb);
_lastAnalogueRead = currentMicros;
_readState = RDS_ANALOGUE;
}
}
}
// Obtain the correct analogue input value, with reference to the analogue
// pin map.
// Obtain the correct analogue input value
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
uint8_t _pinLSBByte;
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
if (_analoguePinMap[aPin] == pin) {
_pinLSBByte = aPin * 2;
uint8_t _pinLSBByte = aPin * 2;
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
}
}
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
return -1; // pin not found in table
}
// Obtain the correct digital input value
@@ -185,63 +283,102 @@ private:
return value;
}
// Write digital value. We could have an output buffer of states, that is periodically
// written to the device if there are any changes; this would reduce the I2C overhead
// if lots of output requests are being made. We could also cache the last value
// sent so that we don't write the same value over and over to the output.
// However, for the time being, we just write the current value (blocking I2C) to the
// IOExpander node. As it is a blocking request, we can use buffers allocated from
// the stack to save RAM allocation.
void _write(VPIN vpin, int value) override {
uint8_t digitalOutBuffer[3];
uint8_t responseBuffer[1];
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIOWRD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = value;
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);
digitalOutBuffer[0] = EXIOWRD;
digitalOutBuffer[1] = pin;
digitalOutBuffer[2] = value;
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, digitalOutBuffer, 3);
if (status != I2C_STATUS_OK) {
reportError(status);
} else {
if (responseBuffer[0] != EXIORDY) {
DIAG(F("Vpin %u cannot be used as a digital output pin"), (int)vpin);
}
}
}
// Write analogue (integer) value. Write the parameters (blocking I2C) to the
// IOExpander node. As it is a blocking request, we can use buffers allocated from
// the stack to reduce RAM allocation.
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
uint8_t servoBuffer[7];
uint8_t responseBuffer[1];
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
DIAG(F("Servo: WriteAnalogue Vpin:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
_servoBuffer[0] = EXIOWRAN;
_servoBuffer[1] = pin;
_servoBuffer[2] = value & 0xFF;
_servoBuffer[3] = value >> 8;
_servoBuffer[4] = profile;
_servoBuffer[5] = duration & 0xFF;
_servoBuffer[6] = duration >> 8;
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);
servoBuffer[0] = EXIOWRAN;
servoBuffer[1] = pin;
servoBuffer[2] = value & 0xFF;
servoBuffer[3] = value >> 8;
servoBuffer[4] = profile;
servoBuffer[5] = duration & 0xFF;
servoBuffer[6] = duration >> 8;
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, servoBuffer, 7);
if (status != I2C_STATUS_OK) {
DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED;
} else {
if (responseBuffer[0] != EXIORDY) {
DIAG(F("Vpin %u cannot be used as a servo/PWM pin"), (int)vpin);
}
}
}
// Display device information and status.
void _display() override {
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"),
_i2cAddress.toString(), _majorVer, _minorVer, _patchVer,
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %u-%u %S"),
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
(int)_firstVpin, (int)_firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
I2CAddress _i2cAddress;
// Helper function for error handling
void reportError(uint8_t status, bool fail=true) {
DIAG(F("EX-IOExpander I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
status, I2CManager.getErrorMessage(status));
if (fail)
_deviceState = DEVSTATE_FAILED;
}
uint8_t _numDigitalPins = 0;
uint8_t _numAnaloguePins = 0;
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* _digitalInputStates;
uint8_t* _analogueInputStates;
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers
uint8_t _readCommandBuffer[1];
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed)
uint8_t* _analoguePinMap;
I2CRB _i2crb;
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states
uint8_t _readState = RDS_IDLE;
unsigned long _lastDigitalRead = 0;
unsigned long _lastAnalogueRead = 0;
const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms
const unsigned long _analogueRefresh = 50000UL; // Delay refreshing analogue inputs for 50ms
// EX-IOExpander protocol flags
enum {

View File

@@ -20,20 +20,21 @@
/*
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
*
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic.
*
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
* This device driver sends a step position to EX-Turntable to indicate the step position to move to using either of these commands:
* <D TT vpin steps activity> in the serial console
* MOVETT(vpin, steps, activity) in EX-RAIL
* Refer to the documentation for further information including the valid activities.
*/
#ifndef IO_EXTurntable_h
#define IO_EXTurntable_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "Turntables.h"
#include "CommandDistributor.h"
#ifndef IO_NO_HAL
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
new EXTurntable(firstVpin, nPins, I2CAddress);
@@ -44,18 +45,21 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
_stepperStatus = 0;
_previousStatus = 0;
addDevice(this);
}
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
if (I2CManager.exists(_I2CAddress)) {
DIAG(F("EX-Turntable device found, I2C:%s"), _I2CAddress.toString());
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
@@ -67,15 +71,19 @@ void EXTurntable::_loop(unsigned long currentMicros) {
uint8_t readBuffer[1];
I2CManager.read(_I2CAddress, readBuffer, 1);
_stepperStatus = readBuffer[0];
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes
if ( _currentActivity < 4) {
_broadcastStatus(_firstVpin, _stepperStatus, _currentActivity);
}
_previousStatus = _stepperStatus;
}
delayUntil(currentMicros + 100000); // Wait 100ms before checking again
}
// Read returns status as obtained in our loop.
// Return false if our status value is invalid.
int EXTurntable::_read(VPIN vpin) {
if (_deviceState == DEVSTATE_FAILED) return 0;
// DIAG(F("_read status: %d"), _stepperStatus);
if (_stepperStatus > 1) {
return false;
} else {
@@ -83,6 +91,17 @@ int EXTurntable::_read(VPIN vpin) {
}
}
// If a status change has occurred for a turntable object, broadcast it
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) {
Turntable *tto = Turntable::getByVpin(vpin);
if (tto) {
if (activity < 4) {
tto->setMoving(status);
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
}
}
}
// writeAnalogue to send the steps and activity to Turntable-EX.
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
// value contains the steps, bit shifted to MSB + LSB.
@@ -100,21 +119,25 @@ int EXTurntable::_read(VPIN vpin) {
// Acc_Off = 9 // Turn accessory pin off
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
if (_deviceState == DEVSTATE_FAILED) return;
if (value < 0) return;
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
#endif
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
_previousStatus = _stepperStatus;
_currentActivity = activity;
_broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
}
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

View File

@@ -84,7 +84,7 @@ protected:
void _write(VPIN vpin, int value) {
int pin = vpin -_firstVpin;
#ifdef DIAG_IO
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value);
#endif
// Send a command string over the serial line
_serial->print('#');
@@ -153,10 +153,10 @@ protected:
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
// Here we display the current values held for the pins.
void _display() {
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin,
(int)_firstVpin+_nPins-1);
for (int i=0; i<_nPins; i++)
DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]);
DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]);
}

View File

@@ -1,141 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This device driver monitors the state of turnout objects and writes updates,
* on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte)
* EEPROM device connected via I2C.
*
* When the device is restarted, it repositions the turnouts in accordance
* with the last saved position.
*
* To create a device instance,
* IO_ExternalEEPROM::create(0, 0, i2cAddress);
*
*
*/
#ifndef IO_EXTERNALEEPROM_H
#define IO_EXTERNALEEPROM_H
#include "IODevice.h"
#include "I2CManager.h"
#include "Turnouts.h"
class ExternalEEPROM : public IODevice {
private:
// Here we define the device-specific variables.
int _sizeInKBytes = 128;
Turnout *_turnout = 0;
int _lastTurnoutHash = 0;
I2CRB _rb;
uint8_t _buffer[32]; // 32 is max for Wire write
public:
// Static function to handle "IO_ExampleSerial::create(...)" calls.
static void create(I2CAddress i2cAddress, int sizeInKBytes) {
if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes);
}
protected:
// Constructor.
ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) {
_I2CAddress = i2cAddress;
_sizeInKBytes = sizeInKBytes;
// Set up I2C structures.
_rb.setWriteParams(_I2CAddress, _buffer, 32);
addDevice(this);
}
// Device-specific initialisation
void _begin() override {
I2CManager.begin();
I2CManager.setClock(1000000); // Max supported speed
if (I2CManager.exists(_I2CAddress)) {
// Initialise or read contents of EEPROM
// and set turnout states accordingly.
// Read 32 bytes from address 0x0000.
I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0);
// Dump data
DIAG(F("EEPROM First 32 bytes:"));
for (int i=0; i<32; i+=8)
DIAG(F("%d: %x %x %x %x %x %x %x %x"),
i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3],
_buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]);
#if defined(DIAG_IO)
_display();
#endif
} else {
DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Loop function to do background scanning of the turnouts
void _loop(unsigned long currentMicros) {
(void)currentMicros; // Suppress compiler warnings
if (_rb.isBusy()) return; // Can't do anything until previous request has completed.
if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) {
// Device not responding, probably still writing data, so requeue request
I2CManager.queueRequest(&_rb);
return;
}
if (_lastTurnoutHash != Turnout::turnoutlistHash) {
_lastTurnoutHash = Turnout::turnoutlistHash;
// Turnout list has changed, so pointer held from last run may be invalid
_turnout = 0; // Start at the beginning of the list again.
//#if defined(DIAG_IO)
DIAG(F("Turnout Hash Changed!"));
//#endif
}
// Locate next turnout, or first one if there is no current one.
if (_turnout)
_turnout = _turnout->next();
else
_turnout = Turnout::first();
// Retrieve turnout state
int turnoutID = _turnout->getId();
int turnoutState = _turnout->isThrown();
(void)turnoutID; // Suppress compiler warning
(void)turnoutState; // Suppress compiler warning
// TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed.
// TODO: If it has, then initiate a write of the updated state to EEPROM
delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256
}
// Display information about the device.
void _display() {
DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(),
_deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
};
#endif // IO_EXTERNALEEPROM_H

View File

@@ -196,7 +196,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
template <class T>
void GPIOBase<T>::_display() {
DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(),
DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(),
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

View File

@@ -76,21 +76,23 @@ private:
uint8_t *_lastRowGeneration = NULL;
uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0;
bool _startAgain = false;
DisplayInterface *_nextDisplay = NULL;
public:
// Static function to handle "HALDisplay::create(...)" calls.
static void create(I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height);
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height);
}
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height);
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(displayNo, i2cAddress, width, height);
}
protected:
// Constructor
HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayDriver = new T(i2cAddress, width, height);
if (!_displayDriver) return; // Check for memory allocation failure
_I2CAddress = i2cAddress;
_width = width;
_height = height;
@@ -101,8 +103,12 @@ protected:
// Allocate arrays
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
if (!_buffer) return; // Check for memory allocation failure
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
if (!_rowGeneration) return; // Check for memory allocation failure
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
if (!_lastRowGeneration) return; // Check for memory allocation failure
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
@@ -116,7 +122,7 @@ protected:
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);
// Is this the main display?
// Is this the system display (0)?
if (displayNo == 0) {
// Set first two lines on screen
this->setRow(displayNo, 0);
@@ -135,13 +141,15 @@ protected:
// to the screen until that row has been refreshed.
// First check if the OLED driver is still busy from a previous
// call. If so, don't to anything until the next entry.
// call. If so, don't do anything until the next entry.
if (!_displayDriver->isBusy()) {
// Check if we've just done the end of a row
if (_charPosToScreen >= _numCols) {
// Move to next line
if (++_rowNoToScreen >= _numRows)
if (++_rowNoToScreen >= _numRows || _startAgain) {
_rowNoToScreen = 0; // Wrap to first row
_startAgain = false;
}
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
// Row content has changed, so start outputting it
@@ -222,10 +230,14 @@ public:
for (_colNo = 0; _colNo < _numCols; _colNo++)
_buffer[_rowNo*_numCols+_colNo] = ' ';
_colNo = 0;
// Mark that the buffer has been touched. It will be
// Mark that the buffer has been touched. It will start being
// sent to the screen on the next loop entry, by which time
// the line should have been written to the buffer.
_rowGeneration[_rowNo]++;
// Indicate that the output loop is to start updating the screen again from
// row 0. Otherwise, on a full screen rewrite the bottom part may be drawn
// before the top part!
_startAgain = true;
}
// Write one character to the screen referenced in the last setRow() call.

View File

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

111
IO_PCA9555.h Normal file
View File

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

View File

@@ -45,8 +45,8 @@ void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t
bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false;
#if DIAG_IO >= 3
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
#ifdef DIAG_IO
DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]);
#endif
@@ -117,8 +117,8 @@ void PCA9685::_begin() {
// Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) {
#if DIAG_IO >= 3
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
#ifdef DIAG_IO
DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
#endif
int pin = vpin - _firstVpin;
if (value) value = 1;
@@ -144,8 +144,8 @@ void PCA9685::_write(VPIN vpin, int value) {
// 4 (Bounce) Servo 'bounces' at extremes.
//
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#if DIAG_IO >= 3
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
#ifdef DIAG_IO
DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
@@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
// Display details of this device.
void PCA9685::_display() {
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

View File

@@ -120,8 +120,8 @@ private:
//
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
(void)param1; (void)param2; // suppress compiler warning
#if DIAG_IO >= 3
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
#ifdef DIAG_IO
DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"),
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
@@ -134,14 +134,14 @@ private:
// Display details of this device.
void _display() override {
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
// writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void writeDevice(uint8_t pin, int value) {
#if DIAG_IO >= 3
#ifdef DIAG_IO
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif
// Wait for previous request to complete

View File

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

View File

@@ -98,7 +98,7 @@ private:
if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false;
#ifdef DIAG_IO
DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]);
#endif
@@ -140,12 +140,12 @@ private:
// Get reference to slave device.
_slaveDevice = findDevice(_firstSlavePin);
if (!_slaveDevice) {
DIAG(F("Servo: Slave device not found on pins %d-%d"),
DIAG(F("Servo: Slave device not found on Vpins %u-%u"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
DIAG(F("Servo: Slave device does not cover all pins %d-%d"),
DIAG(F("Servo: Slave device does not cover all Vpins %u-%u"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
@@ -165,7 +165,7 @@ private:
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value);
DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value);
#endif
int pin = vpin - _firstVpin;
if (value) value = 1;
@@ -193,7 +193,7 @@ private:
//
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
@@ -288,7 +288,7 @@ private:
// Display details of this device.
void _display() override {
DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"),
DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1,
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));

View File

@@ -1,256 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text").
* If the line specified is off the screen then the text in the bottom line will be
* overwritten. There is however a special case that if line 255 is specified,
* the existing text will scroll up and the new line added to the bottom
* line of the screen.
*
* To install, use the following command in myHal.cpp:
* TFTDisplay::create(address, width, height);
*
* where address is the I2C address (0x3c or 0x3d),
* width is the width in pixels of the display, and
* height is the height in pixels of the display.
*
*/
#ifndef IO_TFTDISPLAY_H
#define IO_TFTDDISPLAY_H
#include "IODevice.h"
#include "DisplayInterface.h"
#include "version.h"
template <class T>
class TFTDisplay : public IODevice, public DisplayInterface {
private:
uint8_t _displayNo = 0;
// Here we define the device-specific variables.
uint8_t _height; // in pixels
uint8_t _width; // in pixels
T *_displayDriver;
uint8_t _rowNo = 0; // Row number being written by caller
uint8_t _colNo = 0; // Position in line being written by caller
uint8_t _numRows;
uint8_t _numCols;
char *_buffer = NULL;
uint8_t *_rowGeneration = NULL;
uint8_t *_lastRowGeneration = NULL;
uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0;
DisplayInterface *_nextDisplay = NULL;
uint8_t _selectedDisplayNo = 0;
public:
// Static function to handle "TFTDisplay::create(...)" calls.
static void create(I2CAddress i2cAddress, int width = 128, int height=64) {
/* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height);
}
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) {
/* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height);
}
protected:
// Constructor
TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayDriver = new T(i2cAddress, width, height);
_displayNo = displayNo;
_I2CAddress = i2cAddress;
_width = width;
_height = height;
_numCols = (_width+5) / 6; // character block 6 x 8, round up
_numRows = _height / 8; // Round down
_charPosToScreen = _numCols;
// Allocate arrays
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Is this the main display?
if (_displayNo == 0) {
// Set first two lines on screen
this->setRow(0, 0);
print(F("DCC-EX v"));
print(F(VERSION));
setRow(0, 1);
print(F("Lic GPLv3"));
}
// Store pointer to this object into CS display hook, so that we
// will intercept any subsequent calls to displayHandler methods.
// Make a note of the existing display reference, to that we can
// pass on anything we're not interested in.
_nextDisplay = DisplayInterface::displayHandler;
DisplayInterface::displayHandler = this;
addDevice(this);
}
void screenUpdate() {
// Loop through the buffer and if a row has changed
// (rowGeneration[row] is changed) then start writing the
// characters from the buffer, one character per entry,
// to the screen until that row has been refreshed.
// First check if the OLED driver is still busy from a previous
// call. If so, don't to anything until the next entry.
if (!_displayDriver->isBusy()) {
// Check if we've just done the end of a row or just started
if (_charPosToScreen >= _numCols) {
// Move to next line
if (++_rowNoToScreen >= _numRows)
_rowNoToScreen = 0; // Wrap to first row
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
// Row content has changed, so start outputting it
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
_displayDriver->setRowNative(_rowNoToScreen);
_charPosToScreen = 0; // Prepare to output first character on next entry
} else {
// Row not changed, don't bother writing it.
}
} else {
// output character at current position
_displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
}
}
return;
}
/////////////////////////////////////////////////
// IODevice Class Member Overrides
/////////////////////////////////////////////////
// Device-specific initialisation
void _begin() override {
// Initialise device
if (_displayDriver->begin()) {
DIAG(F("TFTDisplay installed on address %s as screen %d"),
_I2CAddress.toString(), _displayNo);
// Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++)
_rowGeneration[row]++;
// Start with top line (looks better)
_rowNoToScreen = _numRows;
_charPosToScreen = _numCols;
}
}
void _loop(unsigned long) override {
screenUpdate();
}
/////////////////////////////////////////////////
// DisplayInterface functions
//
/////////////////////////////////////////////////
public:
void loop() override {
screenUpdate();
if (_nextDisplay)
_nextDisplay->loop(); // continue to next display
return;
}
// Position on nominated line number (0 to number of lines -1)
// Clear the line in the buffer ready for updating
// The displayNo referenced here is remembered and any following
// calls to write() will be directed to that display.
void setRow(uint8_t displayNo, byte line) override {
_selectedDisplayNo = displayNo;
if (displayNo == _displayNo) {
if (line == 255) {
// LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") -
// scroll the contents of the buffer and put the new line
// at the bottom of the screen
for (int row=1; row<_numRows; row++) {
strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols);
_rowGeneration[row-1]++;
}
line = _numRows-1;
} else if (line >= _numRows)
line = _numRows - 1; // Overwrite bottom line.
_rowNo = line;
// Fill line with blanks
for (_colNo = 0; _colNo < _numCols; _colNo++)
_buffer[_rowNo*_numCols+_colNo] = ' ';
_colNo = 0;
// Mark that the buffer has been touched. It will be
// sent to the screen on the next loop entry, by which time
// the line should have been written to the buffer.
_rowGeneration[_rowNo]++;
}
if (_nextDisplay)
_nextDisplay->setRow(displayNo, line); // Pass to next display
}
// Write one character to the screen referenced in the last setRow() call.
size_t write(uint8_t c) override {
if (_selectedDisplayNo == _displayNo) {
// Write character to buffer (if there's space)
if (_colNo < _numCols) {
_buffer[_rowNo*_numCols+_colNo++] = c;
}
}
if (_nextDisplay)
_nextDisplay->write(c);
return 1;
}
// Write blanks to all of the screen (blocks until complete)
void clear (uint8_t displayNo) override {
if (displayNo == _displayNo) {
// Clear buffer
for (_rowNo = 0; _rowNo < _numRows; _rowNo++) {
setRow(displayNo, _rowNo);
}
_rowNo = 0;
}
if (_nextDisplay)
_nextDisplay->clear(displayNo); // Pass to next display
}
// Display information about the device.
void _display() {
DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString());
}
};
#endif // IO_TFTDDISPLAY_H

134
IO_TouchKeypad.h Normal file
View File

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

View File

@@ -319,7 +319,7 @@ protected:
}
void _display() override {
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"),
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

136
IO_Wire.h
View File

@@ -1,136 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* The purpose of this module is to provide an interface to the DCC
* I2CManager that is compatible with code written for the Arduino
* 'Wire' interface.
*
* To use it, just replace
* #include "Wire.h" or #include <Wire.h>
* with
* #include "IO_Wire.h"
*
* Note that the CS only supports I2C master mode, so the calls related to
* slave mode are not implemented here.
*
*/
#ifndef IO_WIRE
#define IO_WIRE
#include "IODevice.h"
#ifndef I2C_USE_WIRE
class IO_Wire : public IODevice, public Stream {
public:
IO_Wire() {
addDevice(this);
};
void begin() {
I2CManager.begin();
}
void setClock(uint32_t speed) {
I2CManager.setClock(speed);
}
void beginTransmission(uint8_t address) {
i2cAddress = address;
outputLength = 0;
}
size_t write(byte value) override {
if (outputLength < sizeof(outputBuffer)) {
outputBuffer[outputLength++] = value;
return 1;
} else
return 0;
}
size_t write(const uint8_t *buffer, size_t size) override {
for (size_t i=0; i<size; i++) {
if (!write(buffer[i])) return i;
}
return size;
}
uint8_t endTransmission(bool) {
// As this software doesn't run in a multi-master environment, there
// is no advantage to holding the bus between transactions. Therefore,
// for simplicity, a stop condition is always sent.
return I2CManager.write(i2cAddress, outputBuffer, outputLength);
}
uint8_t requestFrom(uint8_t address, uint8_t readSize, uint8_t sendStop) {
(void)sendStop; // suppress compiler warning
uint8_t status = I2CManager.read(address, inputBuffer, readSize);
inputPos = 0;
inputLength = readSize;
return status;
}
uint8_t requestFrom(uint8_t address, uint8_t quantity)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}
uint8_t requestFrom(int address, int quantity)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}
uint8_t requestFrom(int address, int quantity, int sendStop)
{
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
}
int read() override {
if (inputPos < inputLength)
return inputBuffer[inputPos++];
else
return -1;
}
int available() override {
return (inputPos < inputLength);
}
int peek() override {
if (inputPos < inputLength)
return inputBuffer[inputPos];
else
return -1;
}
uint8_t endTransmission() {
return endTransmission(true);
}
static IO_Wire Wire();
protected:
void _begin() { }
void _display() {
DIAG(F("I2CManager Wire Interface"));
}
private:
uint8_t outputBuffer[32];
uint8_t outputLength = 0;
uint8_t inputBuffer[32];
uint8_t inputLength = 0;
uint8_t inputPos = 0;
uint8_t i2cAddress;
};
static IO_Wire Wire;
#else
#include <Wire.h>
#endif
#endif

View File

@@ -55,6 +55,7 @@ public:
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
if (!_pinMap) _loopOutput();
}
// loop called by HAL supervisor
@@ -121,7 +122,7 @@ void _loopOutput() {
}
void _display() override {
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"),
_pinMap?F("IN"):F("OUT"),
(int)_firstVpin,
(int)_firstVpin+_nPins-1, _nShiftBytes*8);

View File

@@ -1,9 +1,10 @@
/*
* © 2022 Paul M Antoine
* © 2022-2023 Paul M Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2023 Harald Barth
* © 2020-2021 Chris Harlow
* © 2023 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -26,20 +27,23 @@
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include "EXRAIL2.h"
#if defined(ARDUINO_ARCH_ESP32)
#include "ESP32-fixes.h"
#endif
bool MotorDriver::commonFaultPin=false;
unsigned long MotorDriver::globalOverloadStart = 0;
volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC;
#if defined(ARDUINO_ARCH_STM32)
volatile portreg_t shadowPORTD;
volatile portreg_t shadowPORTE;
volatile portreg_t shadowPORTF;
#endif
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
const FSH * warnString = F("** WARNING **");
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
powerPin=power_pin;
invertPower=power_pin < 0;
if (invertPower) {
powerPin = 0-power_pin;
@@ -69,42 +73,110 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTC;
}
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
DIAG(F("Found PORTD pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTD;
}
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
DIAG(F("Found PORTE pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTE;
}
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
DIAG(F("Found PORTF pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTF;
}
signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) {
dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT);
fastSignalPin2.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTA;
}
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
DIAG(F("Found PORTB pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTB;
}
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
DIAG(F("Found PORTC pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTC;
}
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
DIAG(F("Found PORTD pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTD;
}
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
DIAG(F("Found PORTE pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTE;
}
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
DIAG(F("Found PORTF pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTF;
}
}
else dualSignal=false;
brakePin=brake_pin;
if (brake_pin!=UNUSED_PIN){
invertBrake=brake_pin < 0;
brakePin=invertBrake ? 0-brake_pin : brake_pin;
if (invertBrake)
brake_pin = 0-brake_pin;
if (brake_pin > MAX_PIN)
DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN);
brakePin=(byte)brake_pin;
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
// if brake is used for railcom cutout we need to do PORTX register trick here as well
pinMode(brakePin, OUTPUT);
setBrake(true); // start with brake on in case we hace DC stuff going on
} else {
brakePin=UNUSED_PIN;
}
else brakePin=UNUSED_PIN;
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
senseOffset = ADCee::init(currentPin);
int ret = ADCee::init(currentPin);
if (ret < -1010) { // XXX give value a name later
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
currentPin = UNUSED_PIN;
}
}
senseOffset=0; // value can not be obtained until waveform is activated
faultPin=fault_pin;
if (faultPin != UNUSED_PIN) {
if (fault_pin != UNUSED_PIN) {
invertFault=fault_pin < 0;
if (invertFault)
fault_pin = 0-fault_pin;
if (fault_pin > MAX_PIN)
DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN);
faultPin=(byte)fault_pin;
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
pinMode(faultPin, INPUT);
} else {
faultPin=UNUSED_PIN;
}
// This conversion performed at compile time so the remainder of the code never needs
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=mA2raw(trip_milliamps);
#ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
@@ -119,21 +191,16 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
}
if (currentPin==UNUSED_PIN)
DIAG(F("** WARNING ** No current or short detection"));
DIAG(F("%S No current or short detection"), warnString);
else {
DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"),
currentPin-A0, senseOffset,rawCurrentTripValue);
DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
// self testing diagnostic for the non-float converters... may be removed when happy
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
}
// prepare values for current detection
sampleDelay = 0;
lastSampleTaken = millis();
progTripValue = mA2raw(TRIP_CURRENT_PROG);
}
bool MotorDriver::isPWMCapable() {
@@ -142,8 +209,19 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) {
bool on=mode==POWERMODE::ON;
if (powerMode == mode) return;
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
lastPowerChange[(int)mode] = micros();
if (mode == POWERMODE::OVERLOAD)
globalOverloadStart = lastPowerChange[(int)mode];
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
senseOffset = ADCee::read(currentPin);
DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset);
}
IODevice::write(powerPin,invertPower ? LOW : HIGH);
if (isProgTrack)
DCCWaveform::progTrack.clearResets();
@@ -176,8 +254,8 @@ bool MotorDriver::canMeasureCurrent() {
return currentPin!=UNUSED_PIN;
}
/*
* Return the current reading as pin reading 0 to 1023. If the fault
* pin is activated return a negative current to show active fault pin.
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
* If the fault pin is activated return a negative current to show active fault pin.
* As there is no -0, cheat a little and return -1 in that case.
*
* senseOffset handles the case where a shield returns values above or below
@@ -189,12 +267,17 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR;
if (currentPin==UNUSED_PIN) return 0;
int current;
current = ADCee::read(currentPin, fromISR)-senseOffset;
current = ADCee::read(currentPin, fromISR);
// here one can diag raw value
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
current = current-senseOffset; // adjust with offset
if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
// current >= 0 here, we use negative current as fault pin flag
if ((faultPin != UNUSED_PIN) && powerPin) {
if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
return (current == 0 ? -1 : -current);
}
return current;
}
#ifdef ANALOG_READ_INTERRUPT
@@ -231,38 +314,68 @@ void MotorDriver::startCurrentFromHW() {
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
#ifdef VARIABLE_TONES
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
249, 392, 440, 494,
349, 392, 440, 494,
523, 587, 659, 698,
494, 440, 392, 249,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
case 9:
case 10:
// Timer2 (is differnet)
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
#endif
default:
break;
}
// spedcoode is a dcc speed & direction
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
byte brake;
#if defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
{
int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
if (tSpeed <= 1) brake = 255;
@@ -270,8 +383,8 @@ void MotorDriver::setDCSignal(byte speedcode) {
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32)
DCCEXanalogWrite(brakePin,brake);
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
#endif
@@ -294,13 +407,91 @@ void MotorDriver::setDCSignal(byte speedcode) {
setSignal(tDir);
HAVE_PORTC(PORTC=shadowPORTC);
interrupts();
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
noInterrupts();
HAVE_PORTD(shadowPORTD=PORTD);
setSignal(tDir);
HAVE_PORTD(PORTD=shadowPORTD);
interrupts();
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
noInterrupts();
HAVE_PORTE(shadowPORTE=PORTE);
setSignal(tDir);
HAVE_PORTE(PORTE=shadowPORTE);
interrupts();
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
noInterrupts();
HAVE_PORTF(shadowPORTF=PORTF);
setSignal(tDir);
HAVE_PORTF(PORTF=shadowPORTF);
interrupts();
} else {
noInterrupts();
setSignal(tDir);
interrupts();
}
}
void MotorDriver::throttleInrush(bool on) {
if (brakePin == UNUSED_PIN)
return;
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
return;
byte duty = on ? 208 : 0;
if (invertBrake)
duty = 255-duty;
#if defined(ARDUINO_ARCH_ESP32)
if(on) {
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
} else {
ledcDetachPin(brakePin);
}
#elif defined(ARDUINO_ARCH_STM32)
if(on) {
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
DCCTimer::DCCEXanalogWrite(brakePin,duty);
} else {
pinMode(brakePin, OUTPUT);
}
#else
if(on){
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is different)
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
#endif
default:
break;
}
}
analogWrite(brakePin,duty);
#endif
}
unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
@@ -329,64 +520,178 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
}
///////////////////////////////////////////////////////////////////////////////////////////
// checkPowerOverload(useProgLimit, trackno)
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
// byte trackno: trackmanager knows it's number (could be skipped?)
//
// Short ciruit handling strategy:
//
// There are the following power states: ON ALERT OVERLOAD OFF
// OFF state is only changed to/from manually. Power is on
// during ON and ALERT. Power is off during OVERLOAD and OFF.
// The overload mechanism changes between the other states like
//
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
// or
// ON -1-> ALERT -4-> ON
//
// Times are in class MotorDriver (MotorDriver.h).
//
// 1. ON to ALERT:
// Transition on fault pin condition or current overload
//
// 2. ALERT to OVERLOAD:
// Transition happens if different timeouts have elapsed.
// If only the fault pin is active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
// If only overcurrent is detected, timeout is
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
// If fault pin and overcurrent are active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
// Transition to OVERLOAD turns off power to the affected
// output (unless fault pins are shared)
// If the transition conditions are not fullfilled,
// transition according to 4 is tested.
//
// 3. OVERLOAD to ALERT
// Transiton happens when timeout has elapsed, timeout
// is named power_sample_overload_wait. It is started
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
// to OVERLOAD and then increased by a factor of 2
// at further entries to the OVERLOAD condition. This
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
// power_sample_overload_wait is reset by a poweroff or
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
// After timeout power is turned on again and state
// goes back to ALERT.
//
// 4. ALERT to ON
// Transition happens by watching the current and fault pin
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
// values have been good during that time, transition is
// made back to ON. Note that even if state is back to ON,
// the power_sample_overload_wait time is first reset
// later (see above).
//
// The time keeping is handled by timestamps lastPowerChange[]
// which are set by each power change and by lastBadSample which
// keeps track if conditions during ALERT have been good enough
// to go back to ON. The time differences are calculated by
// microsSinceLastPowerChange().
//
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
// Trackname for diag messages later
switch (powerMode) {
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (commonFaultPin) {
if (lastCurrent < tripValue) {
setPower(POWERMODE::ON); // maybe other track
}
// Write this after the fact as we want to turn on as fast as possible
// because we don't know which output actually triggered the fault pin
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
} else {
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
}
}
if (lastCurrent < tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
else
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
case POWERMODE::OFF: {
lastPowerMode = POWERMODE::OFF;
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
break;
}
case POWERMODE::ON: {
lastPowerMode = POWERMODE::ON;
bool cF = checkFault();
bool cC = checkCurrent(useProgLimit);
if(cF || cC ) {
if (cC) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
cF ? "FAULT" : "",
mA);
} else {
setPower(POWERMODE::OVERLOAD);
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
}
setPower(POWERMODE::ALERT);
if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
DIAG(F("TRACK %c INVERT"), trackno + 'A');
invertOutput();
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPower(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
}
// all well
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
}
case POWERMODE::ALERT: {
// set local flags that handle how much is output to diag (do not output duplicates)
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
bool powerModeChange = (powerMode != lastPowerMode);
unsigned long now = micros();
if (powerModeChange)
lastBadSample = now;
lastPowerMode = POWERMODE::ALERT;
// check how long we have been in this state
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
if(checkFault()) {
throttleInrush(true);
lastBadSample = now;
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
if ( mslpc < timeout) {
if (powerModeChange)
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
break;
}
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
if (checkCurrent(useProgLimit)) {
lastBadSample = now;
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
if (powerModeChange) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
}
break;
}
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
// all well
unsigned long goodtime = micros() - lastBadSample;
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
if (true || notFromOverload) { // we did a RESTORE message XXX
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
}
throttleInrush(false);
setPower(POWERMODE::ON);
}
break;
}
case POWERMODE::OVERLOAD: {
lastPowerMode = POWERMODE::OVERLOAD;
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
if (mslpc > power_sample_overload_wait) {
// adjust next wait time
power_sample_overload_wait *= 2;
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
#ifdef EXRAIL_ACTIVE
DIAG(F("Calling EXRAIL"));
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
#endif
// power on test
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);
}
break;
}
default:
break;
}
}

View File

@@ -1,9 +1,9 @@
/*
* © 2022 Paul M Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020 Chris Harlow
* © 2022 Harald Barth
* © 2022,2023 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -27,6 +27,17 @@
#include "IODevice.h"
#include "DCCTimer.h"
// use powers of two so we can do logical and/or on the track modes in if clauses.
// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV)
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32,
TRACK_MODE_ALL = 62, // only to operate all tracks
TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128};
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
@@ -56,6 +67,16 @@
#define HAVE_PORTB(X) X
#define PORTC GPIOC->ODR
#define HAVE_PORTC(X) X
#define PORTD GPIOD->ODR
#define HAVE_PORTD(X) X
#if defined(GPIOE)
#define PORTE GPIOE->ODR
#define HAVE_PORTE(X) X
#endif
#if defined(GPIOF)
#define PORTF GPIOF->ODR
#define HAVE_PORTF(X) X
#endif
#endif
// if macros not defined as pass-through we define
@@ -70,12 +91,22 @@
#ifndef HAVE_PORTC
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTD
#define HAVE_PORTD(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTE
#define HAVE_PORTE(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTF
#define HAVE_PORTF(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
// Virtualised Motor shield 1-track hardware Interface
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
#define UNUSED_PIN 127 // inside int8_t
#define UNUSED_PIN 255 // inside uint8_t
#endif
#define MAX_PIN 254
class pinpair {
public:
@@ -105,14 +136,17 @@ struct FASTPIN {
extern volatile portreg_t shadowPORTA;
extern volatile portreg_t shadowPORTB;
extern volatile portreg_t shadowPORTC;
extern volatile portreg_t shadowPORTD;
extern volatile portreg_t shadowPORTE;
extern volatile portreg_t shadowPORTF;
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
class MotorDriver {
public:
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
void setPower( POWERMODE mode);
POWERMODE getPower() { return powerMode;}
// as the port registers can be shadowed to get syncronized DCC signals
@@ -121,7 +155,9 @@ class MotorDriver {
// otherwise the call from interrupt context can undo whatever we do
// from outside interrupt
void setBrake( bool on, bool interruptContext=false);
__attribute__((always_inline)) inline void setSignal( bool high) {
__attribute__((always_inline)) inline void setSignal( bool high) {
if (invertPhase)
high = !high;
if (trackPWM) {
DCCTimer::setPWM(signalPin,high);
}
@@ -141,9 +177,16 @@ class MotorDriver {
pinMode(signalPin, OUTPUT);
else
pinMode(signalPin, INPUT);
if (signalPin2 != UNUSED_PIN) {
if (on)
pinMode(signalPin2, OUTPUT);
else
pinMode(signalPin2, INPUT);
}
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
void throttleInrush(bool on);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
@@ -157,16 +200,16 @@ class MotorDriver {
unsigned int raw2mA( int raw);
unsigned int mA2raw( unsigned int mA);
inline bool brakeCanPWM() {
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
return true;
#else
#ifdef digitalPinToTimer
#if defined(ARDUINO_ARCH_ESP32)
return (brakePin != UNUSED_PIN); // This was just (true) but we probably do need to check for UNUSED_PIN!
#elif defined(__arm__)
// On ARM we can use digitalPinHasPWM
return ((brakePin!=UNUSED_PIN) && (digitalPinHasPWM(brakePin)));
#elif defined(digitalPinToTimer)
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
#else
return (brakePin<14 && brakePin >1);
#endif //digitalPinToTimer
#endif //ESP32/ARM
#endif
}
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
@@ -174,7 +217,10 @@ class MotorDriver {
bool isPWMCapable();
bool canMeasureCurrent();
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte setCommonFaultPin() {
return commonFaultPin = true;
}
inline byte getFaultPin() {
return faultPin;
}
@@ -182,23 +228,83 @@ class MotorDriver {
isProgTrack = on;
}
void checkPowerOverload(bool useProgLimit, byte trackno);
inline void setTrackLetter(char c) {
trackLetter = c;
};
// this returns how much time has passed since the last power change. If it
// was really long ago (approx > 52min) advance counter approx 35 min so that
// we are at 18 minutes again. Times for 32 bit unsigned long.
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange[(int)mode];
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
return diff;
};
#ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
invertOutput(trackMode & TRACK_MODE_INV);
};
inline void invertOutput() { // toggles output inversion
invertPhase = !invertPhase;
invertOutput(invertPhase);
};
inline void invertOutput(bool b) { // sets output inverted or not
if (b)
invertPhase = 1;
else
invertPhase = 0;
#if defined(ARDUINO_ARCH_ESP32)
pinpair p = getSignalPin();
uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin);
if (invertPhase) // set or clear the invert bit in the gpio out register
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
else
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
if (p.invpin != UNUSED_PIN) {
outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin);
if (invertPhase) // clear or set the invert bit in the gpio out register
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
else
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
}
#endif
};
inline TRACK_MODE getMode() {
return trackMode;
};
private:
char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result);
}
};
// side effect sets lastCurrent and tripValue
inline bool checkCurrent(bool useProgLimit) {
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
lastCurrent = getCurrentRaw();
if (lastCurrent < 0)
lastCurrent = -lastCurrent;
return lastCurrent >= tripValue;
};
// side effect sets lastCurrent
inline bool checkFault() {
lastCurrent = getCurrentRaw();
return lastCurrent < 0;
};
VPIN powerPin;
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
bool dualSignal; // true to use signalPin2
bool invertBrake; // brake pin passed as negative means pin is inverted
bool invertPower; // power pin passed as negative means pin is inverted
bool invertFault; // fault pin passed as negative means pin is inverted
bool invertPhase = 0; // phase of out pin is inverted
// Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale
//
@@ -211,10 +317,14 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
POWERMODE lastPowerMode;
unsigned long lastPowerChange[4]; // timestamp in microseconds
unsigned long lastBadSample; // timestamp in microseconds
// used to sync restore time when common Fault pin detected
static unsigned long globalOverloadStart; // timestamp in microseconds
int progTripValue;
int lastCurrent;
int lastCurrent; //temp value
int tripValue; //temp value
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
@@ -222,16 +332,28 @@ class MotorDriver {
int maxmA;
int tripmA;
// Wait times for power management. Unit: milliseconds
static const int POWER_SAMPLE_ON_WAIT = 100;
static const int POWER_SAMPLE_OFF_WAIT = 1000;
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
// Times for overload management. Unit: microseconds.
// Base for wait time until power is turned on again
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
// Time after we consider all faults old and forgotten
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
// Time after which we consider a ALERT over
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
// How long to ignore fault pin if current is under limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
// How long to ignore fault pin if current is higher than limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
// How long to wait between overcurrent and turning off
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
// Upper limit for retry period
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
};
#endif

View File

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

197
Release_Notes/CommandRef.md Normal file
View File

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

View File

@@ -236,11 +236,6 @@ void SSD1306AsciiWire::setRowNative(uint8_t line) {
size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
const uint8_t* base = m_font;
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0;
// Check if character would be partly or wholly off the display
if (m_col + fontWidth > m_displayWidth)
return 0;
#if defined(NOLOWERCASE)
// Adjust if lowercase is missing
if (ch >= 'a') {
@@ -250,6 +245,12 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
ch -= 26; // Allow for missing lowercase letters
}
#endif
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0;
// Check if character would be partly or wholly off the display
if (m_col + fontWidth > m_displayWidth)
return 0;
ch -= m_fontFirstChar;
base += fontWidth * ch;
// Before using buffer, wait for last request to complete
@@ -406,8 +407,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x38, 0x44, 0xc6, 0x44, 0x20, 0x00, // cent 0x9b
0x44, 0x6e, 0x59, 0x49, 0x62, 0x00, // £ 0x9c
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
@@ -425,27 +426,27 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x10, 0x28, 0x54, 0x28, 0x44, 0x00, // <<
0x44, 0x28, 0x54, 0x28, 0x10, 0x00, // >>
// Extended characters 176-180
0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0
0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1
0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x55, // Mid grey 0xb1
0x6d, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3
0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4
0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x14, 0x14, 0x14, 0xff, 0x00, 0x00, // Vertical line with double left spur 0xb5
0x08, 0x08, 0xff, 0x00, 0xff, 0x00, // Double vertical line with single left spur
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x00, // Top right corner, single horiz, double vert
0x14, 0x14, 0x14, 0xfc, 0x00, 0x00, // Top right corner, double horiz, single vert
// Extended characters 185-190
0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
0x14, 0x14, 0xf7, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba
0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb
0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x00, // Bottom right corner, single horiz, double vert 0xbd
0x14, 0x14, 0x14, 0x1f, 0x00, 0x00, // Bottom right corner, double horiz, single vert 0xbe
// Extended characters 191-199
0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf
@@ -455,8 +456,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4
0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0xff, 0x14, 0x14, // Vertical line double right spur 0xc6
0x00, 0x00, 0xff, 0x00, 0xff, 0x08, // Double vertical line single right spur 0xc7
// Extended characters 200-206
0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8
@@ -467,16 +468,16 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd
0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x14, 0x14, 0x14, 0x17, 0x14, 0x14, // Double horizontal line single upward spur 0xcf
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, // Horiz single line with double upward spur 0xd0
0x14, 0x14, 0x14, 0xf4, 0x14, 0x14, // Horiz double line with single downward spur 0xd1
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x08, // Horiz single line with double downward spur 0xd2
0x00, 0x00, 0x0f, 0x08, 0x0f, 0x08, // Bottom left corner, double vert single horiz 0xd3
0x00, 0x00, 0x00, 0x1f, 0x14, 0x14, // Bottom left corner, single vert double horiz 0xd4
0x00, 0x00, 0x00, 0xfc, 0x14, 0x14, // Top left corner, single vert double horiz 0xd5
0x00, 0x00, 0xf8, 0x08, 0xf8, 0x08, // Top left corner, double vert single horiz 0xd6
0x08, 0x08, 0xff, 0x00, 0xff, 0x08, // Cross, double vert single horiz 0xd7
0x14, 0x14, 0x14, 0xf7, 0x14, 0x14, // Cross, single vert double horiz 0xd8
// Extended characters 217-223
0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9
@@ -487,10 +488,10 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, // Bottom Left block 0xe0
0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, // Bottom Right block
0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, // Top left block
0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, // Top right block 0xe3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
@@ -511,9 +512,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
// Extended character 248
0x00, 0x06, 0x09, 0x09, 0x06, 0x00, // degree symbol 0xf8
0x00, 0x06, 0x09, 0x09, 0x06, 0x00 // degree symbol 0xf8
#endif
0x00
};
const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;

View File

@@ -1,517 +0,0 @@
/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI
David Johnson-Davies - www.technoblogy.com - 26th October 2022
CC BY 4.0
Licensed under a Creative Commons Attribution 4.0 International license:
http://creativecommons.org/licenses/by/4.0/
*/
#include "FSH.h"
#include "DisplayInterface.h"
#if defined(MEGATINYCORE)
// ATtiny402/412 PORTA positions. Change these for the chip you're using
int const dc = 7;
int const mosi = 1;
int const sck = 3;
int const cs = 6;
// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port
#define PORT_TOGGLE(x) PORTA.OUTTGL = (x)
#define PORT_LOW(x) PORTA.OUTCLR = (x)
#define PORT_HIGH(x) PORTA.OUTSET = (x)
#define PORT_OUTPUT(x) PORTA.DIRSET = (x)
#else
// ATtiny45/85 PORTB positions. Change these for the chip you're using
int const dc = 0;
int const mosi = 1;
int const sck = 2;
int const cs = 3;
// Classic ATtiny port manipulations - assumes all pins in same port
#define PORT_TOGGLE(x) PINB = (x)
#define PORT_LOW(x) PORTB = PORTB & ~((x));
#define PORT_HIGH(x) PORTB = PORTB | ((x))
#define PORT_OUTPUT(x) DDRB = (x)
#endif
// Display parameters - uncomment the line for the one you want to use
// Adafruit 1.44" 128x128 display
// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1;
// AliExpress 1.44" 128x128 display
// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1;
// Adafruit 0.96" 160x80 display
// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0;
// AliExpress 0.96" 160x80 display
// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1;
// Adafruit 1.8" 160x128 display
// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1;
// AliExpress 1.8" 160x128 display (red PCB)
int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1;
// AliExpress 1.8" 160x128 display (blue PCB)
// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0;
// Adafruit 1.14" 240x135 display
// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0;
// AliExpress 1.14" 240x135 display
// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0;
// Adafruit 1.3" 240x240 display
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
// Adafruit 1.54" 240x240 display
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
// AliExpress 1.54" 240x240 display
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
// Adafruit 1.9" 320x170 display
// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0;
// AliExpress 1.9" 320x170 display
// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0;
// Adafruit 1.47" 320x172 rounded rectangle display
// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0;
// AliExpress 1.47" 320x172 rounded rectangle display
// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0;
// Adafruit 2.0" 320x240 display
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0;
// AliExpress 2.0" 320x240 display
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0;
// Adafruit 2.2" 320x240 display
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1;
// AliExpress 2.4" 320x240 display
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1;
// Character set for text - stored in program memory
const uint8_t CharMap[96][6] FLASH = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 },
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 },
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 },
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 },
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 },
{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 },
{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 },
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 },
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 },
{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 },
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 },
{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 },
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 },
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 },
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 },
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 },
{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 },
{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 },
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 },
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 },
{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 },
{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 },
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 },
{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 },
{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 },
{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 },
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 },
{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 },
{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 },
{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 },
{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 },
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 },
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 },
{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 },
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 },
{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 },
{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 },
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 },
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 },
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 },
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 },
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 },
{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 },
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 },
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 },
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 },
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 },
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 },
{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 },
{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 },
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 },
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 },
{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 },
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 },
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 },
{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 },
{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 },
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 },
{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 },
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 },
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 },
{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 },
{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 },
{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 },
{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 },
{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 },
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 },
{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 },
{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 },
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 },
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 },
{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 },
{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 },
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 },
{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 },
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 },
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 },
{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 },
{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 },
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 },
{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 },
{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 },
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 },
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 },
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 },
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 },
{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 },
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 },
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 },
{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 },
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 },
{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~'
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }
};
// TFT colour display **********************************************
int const CASET = 0x2A; // Define column address
int const RASET = 0x2B; // Define row address
int const RAMWR = 0x2C; // Write to display RAM
int const White = 0xFFFF;
int const Black = 0;
// Current plot position and colours
int xpos, ypos;
int fore = White;
int back = Black;
int scale = 1; // Text scale
// Send a byte to the display
void Data (uint8_t d) {
for (uint8_t bit = 0x80; bit; bit >>= 1) {
PORT_TOGGLE(1<<sck);
if (d & bit) PORT_HIGH(1<<mosi); else PORT_LOW(1<<mosi);
PORT_TOGGLE(1<<sck);
}
}
// Send a command to the display
void Command (uint8_t c) {
PORT_TOGGLE(1<<dc);
Data(c);
PORT_TOGGLE(1<<dc);
}
// Send a command followed by two data words
void Command2 (uint8_t c, uint16_t d1, uint16_t d2) {
PORT_TOGGLE(1<<dc);
Data(c);
PORT_TOGGLE(1<<dc);
Data(d1>>8); Data(d1); Data(d2>>8); Data(d2);
}
void InitDisplay () {
PORT_OUTPUT(1<<dc | 1<<cs | 1<<mosi | 1<<sck); // All outputs
PORT_HIGH(1<<dc | 1<<cs | 1<<sck); // Outputs high
PORT_TOGGLE(1<<cs);
Command(0x01); // Software reset
delay(250); // delay 250 ms
Command(0x36); Data(rotate<<5 | bgr<<3); // Set orientation and rgb/bgr
Command(0x3A); Data(0x55); // Set color mode - 16-bit color
Command(0x20+invert); // Invert
Command(0x11); // Out of sleep mode
delay(150);
PORT_TOGGLE(1<<cs);
}
void DisplayOn () {
PORT_TOGGLE(1<<cs);
Command(0x29); // Display on
delay(150);
PORT_TOGGLE(1<<cs);
}
void ClearDisplay () {
PORT_TOGGLE(1<<cs);
Command2(CASET, yoff, yoff + ysize - 1);
Command2(RASET, xoff, xoff + xsize - 1);
Command(0x3A); Data(0x03); // 12-bit colour
Command(RAMWR); // Leaves mosi low
for (int i=0; i<xsize*4; i++) {
for (int j=0; j<ysize*3; j++) {
PORT_TOGGLE(1<<sck);
PORT_TOGGLE(1<<sck);
}
}
Command(0x3A); Data(0x05); // Back to 16-bit colour
PORT_TOGGLE(1<<cs);
}
unsigned int Colour (int r, int g, int b) {
return (r & 0xf8)<<8 | (g & 0xfc)<<3 | b>>3;
}
// Move current plot position to x,y
void MoveTo (int x, int y) {
xpos = x; ypos = y;
}
// Plot point at x,y
void PlotPoint (int x, int y) {
PORT_TOGGLE(1<<cs);
Command2(CASET, yoff+y, yoff+y);
Command2(RASET, xoff+x, xoff+x);
Command(RAMWR); Data(fore>>8); Data(fore & 0xff);
PORT_TOGGLE(1<<cs);
}
// Draw a line to x,y
void DrawTo (int x, int y) {
int sx, sy, e2, err;
int dx = abs(x - xpos);
int dy = abs(y - ypos);
if (xpos < x) sx = 1; else sx = -1;
if (ypos < y) sy = 1; else sy = -1;
err = dx - dy;
for (;;) {
PlotPoint(xpos, ypos);
if (xpos==x && ypos==y) return;
e2 = err<<1;
if (e2 > -dy) { err = err - dy; xpos = xpos + sx; }
if (e2 < dx) { err = err + dx; ypos = ypos + sy; }
}
}
void FillRect (int w, int h) {
PORT_TOGGLE(1<<cs);
Command2(CASET, ypos+yoff, ypos+yoff+h-1);
Command2(RASET, xpos+xoff, xpos+xoff+w-1);
Command(RAMWR);
uint8_t hi = fore>>8;
uint8_t lo = fore & 0xff;
for (int i=0; i<w; i++) {
for (int j=0; j<h; j++) {
Data(hi); Data(lo);
}
}
PORT_TOGGLE(1<<cs);
}
void DrawRect (int w, int h) {
int x1 = xpos, y1 = ypos;
FillRect(w-1, 1); MoveTo(x1, y1+1);
FillRect(1, h-1); MoveTo(x1+1, y1+h-1);
FillRect(w-1, 1); MoveTo(x1+w-1, y1);
FillRect(1, h-1);
xpos = x1; ypos = y1;
}
void FillCircle (int radius) {
int x1 = xpos, y1 = ypos, dx = 1, dy = 1;
int x = radius - 1, y = 0;
int err = dx - (radius<<1);
while (x >= y) {
MoveTo(x1-x, y1+y); FillRect(x<<1, 1);
MoveTo(x1-y, y1+x); FillRect(y<<1, 1);
MoveTo(x1-y, y1-x); FillRect(y<<1, 1);
MoveTo(x1-x, y1-y); FillRect(x<<1, 1);
if (err > 0) {
x = x - 1; dx = dx + 2;
err = err - (radius<<1) + dx;
} else {
y = y + 1; err = err + dy;
dy = dy + 2;
}
}
xpos = x1; ypos = y1;
}
void DrawCircle (int radius) {
int x1 = xpos, y1 = ypos, dx = 1, dy = 1;
int x = radius - 1, y = 0;
int err = dx - (radius<<1);
while (x >= y) {
PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y);
PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x);
PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x);
PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y);
if (err > 0) {
x = x - 1; dx = dx + 2;
err = err - (radius<<1) + dx;
} else {
y = y + 1; err = err + dy;
dy = dy + 2;
}
}
}
// Plot an ASCII character with bottom left corner at x,y
void PlotChar (char c) {
int colour;
PORT_TOGGLE(1<<cs);
Command2(CASET, yoff+ypos, yoff+ypos+8*scale-1);
Command2(RASET, xoff+xpos, xoff+xpos+6*scale-1);
Command(RAMWR);
for (int xx=0; xx<6; xx++) {
int bits = pgm_read_byte(&CharMap[c-32][xx]);
for (int xr=0; xr<scale; xr++) {
for (int yy=0; yy<8; yy++) {
if (bits>>(7-yy) & 1) colour = fore; else colour = back;
for (int yr=0; yr<scale; yr++) {
Data(colour>>8); Data(colour & 0xFF);
}
}
}
}
PORT_TOGGLE(1<<cs);
xpos = xpos + 6*scale;
}
// Plot text starting at the current plot position
void PlotText(PGM_P p) {
while (1) {
char c = pgm_read_byte(p++);
if (c == 0) return;
PlotChar(c);
}
}
void PlotInt(int n) {
bool lead = false;
for (int d=10000; d>0; d = d/10) {
char j = (n/d) % 10;
if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; }
}
}
void TestChart () {
DrawRect(xsize, ysize);
scale = 8;
fore = Colour(255, 0, 0);
MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F');
scale = 1;
}
// Demos **********************************************
void BarChart () {
int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11;
MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings"));
// Horizontal axis
int xinc = (w-x1)/20;
MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1);
for (int i=0; i<=20; i=i+4) {
int mark = x1+i*xinc;
MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2);
// Draw histogram
if (i != 20) {
int bar = xinc*4/3;
for (int b=2; b>=0; b--) {
fore = Colour(255, 127*b, 0); // Red, Orange, Yellow
MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20));
}
fore = White;
}
if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11);
PlotInt(i);
}
// Vertical axis
int yinc = (h-y1)/20;
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1);
for (int i=0; i<=20; i=i+5) {
int mark = y1+i*yinc;
MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark);
if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4);
PlotInt(i);
}
}
void Waterfall () {
int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11;
int factor = 5160/h*10;
MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance"));
// Horizontal axis
int xinc = (w-x1-15)/30;
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1);
for (int i=0; i<=20; i=i+5) {
int mark = x1+i*xinc;
MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2);
if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11);
PlotInt(i);
}
// Vertical axis
int yinc = (h-y1)/20;
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1);
for (int i=0; i<=20; i=i+5) {
int mark = y1+i*yinc;
MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark);
if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4);
PlotInt(i);
}
// Diagonal axis
yinc = xinc/2;
// MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc);
MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc);
for (int i=0; i<=20; i=i+5) {
MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2);
DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2);
MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i);
}
// Plot data
for (int y=20; y>=0; y--) {
for (int i=0; i<=20; i++) {
int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10);
int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10);
fore = Colour(255, 255, 0);
MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor);
DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor);
fore = White;
}
}
}
// Setup **********************************************
void setup() {
InitDisplay();
ClearDisplay();
DisplayOn();
MoveTo(0,0);
// TestChart();
}
void loop () {
BarChart();
// Waterfall();
for (;;);
}

View File

@@ -87,6 +87,9 @@ void SerialManager::init() {
delay(1000);
}
#endif
#ifdef SABERTOOTH
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#endif
}
void SerialManager::broadcast(char * stringBuffer) {
@@ -108,14 +111,15 @@ void SerialManager::loop2() {
bufferLength = 0;
buffer[0] = '\0';
}
else if (ch == '>') {
buffer[bufferLength] = '\0';
DCCEXParser::parse(serial, buffer, NULL);
inCommandPayload = false;
break;
}
else if (inCommandPayload) {
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
else if (inCommandPayload) {
if (bufferLength < (COMMAND_BUFFER_SIZE-1))
buffer[bufferLength++] = ch;
if (ch == '>') {
buffer[bufferLength] = '\0';
DCCEXParser::parse(serial, buffer, NULL);
inCommandPayload = false;
break;
}
}
}

View File

@@ -19,6 +19,7 @@
#include "StringFormatter.h"
#include <stdarg.h>
#include "DisplayInterface.h"
#include "CommandDistributor.h"
bool Diag::ACK=false;
bool Diag::CMD=false;
@@ -45,6 +46,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n"));
// send to virtual LCD collector (if any)
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row);
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
DisplayInterface::setRow(row);
va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args);
@@ -52,6 +61,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args;
// send to virtual LCD collector (if any)
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row);
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
DisplayInterface::setRow(display, row);
va_start(args, input);
@@ -117,6 +134,25 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
case 'M':
{ // this prints a unsigned long microseconds time in readable format
unsigned long time = va_arg(args, long);
if (time >= 2000) {
time = time / 1000;
if (time >= 2000) {
printPadded(stream, time/1000, formatWidth, formatLeft);
stream->print(F("sec"));
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("msec"));
}
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("usec"));
}
}
break;
//case 'f': stream->print(va_arg(args, double), 2); break;
//format width prefix
case '-':
@@ -200,4 +236,15 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
if (!formatLeft) stream->print(value, DEC);
}
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
const char FLASH hexchars[]="0123456789ABCDEF";
void StringFormatter::printHex(Print * stream,uint16_t value) {
char result[5];
for (int i=3;i>=0;i--) {
result[i]=GETFLASH(hexchars+(value & 0x0F));
value>>=4;
}
result[4]='\0';
stream->print(result);
}

View File

@@ -49,10 +49,10 @@ class StringFormatter
static void lcd2(uint8_t display, byte row, const FSH* input...);
static void printEscapes(char * input);
static void printEscape( char c);
static void printHex(Print * stream,uint16_t value);
private:
static void send2(Print * serial, const FSH* input,va_list args);
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
};
#endif

View File

@@ -1,6 +1,7 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2022,2023 Harald Barth
* © 2023 Colin Murdoch
* All rights reserved.
*
* This file is part of DCC++EX
@@ -25,24 +26,32 @@
#include "MotorDriver.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include "CommandDistributor.h"
#include "DCCEXParser.h"
// Virtualised Motor shield multi-track hardware Interface
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
#define APPLY_BY_MODE(findmode,function) \
FOR_EACH_TRACK(t) \
if (trackMode[t]==findmode) \
if (track[t]->getMode()==findmode) \
track[t]->function;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_PROG = -29718;
#endif
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_OFF = 22479;
const int16_t HASH_KEYWORD_NONE = -26550;
const int16_t HASH_KEYWORD_DC = 2183;
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
const int16_t HASH_KEYWORD_AUTO = -5457;
#ifdef BOOSTER_INPUT
const int16_t HASH_KEYWORD_BOOST = 11269;
#endif
const int16_t HASH_KEYWORD_INV = 11857;
MotorDriver * TrackManager::track[MAX_TRACKS];
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
@@ -51,7 +60,7 @@ bool TrackManager::progTrackSyncMain=false;
bool TrackManager::progTrackBoosted=false;
int16_t TrackManager::joinRelay=UNUSED_PIN;
#ifdef ARDUINO_ARCH_ESP32
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
#endif
#ifdef ANALOG_READ_INTERRUPT
@@ -72,7 +81,7 @@ void TrackManager::sampleCurrent() {
waiting = false;
tr++;
if (tr > lastTrack) tr = 0;
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) {
return; // We could continue but for prog track we
// rather do it in next interrupt beacuse
// that gives us well defined sampling point.
@@ -83,7 +92,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) {
// look for a valid track to sample or until we are around
while (true) {
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1);
waiting = true;
@@ -115,19 +124,32 @@ void TrackManager::Setup(const FSH * shieldname,
// Default the first 2 tracks (which may be null) and perform HA waveform check.
setTrackMode(0,TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
setTrackMode(1,TRACK_MODE_PROG);
#else
setTrackMode(1,TRACK_MODE_MAIN);
#endif
// TODO Fault pin config for odd motor boards (example pololu)
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
// && (mainDriver->getFaultPin() != UNUSED_PIN));
DCC::begin(shieldname);
// Fault pin config for odd motor boards (example pololu)
FOR_EACH_TRACK(t) {
for (byte s=t+1;s<=lastTrack;s++) {
if (track[t]->getFaultPin() != UNUSED_PIN &&
track[t]->getFaultPin() == track[s]->getFaultPin()) {
track[t]->setCommonFaultPin();
track[s]->setCommonFaultPin();
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
}
}
}
DCC::setShieldName(shieldname);
}
void TrackManager::addTrack(byte t, MotorDriver* driver) {
trackMode[t]=TRACK_MODE_OFF;
track[t]=driver;
if (driver) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
track[t]->setTrackLetter('A'+t);
lastTrack=t;
}
}
@@ -138,10 +160,16 @@ void TrackManager::setDCCSignal( bool on) {
HAVE_PORTA(shadowPORTA=PORTA);
HAVE_PORTB(shadowPORTB=PORTB);
HAVE_PORTC(shadowPORTC=PORTC);
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
HAVE_PORTC(PORTC=shadowPORTC);
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
}
void TrackManager::setCutout( bool on) {
@@ -156,10 +184,16 @@ void TrackManager::setPROGSignal( bool on) {
HAVE_PORTA(shadowPORTA=PORTA);
HAVE_PORTB(shadowPORTB=PORTB);
HAVE_PORTC(shadowPORTC=PORTC);
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
HAVE_PORTC(PORTC=shadowPORTC);
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
}
// setDCSignal(), called from normal context
@@ -167,64 +201,95 @@ void TrackManager::setPROGSignal( bool on) {
// with interrupts turned off around the critical section
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab) continue;
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
if (trackDCAddr[t]!=cab && cab != 0) continue;
if (track[t]->getMode() & TRACK_MODE_DC)
track[t]->setDCSignal(speedbyte);
}
}
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
//DIAG(F("Track=%c"),trackToSet+'A');
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
// DC tracks require a motorDriver that can set brake!
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
&& !track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
if (mode & TRACK_MODE_DC) {
#if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC"));
return false;
#endif
if (!track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
}
#ifdef ARDUINO_ARCH_ESP32
// remove pin from MUX matrix and turn it off
pinpair p = track[trackToSet]->getSignalPin();
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
gpio_reset_pin((gpio_num_t)p.pin);
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
if (p.invpin != UNUSED_PIN) {
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
gpio_reset_pin((gpio_num_t)p.invpin);
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
}
#ifdef BOOSTER_INPUT
if (mode & TRACK_MODE_BOOST) {
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
pinMode(BOOSTER_INPUT, INPUT);
gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
if (p.invpin != UNUSED_PIN) {
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
}
} else // elseif clause continues
#endif
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
// gpio_reset_pin may reset to input
pinMode(p.pin, OUTPUT);
if (p.invpin != UNUSED_PIN)
pinMode(p.invpin, OUTPUT);
}
#endif
#ifndef DISABLE_PROG
if (mode & TRACK_MODE_PROG) {
#else
if (false) {
#endif
if (mode==TRACK_MODE_PROG) {
// only allow 1 track to be prog
FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
track[t]->setPower(POWERMODE::OFF);
trackMode[t]=TRACK_MODE_OFF;
track[t]->setMode(TRACK_MODE_NONE);
track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t);
}
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
} else {
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
}
trackMode[trackToSet]=mode;
track[trackToSet]->setMode(mode);
trackDCAddr[trackToSet]=dcAddr;
streamTrackState(NULL,trackToSet);
// When a track is switched, we must clear any side effects of its previous
// state, otherwise trains run away or just dont move.
// This can be done BEFORE the PWM-Timer evaluation (methinks)
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
if (!(mode & TRACK_MODE_DC)) {
// DCC tracks need to have set the PWM to zero or they will not work.
track[trackToSet]->detachDCSignal();
track[trackToSet]->setBrake(false);
}
// EXT is a special case where the signal pin is
// turned off. So unless that is set, the signal
// pin should be turned on
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
// BOOST:
// Leave it as is
// otherwise:
// EXT is a special case where the signal pin is
// turned off. So unless that is set, the signal
// pin should be turned on
if (!(mode & TRACK_MODE_BOOST))
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
#ifndef ARDUINO_ARCH_ESP32
// re-evaluate HighAccuracy mode
@@ -234,7 +299,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX.
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
if (track[t]->getMode() & TRACK_MODE_DC) {
if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below
@@ -242,7 +307,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
}
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM;
@@ -260,10 +325,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
#else
// For ESP32 we just reinitialize the DCC Waveform
DCCWaveform::begin();
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
track[trackToSet]->setMode(mode);
#endif
// This block must be AFTER the PWM-Timer modifications
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
if (mode & TRACK_MODE_DC) {
// DC tracks need to be given speed of the throttle for that cab address
// otherwise will not match other tracks on same cab.
// This also needs to allow for inverted DCX
@@ -272,7 +339,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// Normal running tracks are set to the global power state
track[trackToSet]->setPower(
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
(mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ?
mainPowerGuess : POWERMODE::OFF);
//DIAG(F("TrackMode=%d"),mode);
return true;
@@ -280,8 +347,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
if (trackMode[t]==TRACK_MODE_DCX)
speedByte = speedByte ^ 128; // reverse direction bit
track[t]->setDCSignal(speedByte);
}
@@ -290,37 +355,9 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
if (params==0) { // <=> List track assignments
FOR_EACH_TRACK(t)
if (track[t]!=NULL) {
StringFormatter::send(stream,F("<= %c "),'A'+t);
switch(trackMode[t]) {
case TRACK_MODE_MAIN:
StringFormatter::send(stream,F("MAIN"));
if (track[t]->trackPWM)
StringFormatter::send(stream,F("+"));
break;
case TRACK_MODE_PROG:
StringFormatter::send(stream,F("PROG"));
if (track[t]->trackPWM)
StringFormatter::send(stream,F("+"));
break;
case TRACK_MODE_OFF:
StringFormatter::send(stream,F("OFF"));
break;
case TRACK_MODE_EXT:
StringFormatter::send(stream,F("EXT"));
break;
case TRACK_MODE_DC:
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
break;
case TRACK_MODE_DCX:
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
break;
default:
break; // unknown, dont care
}
StringFormatter::send(stream,F(">\n"));
}
streamTrackState(stream,t);
return true;
}
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
@@ -331,41 +368,97 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
return setTrackMode(p[0],TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
return setTrackMode(p[0],TRACK_MODE_PROG);
#endif
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
return setTrackMode(p[0],TRACK_MODE_OFF);
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
return setTrackMode(p[0],TRACK_MODE_NONE);
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT);
#ifdef BOOSTER_INPUT
if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST>
return setTrackMode(p[0],TRACK_MODE_BOOST);
#endif
if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
return false;
}
void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return;
auto format=F("<= %d XXX>\n");
TRACK_MODE tm = track[t]->getMode();
if (tm & TRACK_MODE_MAIN) {
if(tm & TRACK_MODE_AUTOINV)
format=F("<= %c MAIN A>\n");
else if (tm & TRACK_MODE_INV)
format=F("<= %c MAIN I>\n");
else
format=F("<= %c MAIN>\n");
}
#ifndef DISABLE_PROG
else if (tm & TRACK_MODE_PROG)
format=F("<= %c PROG>\n");
#endif
else if (tm & TRACK_MODE_NONE)
format=F("<= %c NONE>\n");
else if(tm & TRACK_MODE_EXT)
format=F("<= %c EXT>\n");
else if(tm & TRACK_MODE_BOOST) {
if(tm & TRACK_MODE_AUTOINV)
format=F("<= %c B A>\n");
else if (tm & TRACK_MODE_INV)
format=F("<= %c B I>\n");
else
format=F("<= %c B>\n");
}
else if (tm & TRACK_MODE_DC) {
if (tm & TRACK_MODE_INV)
format=F("<= %c DCX %d>\n");
else
format=F("<= %c DC %d>\n");
}
if (stream)
StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
else
CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
}
byte TrackManager::nextCycleTrack=MAX_TRACKS;
void TrackManager::loop() {
DCCWaveform::loop();
DCCACK::loop();
DCCWaveform::loop();
#ifndef DISABLE_PROG
DCCACK::loop();
#endif
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
nextCycleTrack++;
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack];
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
}
MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
return NULL;
}
@@ -373,57 +466,124 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
return v;
}
#endif
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
if (!setProg) mainPowerGuess=mode;
FOR_EACH_TRACK(t) {
MotorDriver * driver=track[t];
if (!driver) continue;
switch (trackMode[t]) {
case TRACK_MODE_MAIN:
if (setProg) break;
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
// XXX see if we can make this conditional
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
driver->setPower(mode);
break;
case TRACK_MODE_DC:
case TRACK_MODE_DCX:
if (setProg) break;
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
driver->setPower(mode);
break;
case TRACK_MODE_PROG:
if (!setProg) break;
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_EXT:
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_OFF:
break;
}
// Set track power for all tracks with this mode
void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) {
FOR_EACH_TRACK(t) {
MotorDriver *driver=track[t];
if (trackmode & driver->getMode()) {
if (powermode == POWERMODE::ON) {
if (trackmode & TRACK_MODE_DC) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
} else {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
}
}
driver->setPower(powermode);
}
}
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (trackMode[t]==TRACK_MODE_PROG)
return track[t]->getPower();
return POWERMODE::OFF;
}
}
// Set track power for this track, inependent of mode
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
MotorDriver *driver=track[t];
TRACK_MODE trackmode = driver->getMode();
if (trackmode & TRACK_MODE_DC) {
if (powermode == POWERMODE::ON) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
}
} else {
if (powermode == POWERMODE::ON) {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
}
}
driver->setPower(powermode);
}
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
// This function is for backward JMRI compatibility only
// It reports the first track only, as main, regardless of track settings.
// <c MeterName value C/V unit min max res warn>
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
}
// returns state of the one and only prog track
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (track[t]->getMode() & TRACK_MODE_PROG)
return track[t]->getPower(); // optimize: there is max one prog track
return POWERMODE::OFF;
}
// returns on if all are on. returns off otherwise
POWERMODE TrackManager::getMainPower() {
POWERMODE result = POWERMODE::OFF;
FOR_EACH_TRACK(t) {
if (track[t]->getMode() & TRACK_MODE_MAIN) {
POWERMODE p = track[t]->getPower();
if (p == POWERMODE::OFF)
return POWERMODE::OFF; // done and out
if (p == POWERMODE::ON)
result = POWERMODE::ON;
}
}
return result;
}
bool TrackManager::getPower(byte t, char s[]) {
if (t > lastTrack)
return false;
if (track[t]) {
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
s[2] = t + 'A';
return true;
}
return false;
}
void TrackManager::reportObsoleteCurrent(Print* stream) {
// This function is for backward JMRI compatibility only
// It reports the first track only, as main, regardless of track settings.
// <c MeterName value C/V unit min max res warn>
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
}
void TrackManager::reportCurrent(Print* stream) {
StringFormatter::send(stream,F("<jI"));
FOR_EACH_TRACK(t) {
StringFormatter::send(stream, F(" %d"),
(track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
}
StringFormatter::send(stream,F(">\n"));
}
void TrackManager::reportGauges(Print* stream) {
StringFormatter::send(stream,F("<jG"));
FOR_EACH_TRACK(t) {
StringFormatter::send(stream, F(" %d"),
track[t]->raw2mA(track[t]->getRawCurrentTripValue()));
}
StringFormatter::send(stream,F(">\n"));
}
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
joinRelay=joinRelayPin;
if (joinRelay!=UNUSED_PIN) {
@@ -436,7 +596,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32
if (joined) {
FOR_EACH_TRACK(t) {
if (trackMode[t]==TRACK_MODE_PROG) {
if (track[t]->getMode() & TRACK_MODE_PROG) {
tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN);
break;
@@ -444,7 +604,12 @@ void TrackManager::setJoin(bool joined) {
}
} else {
if (tempProgTrack != MAX_TRACKS+1) {
// as setTrackMode with TRACK_MODE_PROG defaults to
// power off, we will take the current power state
// of our track and then preserve that state.
POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
track[tempProgTrack]->setPower(tPTmode); //set track status as it was before
tempProgTrack = MAX_TRACKS+1;
}
}
@@ -452,3 +617,39 @@ void TrackManager::setJoin(bool joined) {
progTrackSyncMain=joined;
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
}
bool TrackManager::isPowerOn(byte t) {
if (track[t]->getPower()!=POWERMODE::ON)
return false;
return true;
}
bool TrackManager::isProg(byte t) {
if (track[t]->getMode() & TRACK_MODE_PROG)
return true;
return false;
}
byte TrackManager::returnMode(byte t) {
return (track[t]->getMode());
}
int16_t TrackManager::returnDCAddr(byte t) {
return (trackDCAddr[t]);
}
const char* TrackManager::getModeName(byte Mode) {
//DIAG(F("PowerMode %d"), Mode);
switch (Mode)
{
case 1: return "NONE";
case 2: return "MAIN";
case 4: return "PROG";
case 8: return "DC";
case 16: return "DCX";
case 32: return "EXT";
default: return "----";
}
}

View File

@@ -1,6 +1,8 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2023 Colin Murdoch
*
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -27,10 +29,6 @@
#include "MotorDriver.h"
// Virtualised Motor shield multi-track hardware Interface
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
@@ -41,6 +39,10 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
class TrackManager {
public:
static void Setup(const FSH * shieldName,
@@ -60,27 +62,48 @@ class TrackManager {
static void setDCSignal(int16_t cab, byte speedbyte);
static MotorDriver * getProgDriver();
#ifdef ARDUINO_ARCH_ESP32
static std::vector<MotorDriver *>getMainDrivers();
static std::vector<MotorDriver *>getMainDrivers();
#endif
static void setPower2(bool progTrack,POWERMODE mode);
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
static void setTrackPower(POWERMODE mode, byte t);
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
static const int16_t MAX_TRACKS=8;
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
static void loop();
static POWERMODE getMainPower() {return mainPowerGuess;}
static POWERMODE getMainPower();
static POWERMODE getProgPower();
static bool getPower(byte t, char s[]);
static void setJoin(bool join);
static bool isJoined() { return progTrackSyncMain;}
static void setJoinRelayPin(byte joinRelayPin);
static void sampleCurrent();
static void reportGauges(Print* stream);
static void reportCurrent(Print* stream);
static void reportPowerChange(Print* stream, byte thistrack);
static void reportObsoleteCurrent(Print* stream);
static void streamTrackState(Print* stream, byte t);
static bool isPowerOn(byte t);
static bool isProg(byte t);
static byte returnMode(byte t);
static int16_t returnDCAddr(byte t);
static const char* getModeName(byte Mode);
static int16_t joinRelay;
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
static bool progTrackBoosted; // true when prog track is not current limited
#ifdef DEBUG_ADC
public:
#else
private:
#endif
static MotorDriver* track[MAX_TRACKS];
private:
static void addTrack(byte t, MotorDriver* driver);
static byte lastTrack;
@@ -88,9 +111,7 @@ class TrackManager {
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t);
static MotorDriver* track[MAX_TRACKS];
static TRACK_MODE trackMode[MAX_TRACKS];
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC
#ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join
#endif

View File

@@ -110,49 +110,40 @@
/* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) {
Turnout *tt = get(id);
if (!tt) return false;
tt->_turnoutData.closed = closeFlag;
// I know it says setClosedStateOnly, but we need to tell others
// that the state has changed too.
#if defined(EXRAIL_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
CommandDistributor::broadcastTurnout(id, closeFlag);
// that the state has changed too. But we only broadcast if there
// really has been a change.
if (tt->_turnoutData.closed != closeFlag) {
tt->_turnoutData.closed = closeFlag;
CommandDistributor::broadcastTurnout(id, closeFlag);
}
#if defined(EXRAIL_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
return true;
}
#define DIAG_IO
// Static setClosed function is invoked from close(), throw() etc. to perform the
// common parts of the turnout operation. Code which is specific to a turnout
// type should be placed in the virtual function setClosedInternal(bool) which is
// called from here.
/* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) {
#if defined(DIAG_IO)
if (closeFlag)
DIAG(F("Turnout::close(%d)"), id);
else
DIAG(F("Turnout::throw(%d)"), id);
#endif
#if defined(DIAG_IO)
DIAG(F("Turnout(%d,%c)"), id, closeFlag ? 'c':'t');
#endif
Turnout *tt = Turnout::get(id);
if (!tt) return false;
bool ok = tt->setClosedInternal(closeFlag);
if (ok) {
tt->setClosedStateOnly(id, closeFlag);
#ifndef DISABLE_EEPROM
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#endif
#if defined(EXRAIL_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
// Send message to JMRI etc.
CommandDistributor::broadcastTurnout(id, closeFlag);
}
return ok;
}
@@ -259,6 +250,7 @@
}
}
tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed);
DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData));
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
return tt;
#else
@@ -298,7 +290,6 @@
#ifndef IO_NO_HAL
IODevice::writeAnalogue(_servoTurnoutData.vpin,
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
_turnoutData.closed = close;
#else
(void)close; // avoid compiler warnings
#endif
@@ -396,7 +387,6 @@
// and Close writes a 0.
// RCN-213 specifies that Throw is 0 and Close is 1.
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
_turnoutData.closed = close;
return true;
}
@@ -472,7 +462,6 @@
bool VpinTurnout::setClosedInternal(bool close) {
IODevice::write(_vpinTurnoutData.vpin, close);
_turnoutData.closed = close;
return true;
}
@@ -523,7 +512,10 @@
bool LCNTurnout::setClosedInternal(bool close) {
// Assume that the LCN command still uses 1 for throw and 0 for close...
LCN::send('T', _turnoutData.id, !close);
// The _turnoutData.closed flag should be updated by a message from the LCN master, later.
// The _turnoutData.closed flag should be updated by a message from the LCN master.
// but in this implementation it is updated in setClosedStateOnly() instead.
// If the LCN master updates this, setClosedStateOnly() and all setClosedInternal()
// have to be updated accordingly so that the closed flag is only set once.
return true;
}

View File

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

268
Turntables.cpp Normal file
View File

@@ -0,0 +1,268 @@
/*
* © 2023 Peter Cole
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "defines.h"
#include <Arduino.h>
#include "Turntables.h"
#include "StringFormatter.h"
#include "CommandDistributor.h"
#include "EXRAIL2.h"
#include "DCC.h"
// No turntable support without HAL
#ifndef IO_NO_HAL
/*
* Protected static data
*/
Turntable *Turntable::_firstTurntable = 0;
/*
* Public static data
*/
int Turntable::turntablelistHash = 0;
/*
* Protected static functions
*/
// Add new turntable to end of list
void Turntable::add(Turntable *tto) {
if (!_firstTurntable) {
_firstTurntable = tto;
} else {
Turntable *ptr = _firstTurntable;
for ( ; ptr->_nextTurntable!=0; ptr=ptr->_nextTurntable) {}
ptr->_nextTurntable = tto;
}
turntablelistHash++;
}
// Add a position
void Turntable::addPosition(uint8_t idx, uint16_t value, uint16_t angle) {
_turntablePositions.insert(idx, value, angle);
}
// Get value for position
uint16_t Turntable::getPositionValue(uint8_t position) {
TurntablePosition* currentPosition = _turntablePositions.getHead();
while (currentPosition) {
if (currentPosition->index == position) {
return currentPosition->data;
}
currentPosition = currentPosition->next;
}
return false;
}
// Get value for position
uint16_t Turntable::getPositionAngle(uint8_t position) {
TurntablePosition* currentPosition = _turntablePositions.getHead();
while (currentPosition) {
if (currentPosition->index == position) {
return currentPosition->angle;
}
currentPosition = currentPosition->next;
}
return false;
}
// Get the count of positions associated with the turntable
uint8_t Turntable::getPositionCount() {
TurntablePosition* currentPosition = _turntablePositions.getHead();
uint8_t count = 0;
while (currentPosition) {
count++;
currentPosition = currentPosition->next;
}
return count;
}
/*
* Public static functions
*/
// Find turntable from list
Turntable *Turntable::get(uint16_t id) {
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable)
if (tto->_turntableData.id == id) return tto;
return NULL;
}
// Find turntable via Vpin
Turntable *Turntable::getByVpin(VPIN vpin) {
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable) {
if (tto->isEXTT()) {
EXTTTurntable *exttTto = static_cast<EXTTTurntable*>(tto);
if (exttTto->getVpin() == vpin) {
return tto;
}
}
}
return nullptr;
}
// Get the current position for turntable with the specified ID
uint8_t Turntable::getPosition(uint16_t id) {
Turntable *tto = get(id);
if (!tto) return false;
return tto->getPosition();
}
// Got the moving state of the specified turntable
bool Turntable::ttMoving(uint16_t id) {
Turntable *tto = get(id);
if (!tto) return false;
return tto->isMoving();
}
// Initiate a turntable move
bool Turntable::setPosition(uint16_t id, uint8_t position, uint8_t activity) {
#if defined(DIAG_IO)
DIAG(F("Rotate turntable %d to position %d, activity %d)"), id, position, activity);
#endif
Turntable *tto = Turntable::get(id);
if (!tto) return false;
if (tto->isMoving()) return false;
bool ok = tto->setPositionInternal(position, activity);
if (ok) {
// We only deal with broadcasts for DCC turntables here, EXTT in the device driver
if (!tto->isEXTT()) {
CommandDistributor::broadcastTurntable(id, position, false);
}
// Trigger EXRAIL rotateEvent for both types here if changed
#if defined(EXRAIL_ACTIVE)
bool rotated = false;
if (position != tto->_previousPosition) rotated = true;
RMFT2::rotateEvent(id, rotated);
#endif
}
return ok;
}
/*************************************************************************************
* EXTTTurntable - EX-Turntable device.
*
*************************************************************************************/
// Private constructor
EXTTTurntable::EXTTTurntable(uint16_t id, VPIN vpin) :
Turntable(id, TURNTABLE_EXTT)
{
_exttTurntableData.vpin = vpin;
}
using DevState = IODevice::DeviceStateEnum;
// Create function
Turntable *EXTTTurntable::create(uint16_t id, VPIN vpin) {
#ifndef IO_NO_HAL
Turntable *tto = get(id);
if (tto) {
if (tto->isType(TURNTABLE_EXTT)) {
EXTTTurntable *extt = (EXTTTurntable *)tto;
extt->_exttTurntableData.vpin = vpin;
return tto;
}
}
if (!IODevice::exists(vpin)) return nullptr;
if (IODevice::getStatus(vpin) == DevState::DEVSTATE_FAILED) return nullptr;
if (Turntable::getByVpin(vpin)) return nullptr;
tto = (Turntable *)new EXTTTurntable(id, vpin);
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
return tto;
#else
(void)id;
(void)vpin;
return NULL;
#endif
}
void EXTTTurntable::print(Print *stream) {
StringFormatter::send(stream, F("<i %d EXTURNTABLE %d>\n"), _turntableData.id, _exttTurntableData.vpin);
}
// EX-Turntable specific code for moving to the specified position
bool EXTTTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
#ifndef IO_NO_HAL
int16_t value;
if (position == 0) {
value = 0; // Position 0 is just to send activities
} else {
if (activity > 1) return false; // If sending a position update, only phase changes valid (0|1)
value = getPositionValue(position); // Get position value from position list
}
if (position > 0 && !value) return false; // Return false if it's not a valid position
// Set position via device driver
_previousPosition = _turntableData.position;
_turntableData.position = position;
EXTurntable::writeAnalogue(_exttTurntableData.vpin, value, activity);
#else
(void)position;
#endif
return true;
}
/*************************************************************************************
* DCCTurntable - DCC Turntable device.
*
*************************************************************************************/
// Private constructor
DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
// Create function
Turntable *DCCTurntable::create(uint16_t id) {
#ifndef IO_NO_HAL
Turntable *tto = get(id);
if (!tto) {
tto = (Turntable *)new DCCTurntable(id);
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
}
return tto;
#else
(void)id;
return NULL;
#endif
}
void DCCTurntable::print(Print *stream) {
StringFormatter::send(stream, F("<i %d DCCTURNTABLE>\n"), _turntableData.id);
}
// EX-Turntable specific code for moving to the specified position
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
#ifndef IO_NO_HAL
int16_t value = getPositionValue(position);
if (position == 0 || !value) return false; // Return false if it's not a valid position
// Set position via device driver
int16_t addr=value>>3;
int16_t subaddr=(value>>1) & 0x03;
bool active=value & 0x01;
_previousPosition = _turntableData.position;
_turntableData.position = position;
DCC::setAccessory(addr, subaddr, active);
#else
(void)position;
#endif
return true;
}
#endif

243
Turntables.h Normal file
View File

@@ -0,0 +1,243 @@
/*
* © 2023 Peter Cole
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TURNTABLES_H
#define TURNTABLES_H
#include <Arduino.h>
#include "IODevice.h"
#include "StringFormatter.h"
// No turntable support without HAL
#ifndef IO_NO_HAL
// Turntable type definitions
// EXTT = EX-Turntable
// DCC = DCC accessory turntables - to be added later
enum {
TURNTABLE_EXTT = 0,
TURNTABLE_DCC = 1,
};
/*************************************************************************************
* Turntable positions.
*
*************************************************************************************/
struct TurntablePosition {
uint8_t index;
uint16_t data;
uint16_t angle;
TurntablePosition* next;
TurntablePosition(uint8_t idx, uint16_t value, uint16_t angle) : index(idx), data(value), angle(angle), next(nullptr) {}
};
class TurntablePositionList {
public:
TurntablePositionList() : head(nullptr) {}
void insert(uint8_t idx, uint16_t value, uint16_t angle) {
TurntablePosition* newPosition = new TurntablePosition(idx, value, angle);
if(!head) {
head = newPosition;
} else {
newPosition->next = head;
head = newPosition;
}
}
TurntablePosition* getHead() {
return head;
}
private:
TurntablePosition* head;
};
/*************************************************************************************
* Turntable - Base class for turntables.
*
*************************************************************************************/
class Turntable {
protected:
/*
* Object data
*/
// Data common to all turntable types
struct TurntableData {
union {
struct {
bool hidden : 1;
bool turntableType : 1;
uint8_t position : 6; // Allows up to 63 positions including 0/home
};
uint8_t flags;
};
uint16_t id;
} _turntableData;
// Pointer to next turntable object
Turntable *_nextTurntable = 0;
// Linked list for positions
TurntablePositionList _turntablePositions;
// Store the previous position to allow checking for changes
uint8_t _previousPosition = 0;
// Store the current state of the turntable
bool _isMoving = false;
/*
* Constructor
*/
Turntable(uint16_t id, uint8_t turntableType) {
_turntableData.id = id;
_turntableData.turntableType = turntableType;
_turntableData.hidden = false;
_turntableData.position = 0;
add(this);
}
/*
* Static data
*/
static Turntable *_firstTurntable;
static int _turntablelistHash;
/*
* Virtual functions
*/
virtual bool setPositionInternal(uint8_t position, uint8_t activity) = 0;
/*
* Static functions
*/
static void add(Turntable *tto);
public:
static Turntable *get(uint16_t id);
static Turntable *getByVpin(VPIN vpin);
/*
* Static data
*/
static int turntablelistHash;
/*
* Public base class functions
*/
inline uint8_t getPosition() { return _turntableData.position; }
inline bool isHidden() { return _turntableData.hidden; }
inline void setHidden(bool h) {_turntableData.hidden=h; }
inline bool isType(uint8_t type) { return _turntableData.turntableType == type; }
inline bool isEXTT() const { return _turntableData.turntableType == TURNTABLE_EXTT; }
inline uint16_t getId() { return _turntableData.id; }
inline Turntable *next() { return _nextTurntable; }
void printState(Print *stream);
void addPosition(uint8_t idx, uint16_t value, uint16_t angle);
uint16_t getPositionValue(uint8_t position);
uint16_t getPositionAngle(uint8_t position);
uint8_t getPositionCount();
bool isMoving() { return _isMoving; }
void setMoving(bool moving) { _isMoving=moving; }
/*
* Virtual functions
*/
virtual void print(Print *stream) {
(void)stream; // suppress compiler warnings
}
virtual ~Turntable() {} // Destructor
/*
* Public static functions
*/
inline static bool exists(uint16_t id) { return get(id) != 0; }
static bool setPosition(uint16_t id, uint8_t position, uint8_t activity=0);
static uint8_t getPosition(uint16_t id);
static bool ttMoving(uint16_t id);
inline static Turntable *first() { return _firstTurntable; }
static bool printAll(Print *stream) {
bool gotOne = false;
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
if (!tto->isHidden()) {
gotOne = true;
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
}
return gotOne;
}
};
/*************************************************************************************
* EXTTTurntable - EX-Turntable device.
*
*************************************************************************************/
class EXTTTurntable : public Turntable {
private:
// EXTTTurntableData contains device specific data
struct EXTTTurntableData {
VPIN vpin;
} _exttTurntableData;
// Constructor
EXTTTurntable(uint16_t id, VPIN vpin);
public:
// Create function
static Turntable *create(uint16_t id, VPIN vpin);
void print(Print *stream) override;
VPIN getVpin() const { return _exttTurntableData.vpin; }
protected:
// EX-Turntable specific code for setting position
bool setPositionInternal(uint8_t position, uint8_t activity) override;
};
/*************************************************************************************
* DCCTurntable - DCC accessory Turntable device.
*
*************************************************************************************/
class DCCTurntable : public Turntable {
private:
// Constructor
DCCTurntable(uint16_t id);
public:
// Create function
static Turntable *create(uint16_t id);
void print(Print *stream) override;
protected:
// DCC specific code for setting position
bool setPositionInternal(uint8_t position, uint8_t activity=0) override;
};
#endif
#endif

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -52,10 +53,32 @@ Stream * WifiInterface::wifiStream;
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial3
#endif
#if defined(ARDUINO_ARCH_STM32)
// Handle serial ports availability on STM32 for variants!
// #undef NUM_SERIAL
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial6
#elif defined(ARDUINO_NUCLEO_F446RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial3
#define SERIAL3 Serial5
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else
#warning This variant of Nucleo not yet explicitly supported
#endif
#endif
#ifndef NUM_SERIAL
#define NUM_SERIAL 1
#define SERIAL1 Serial1
#endif
bool WifiInterface::setup(long serial_link_speed,
@@ -63,7 +86,8 @@ bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel) {
const byte channel,
const bool forceAP) {
wifiSerialState wifiUp = WIFI_NOAT;
@@ -75,27 +99,34 @@ bool WifiInterface::setup(long serial_link_speed,
(void) hostname;
(void) port;
(void) channel;
(void) forceAP;
#endif
// See if the WiFi is attached to the first serial port
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
SERIAL1.begin(serial_link_speed);
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
#endif
// Other serials are tried, depending on hardware.
// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!)
#if defined(ARDUINO_AVR_MEGA2560)
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
}
#endif
#endif
// We guess here that in all architctures that have a Serial3
// we can use it for our purpose.
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
SERIAL3.begin(serial_link_speed);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
}
#endif
@@ -113,7 +144,7 @@ bool WifiInterface::setup(long serial_link_speed,
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
const FSH* hostname, int port, byte channel, bool forceAP) {
wifiSerialState wifiState;
static uint8_t ntry = 0;
ntry++;
@@ -122,20 +153,21 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel);
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
if (wifiState == WIFI_NOAT) {
DIAG(F("++ Wifi Setup NO AT ++"));
return wifiState;
LCD(4, F("WiFi no AT chip"));
return wifiState;
}
if (wifiState == WIFI_CONNECTED) {
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, true);
checkForOK(200, true);
DIAG(F("WiFi CONNECTED"));
// LCD already shows IP
} else {
LCD(4,F("WiFi DISCON."));
}
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
return wifiState;
}
@@ -145,7 +177,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
const FSH* hostname, int port, byte channel, bool forceAP) {
bool ipOK = false;
bool oldCmd = false;
@@ -168,7 +200,23 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
checkForOK(2000, true, false); // Makes this visible on the console
if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0-xxx";
for (int i=0; i<11;i++) {
while(!wifiStream->available());
version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]);
}
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true;
}
}
checkForOK(2000, true, false);
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
@@ -198,7 +246,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
} else {
} else if (!forceAP) {
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
@@ -258,14 +306,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
i=0;
do {
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
if (!forceAP) {
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
oldCmd ? "" : "_CUR", SSid, password, channel);
}
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
if (i >= 2)

View File

@@ -1,6 +1,7 @@
/*
* © 2020-2021 Chris Harlow
* © 2020, Harald Barth.
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -36,17 +37,18 @@ public:
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel);
const byte channel,
const bool forceAP);
static void loop();
static void ATCommand(HardwareSerial * stream,const byte *command);
private:
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel);
const FSH *hostname, int port, byte channel, bool forceAP);
static Stream *wifiStream;
static DCCEXParser parser;
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel);
const FSH *hostname, int port, byte channel, bool forceAP);
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
static bool connected;

View File

@@ -1,9 +1,10 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie
* © 2020-2021 Harald Barth
* © 2020-2023 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki
*
* This file is part of CommandStation-EX
*
@@ -27,6 +28,16 @@ The configuration file for DCC-EX Command Station
**********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// If you want to add your own motor driver definition(s), add them here
// For example MY_SHIELD with display name "MINE":
// (remove comment start and end marker if you want to edit and use that)
/*
#define MY_SHIELD F("MINE"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5)
*/
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
@@ -34,19 +45,34 @@ The configuration file for DCC-EX Command Station
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h
//
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
// |
// +-----------------------v
//
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
//
/////////////////////////////////////////////////////////////////////////////////////
//
// If you want to restrict the maximum current LOWER than what your
// motor shield can provide, you can do that here. For example if you
// have a motor shield that can provide 5A and your power supply can
// only provide 2.5A then you should restict the maximum current to
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
// it would be 2250, adjust the number according to your PS. If your
// PS has a higher rating than your motor shield you do not need this.
// You can use this as well if you are cautious and your trains do not
// need full current.
// #define MAX_CURRENT 2250
//
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
@@ -98,6 +124,11 @@ The configuration file for DCC-EX Command Station
// this line exists or not. If you need to use an alternate channel (we recommend
// using only 1,6, or 11) you may change it here.
#define WIFI_CHANNEL 1
//
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
// true. Otherwise it is assumed that you'd like to connect to an existing network
// with that SSID.
#define WIFI_FORCE_AP false
/////////////////////////////////////////////////////////////////////////////////////
//
@@ -125,10 +156,10 @@ The configuration file for DCC-EX Command Station
// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows
// #define LCD_DRIVER 0x27,16,2
//OR define OLED_DRIVER width,height in pixels (address auto detected)
//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied)
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
// #define OLED_DRIVER 128,32
// #define OLED_DRIVER 0x3c,128,32
// Define scroll mode as 0, 1 or 2
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
@@ -136,12 +167,20 @@ The configuration file for DCC-EX Command Station
// * #define SCROLLMODE 2 is by row (move up 1 row at a time).
#define SCROLLMODE 1
// In order to avoid wasting memory the current scroll buffer is limited
// to 8 lines. Some users wishing to display additional information
// such as TrackManager power states have requested additional rows aware
// of the warning that this will take extra RAM. if you wish to include additional rows
// uncomment the following #define and set the number of lines you need.
//#define MAX_CHARACTER_ROWS 12
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// and want to use the EXRAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work.
//
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
@@ -149,6 +188,17 @@ The configuration file for DCC-EX Command Station
//
// #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE PROG
//
// If you do not need programming capability, you can disable all programming related
// commands. You might want to do that if you are using an Arduino UNO and still want
// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both.
//
// Note this disables all programming functionality, including EXRAIL.
//
// #define DISABLE_PROG
/////////////////////////////////////////////////////////////////////////////////////
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
// is 127 and the first long address is 128. There are manufacturers which have
@@ -224,8 +274,21 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// BOOSTER PIN INPUT ON ESP32
// On ESP32 you have the possibility to define a pin as booster input
// Arduio pin D2 is GPIO 26 on ESPDuino32
//
//#define BOOSTER_INPUT 26
// SABERTOOTH
//
// This is a very special option and only useful if you happen to have a
// sabertooth motor controller from dimension engineering configured to
// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX)
// and GPIO16 (RX, currently unused).
// The number defined is the DCC address for which speed controls are sent
// to the sabertooth controller _as_well_. Default: Undefined.
//
//#define SABERTOOTH 1
// FastClock Enabler
// To build the FastClock code into the CS please uncomment the line below
//#define USEFASTCLOCK
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -144,10 +144,9 @@
#define DISABLE_EEPROM
#endif
// STM32 support for native I2C is awaiting development
#ifndef I2C_USE_WIRE
#define I2C_USE_WIRE
#endif
// #ifndef I2C_USE_WIRE
// #define I2C_USE_WIRE
// #endif
/* TODO when ready
#elif defined(ARDUINO_ARCH_RP2040)
@@ -183,6 +182,15 @@
#define WIFI_ON false
#endif
#ifndef WIFI_FORCE_AP
#define WIFI_FORCE_AP false
#else
#if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false
#else
#error WIFI_FORCE_AP needs to be true or false
#endif
#endif
#if ENABLE_ETHERNET
#if defined(HAS_ENOUGH_MEMORY)
#define ETHERNET_ON true
@@ -205,8 +213,21 @@
//
#define WIFI_SERIAL_LINK_SPEED 115200
////////////////////////////////////////////////////////////////////////////////
//
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
//
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#if defined(DISABLE_DIAG) && defined(DISABLE_EEPROM) && defined(DISABLE_PROG)
#warning you have sacrificed DIAG for HAL
#else
#define IO_NO_HAL
#endif
#endif
#if __has_include ( "myAutomation.h")
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
#define EXRAIL_ACTIVE
#else
#define EXRAIL_WARNING

View File

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

540
installer.ps1 Normal file
View File

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

View File

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

View File

@@ -23,7 +23,9 @@
*
*/
// This is the startup sequence, AKA SEQUENCE(0)
// This is the startup sequence,
AUTOSTART
POWERON // turn on track power
SENDLOCO(3,1) // send loco 3 off along route 1
SENDLOCO(10,2) // send loco 10 off along route 2
DONE // This just ends the startup thread, leaving 2 others running.

465
myHal.cpp.txt Normal file
View File

@@ -0,0 +1,465 @@
#include "defines.h"
#include "IODevice.h"
#ifndef IO_NO_HAL
#include "IO_VL53L0X.h"
#include "IO_HCSR04.h"
#include "Sensors.h"
#include "Turnouts.h"
#include "IO_DFPlayer.h"
//#include "IO_Wire.h"
#include "IO_AnalogueInputs.h"
#if __has_include("IO_Servo.h")
#include "IO_Servo.h"
#include "IO_PCA9685pwm.h"
#endif
#include "IO_HALDisplay.h"
#include "LiquidCrystal_I2C.h"
#if __has_include("IO_CMRI.h")
#include "IO_CMRI.h"
#endif
//#include "IO_ExampleSerial.h"
//#include "IO_EXFastclock.h"
//#include "IO_EXTurntable.h"
#if __has_include("IO_ExternalEEPROM.h")
#include "IO_ExternalEEPROM.h"
#endif
#if __has_include("IO_Network.h")
#include "IO_Network.h"
#include "Net_RF24.h"
#include "Net_ENC28J60.h"
#include "Net_Ethernet.h"
#define NETWORK_PRESENT
#endif
#include "IO_TouchKeypad.h"
#define WIRE_TEST 0
#define TESTHARNESS 1
#define I2C_STRESS_TEST 0
#define I2C_SETCLOCK 0
#include "DCC.h"
#if 0 // Long Strings
#define s10 "0123456789"
#define s100 s10 s10 s10 s10 s10 s10 s10 s10 s10 s10
#define s1k s100 s100 s100 s100 s100 s100 s100 s100 s100 s100
#define s10k s1k s1k s1k s1k s1k s1k s1k s1k s1k s1k
#define s32k s10k s10k s10k s1k s1k
volatile const char PROGMEM ss1[] = s32k;
#endif
#if TESTHARNESS
// Function to be invoked by test harness
void myTest() {
// DIAG(F("VL53L0X #1 Test: dist=%d signal=%d ambient=%d value=%d"),
// IODevice::readAnalogue(5000),
// IODevice::readAnalogue(5001),
// IODevice::readAnalogue(5002),
// IODevice::read(5000));
// DIAG(F("VL53L0X #2 Test: dist=%d signal=%d ambient=%d value=%d"),
// IODevice::readAnalogue(5003),
// IODevice::readAnalogue(5004),
// IODevice::readAnalogue(5005),
// IODevice::read(5003));
// DIAG(F("HCSR04 Test: dist=%d value=%d"),
// IODevice::readAnalogue(2000),
// IODevice::read(2000));
// DIAG(F("ADS111x Test: %d %d %d %d %d"),
// IODevice::readAnalogue(4500),
// IODevice::readAnalogue(4501),
// IODevice::readAnalogue(4502),
// IODevice::readAnalogue(4503),
// IODevice::readAnalogue(A5)
// );
// DIAG(F("RF24 Test: 4000:%d 4002:%d"),
// IODevice::read(4000),
// IODevice::read(4002)
// );
DIAG(F("EXPANDER: 2212:%d 2213:%d 2214:%d"),
IODevice::readAnalogue(2212),
IODevice::readAnalogue(2213),
IODevice::readAnalogue(2214));
}
#endif
#if I2C_STRESS_TEST
static bool initialised = false;
static uint8_t lastStatus = 0;
static const int nRBs = 3; // request blocks concurrently
static const int I2cTestPeriod = 1; // milliseconds
static I2CAddress testDevice = {SubBus_6, 0x27};
static I2CRB rb[nRBs];
static uint8_t readBuffer[nRBs*32]; // nRB x 32-byte input buffer
static uint8_t writeBuffer[nRBs]; // nRB x 1-byte output buffer
static unsigned long count = 0;
static unsigned long errors = 0;
static unsigned long lastOutput = millis();
void I2CTest() {
if (!initialised) {
// I2C Loading for stress test.
// Write value then read back 32 times
for (int i=0; i<nRBs; i++) {
writeBuffer[i] = (0xc5 ^ i ^ i<<3 ^ i<<6) & ~0x08; // bit corresponding to 08 is hard-wired low
rb[i].setRequestParams(testDevice, &readBuffer[i*32], 32,
&writeBuffer[i], 1);
I2CManager.queueRequest(&rb[i]);
}
initialised = true;
}
for (int i=0; i<nRBs; i++) {
if (!rb[i].isBusy()) {
count++;
uint8_t status = rb[i].status;
if (status != lastStatus) {
DIAG(F("I2CTest: status=%d (%S)"),
(int)status, I2CManager.getErrorMessage(status));
lastStatus = status;
}
if (status == I2C_STATUS_OK) {
bool diff = false;
// Check contents of response
for (uint8_t j=0; j<32; j++) {
if (readBuffer[i*32+j] != writeBuffer[i]) {
DIAG(F("I2CTest: Received message mismatch, sent %2x rcvd %2x"),
writeBuffer[i], readBuffer[i*32+j]);
diff = true;
}
}
if (diff) errors++;
} else
errors++;
I2CManager.queueRequest(&rb[i]);
}
}
if (millis() - lastOutput > 60000) { // 1 minute
DIAG(F("I2CTest: Count=%l Errors=%l"), count, errors);
count = errors = 0;
lastOutput = millis();
}
}
#endif
void updateLocoScreen() {
for (int i=0; i<8; i++) {
if (DCC::speedTable[i].loco > 0) {
int speed = DCC::speedTable[i].speedCode;
char direction = (speed & 0x80) ? 'R' : 'F';
speed = speed & 0x7f;
if (speed > 0) speed = speed - 1;
SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
speed, direction);
}
}
}
void updateTime() {
uint8_t buffer[20];
I2CAddress rtc = {SubBus_1, 0x68}; // Real-time clock I2C address
buffer[0] = 0;
// Set time - only needs to be done once if battery is ok.
static bool timeSet = false;
if (!timeSet) {
// I2CManager.read(rtc, buffer+1, sizeof(buffer)-1);
// uint8_t year = 23; // 2023
// uint8_t day = 2; // tuesday
// uint8_t date = 21; // 21st
// uint8_t month = 2; // feb
// uint8_t hours = 23; // xx:
// uint8_t minutes = 25; // :xx
// buffer[1] = 0; // seconds
// buffer[2] = ((minutes / 10) << 4) | (minutes % 10);
// buffer[3] = ((hours / 10) << 4) | (hours % 10);
// buffer[4] = day;
// buffer[5] = ((date/10) << 4) + date%10; // 24th
// buffer[6] = ((month/10) << 4) + month%10; // feb
// buffer[7] = ((year/10) << 4) + year%10; // xx23
// for (uint8_t i=8; i<sizeof(buffer); i++) buffer[i] = 0;
// I2CManager.write(rtc, buffer, sizeof(buffer));
timeSet = true;
}
uint8_t status = I2CManager.read(rtc, buffer+1, sizeof(buffer)-1, 1, 0);
if (status == I2C_STATUS_OK) {
uint8_t seconds10 = buffer[1] >> 4;
uint8_t seconds1 = buffer[1] & 0xf;
uint8_t minutes10 = buffer[2] >> 4;
uint8_t minutes1 = buffer[2] & 0xf;
uint8_t hours10 = buffer[3] >> 4;
uint8_t hours1 = buffer[3] & 0xf;
SCREEN(10, 0, F("Departures %d%d:%d%d:%d%d"),
hours10, hours1, minutes10, minutes1, seconds10, seconds1);
}
}
void showCharacterSet() {
if (millis() < 3000) return;
const uint8_t lineLen = 20;
char buffer[lineLen+1];
static uint8_t nextChar = 0x20;
for (uint8_t row=0; row<8; row+=1) {
for (uint8_t col=0; col<lineLen; col++) {
buffer[col] = nextChar++;
buffer[++col] = ' ';
if (nextChar == 0) nextChar = 0x20; // check for wrap-around
}
buffer[lineLen] = '\0';
SCREEN(3, row, F("%s"), buffer);
}
}
#if defined(ARDUINO_NUCLEO_F446RE)
HardwareSerial Serial3(PC11, PC10);
#endif
// HAL device initialisation
void halSetup() {
I2CManager.setTimeout(500); // microseconds
I2CManager.forceClock(400000);
HALDisplay<OLED>::create(10, {SubBus_5, 0x3c}, 132, 64); // SH1106
// UserAddin::create(updateLocoScreen, 1000);
// UserAddin::create(showCharacterSet, 5000);
// UserAddin::create(updateTime, 1000);
HALDisplay<OLED>::create(10, {SubBus_4, 0x3c}, 128, 32);
HALDisplay<OLED>::create(10, {SubBus_7, 0x3c}, 128, 32);
//HALDisplay<LiquidCrystal_I2C>::create(10, {SubBus_4, 0x27}, 20, 4);
// Draw double boxes with X O O X inside.
// SCREEN(3, 2, F("\xc9\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xbb"));
// SCREEN(3, 3, F("\xba X \xba O \xba O \xba O \xba X \xba"));
// SCREEN(3, 4, F("\xcc\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xb9"));
// SCREEN(3, 5, F("\xba X \xba O \xba O \xba O \xba X \xba"));
// SCREEN(3, 6, F("\xc8\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xbc"));
// Draw single boxes with X O O X inside.
// SCREEN(3, 0, F("Summary Data:"));
// SCREEN(3, 1, F("\xda\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xbf"));
// SCREEN(3, 2, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
// SCREEN(3, 3, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
// SCREEN(3, 4, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
// SCREEN(3, 5, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
// SCREEN(3, 6, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
// SCREEN(3, 7, F("\xc0\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xd9"));
// Blocks of different greyness
// SCREEN(3, 0, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
// SCREEN(3, 1, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
// SCREEN(3, 2, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
// DCCEX logo
// SCREEN(3, 1, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
// SCREEN(3, 2, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
// SCREEN(3, 3, F("\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0\xb0\x20\x20\x20\xb0\x20"));
// SCREEN(3, 4, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
// SCREEN(3, 5, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
// SCREEN(3, 7, F("\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"));
#if 0
// List versions of devices that respond to the version request
for (uint8_t address = 8; address<0x78; address++) {
uint8_t buffer[3];
uint8_t status = I2CManager.read(0x7c, buffer, sizeof(buffer), 1, address);
if (status == I2C_STATUS_OK) {
uint16_t manufacturer = ((uint16_t)buffer[0] << 4 ) | (buffer[1] >> 4);
uint16_t deviceID = ((uint16_t)(buffer[1] & 0x0f) << 5) | (buffer[2] >> 3);
uint16_t dieRevision = buffer[2] & 0x1f;
DIAG(F("Addr %s version: %x %x %x"), address.toString(), manufacturer, deviceID, dieRevision);
}
}
#endif
#if I2C_STRESS_TEST
UserAddin::create(I2CTest, I2cTestPeriod);
#endif
#if WIRE_TEST
// Test of Wire-I2CManager interface
Wire.begin();
Wire.setClock(400000);
Wire.beginTransmission(0x23);
Wire.print("Hello");
uint8_t status = Wire.endTransmission();
if (status==0) DIAG(F("Wire: device Found on 0x23"));
Wire.beginTransmission(0x23);
Wire.write(0xde);
Wire.endTransmission(false); // don't send stop
Wire.requestFrom(0x23, 1);
if (Wire.available()) {
DIAG(F("Wire: value=x%x"), Wire.read());
}
uint8_t st = I2CManager.write(0x33, 0, 0);
DIAG(F("I2CManager 0x33 st=%d \"%S\""), st,
I2CManager.getErrorMessage(st));
#endif
#if I2C_SETCLOCK
// Test I2C clock changes
// Set up two I2C request blocks
I2CRB rb1, rb2;
uint8_t readBuff[32];
rb1.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
rb2.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
// First set clock to 400kHz and then issue requests
I2CManager.forceClock(400000);
I2CManager.queueRequest(&rb1);
I2CManager.queueRequest(&rb2);
// Wait a little to allow the first transaction to start
delayMicroseconds(2);
// ... then request a clock speed change
I2CManager.forceClock(100000);
DIAG(F("I2CClock: rb1 status=%d"), rb1.wait());
DIAG(F("I2CClock: rb2 status=%d"), rb2.wait());
// Reset clock speed
I2CManager.forceClock(400000);
#endif
EXIOExpander::create(2200, 18, {SubBus_0, 0x65});
//UserAddin::create(myTest, 1000);
// ServoTurnout::create(2200, 2200, 400, 200, 0);
// ServoTurnout::create(2200, 2200, 400, 200, 0);
TouchKeypad::create(2300, 16, 25, 24);
// GPIO
PCF8574::create(800, 8, {SubBus_1, 0x23});
//PCF8574::create(808, 8, {SubBus_2, 0x27});
PCF8574::create(65000, 8, 0x27);
MCP23017::create(164,16,{SubBus_3, 0x20});
//MCP23017::create(180,16,{SubBus_0, 0x27});
Sensor::create(170, 170, 1); // Hall effect, enable pullup.
Sensor::create(171, 171, 1);
// PWM (LEDs and Servos)
// For servos, use default 50Hz pulses.
PCA9685::create(100, 16, {SubBus_1, 0x41});
// For LEDs, use 1kHz pulses.
PCA9685::create(116, 16, {SubBus_1, 0x40}, 1000);
// 4-pin Analogue Input Module
//ADS111x::create(4500, 4, 0x48);
// Laser Time-Of-Flight Sensors
VL53L0X::create(5000, 3, {SubBus_0, 0x60}, 300, 310, 46);
//VL53L0X::create(5003, 3, {SubBus_6, 0x61}, 300, 310, 47);
Sensor::create(5000, 5000, 0);
Sensor::create(5003, 5003, 0);
// Monitor reset digital on first TOF
//Sensor::create(46,46,0);
// // External 24C256 EEPROM (256kBytes) on I2C address 0x50.
// ExternalEEPROM::create({SubBus_0, 0x50}, 256);
// Play up to 10 sounds on pins 10000-10009. Player is connected to Serial1 or Serial2.
#if defined(HAVE_HWSERIAL1) && !defined(ARDUINO_ARCH_STM32)
DFPlayer::create(10000, 14, Serial1);
#elif defined(ARDUINO_ARCH_STM32)
DFPlayer::create(10000, 10, Serial3); // Pins PC11 (RX) and PC10 (TX)
#endif
// Ultrasound echo device
HCSR04::create(2000, 32, 33, 80, 85 /*, HCSR04::LOOP */);
Sensor::create(2000, 2000, 0);
#if __has_include("IO_CMRI.h")
CMRIbus::create(0, Serial2, 115200, 50, 40); // 50ms cycle, pin 40 for DE/!RE pins
CMRInode::create(25000, 72, 0, 0, 'M'); // SMINI address 0
for (int pin=0; pin<24; pin++) {
Sensor::create(25000+pin, 25000+pin, 0);
}
#endif
//CMRInode::create(25072, 72, 0, 13, 'M'); // SMINI address 13
//CMRInode::create(25144, 288, 0, 14, 'C', 144, 144); // CPNODE address 14
#ifdef NETWORK_PRESENT
// Define remote pins to be used. The range of remote pins is like a common data area shared
// between all nodes.
// For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs
// the write operation on the device VPIN that is local to that node.
// For inputs, the state of remote input VPIN is read on the node where it is connected, and then
// sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and
// also periodically if no changes.
//
// Each definition is a triple of remote node, remote pin, indexed by relative pin. Up to 224 rpins can
// be configured (per node). This is to fit into a 32-byte packet.
REMOTEPINS rpins[] = {
{30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input
{30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input
{30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output
{30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output
{30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin
{30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin
{30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin
{30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin
{30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24
{30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25
{30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26
{30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27
{30,1000,RPIN_OUT}, //4012 Node 30, DFPlayer playing flag (when read) / Song selector (when written)
{30,5000,RPIN_IN}, //4013 Node 30, VL53L0X detect pin
{30,VPIN_NONE,0}, //4014 Node 30, spare
{30,VPIN_NONE,0}, //4015 Node 30, spare
{31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input
{31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input
{31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output
{31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output
{31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin
{31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin
{31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin
{31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin
{31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24
{31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25
{31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26
{31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27
{31,3,RPIN_IN}, //4028 Node 31, Arduino pin D3
{31,VPIN_NONE,0}, //4029 Node 31, spare
{31,VPIN_NONE,0}, //4030 Node 31, spare
{31,VPIN_NONE,0} //4031 Node 31, spare
};
// FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
// Net_RF24 *rf24Driver = new Net_RF24(48, 49);
// Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, rf24Driver);
#if NODE==30
//Net_ENC28J60 *encDriver = new Net_ENC28J60(49);
//Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
#elif NODE==31
Net_ENC28J60 *encDriver = new Net_ENC28J60(53);
Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
#else
Net_Ethernet *etherDriver = new Net_Ethernet();
Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, etherDriver);
#endif
for (int i=0; i<=32; i++)
Sensor::create(4000+i, 4000+i, 0);
#endif
#ifdef ARDUINO_ARCH_STM32
//PCF8574::create(1900, 8, 0x27);
Sensor::create(1900,100,1);
Sensor::create(1901,101,1);
#endif
}
#endif // IO_NO_HAL

View File

@@ -17,11 +17,14 @@
// Include devices you need.
#include "IODevice.h"
#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_HALDisplay.h" // Auxiliary display devices (LCD/OLED)
//#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_TouchKeypad.h // Touch keypad with 16 keys
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//#include "IO_EXFastClock.h" // FastClock driver
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.
@@ -31,6 +34,61 @@
void halSetup() {
//=======================================================================
// The following directives define auxiliary display devices.
// These can be defined in addition to the system display (display
// number 0) that is defined in config.h.
// A write to a line which is beyond the length of the screen will overwrite
// the bottom line, unless the line number is 255 in which case the
// screen contents will scroll up before the text is written to the
// bottom line.
//=======================================================================
//
// Create a 128x32 OLED display device as display number 1
// (line 0 is written by EX-RAIL 'SCREEN(1, 0, "text")').
//HALDisplay<OLED>::create(1, 0x3d, 128, 32);
// Create a 20x4 LCD display device as display number 2
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
// HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
//=======================================================================
// User Add-ins
//=======================================================================
// User add-ins can be created when you want to do something that
// can't be done in EX-RAIL but does not merit a HAL driver. The
// user add-in is a C++ function that is executed periodically by the
// HAL subsystem.
// Example: The function will be executed once per second and will display,
// on screen #3, the first eight entries (assuming an 8-line display)
// from the loco speed table.
// Put the following block of code in myHal.cpp OUTSIDE of the
// halSetup() function:
//
// void updateLocoScreen() {
// for (int i=0; i<8; i++) {
// if (DCC::speedTable[i].loco > 0) {
// int speed = DCC::speedTable[i].speedCode;
// char direction = (speed & 0x80) ? 'R' : 'F';
// speed = speed & 0x7f;
// if (speed > 0) speed = speed - 1;
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
// speed, direction);
// }
// }
// }
//
// Put the following line INSIDE the halSetup() function:
//
// UserAddin::create(updateLocoScreen, 1000);
//
//=======================================================================
// The following directive defines a PCA9685 PWM Servo driver module.
//=======================================================================
@@ -156,12 +214,12 @@ void halSetup() {
// With these parameters, up to 10 files may be played on pins 10000-10009.
// Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001)
// for second file, etc. Play may also be initiated by writing an analogue
// value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file.
// SERVO(10000,23,30) will do the same thing, as well as setting the volume to
// value to the first pin, e.g. ANOUT(10000,23,0,0) will play the 23rd mp3 file.
// ANOUT(10000,23,30,0) will do the same thing, as well as setting the volume to
// 30 (maximum value).
// Play is stopped by RESET(10000) (or any other allocated VPIN).
// Volume may also be set by writing an analogue value to the second pin for the player,
// e.g. SERVO(10001,30,0) sets volume to maximum (30).
// e.g. ANOUT(10001,30,0,0) sets volume to maximum (30).
// The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the
// following line when the player is no longer busy.
// E.g.
@@ -170,12 +228,27 @@ void halSetup() {
// SET(10003) // Play fourth MP3 file
// LCD(4, "Playing") // Display message on LCD/OLED
// WAITFOR(10003) // Wait for playing to finish
// LCD(4, " ") // Clear LCD/OLED line
// LCD(4, "") // Clear LCD/OLED line
// FOLLOW(1) // Go back to start
// DFPlayer::create(10000, 10, Serial1);
//=======================================================================
// 16-pad capacitative touch key pad based on TP229 IC.
//=======================================================================
// Parameters below:
// 11000 = first VPIN allocated
// 16 = number of VPINs allocated
// 25 = local GPIO pin number for clock signal
// 24 = local GPIO pin number for data signal
//
// Pressing the key pads numbered 1-16 cause each of the nominated digital VPINs
// (11000-11015 in this case) to be activated.
// TouchKeypad::create(11000, 16, 25, 24);
//=======================================================================
// The following directive defines an EX-Turntable turntable instance.
//=======================================================================

View File

@@ -20,18 +20,17 @@ default_envs =
ESP32
Nucleo-F411RE
Nucleo-F446RE
Teensy3.2
Teensy3.5
Teensy3.6
Teensy4.0
Teensy4.1
Teensy3_2
Teensy3_5
Teensy3_6
Teensy4_0
Teensy4_1
src_dir = .
include_dir = .
[env]
build_flags = -Wall -Wextra
monitor_filters = time
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
; monitor_filters = time
[env:samd21-dev-usb]
platform = atmelsam
@@ -53,14 +52,14 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:Arduino M0]
[env:Arduino-M0]
platform = atmelsam
board = mzeroUSB
framework = arduino
lib_deps = ${env.lib_deps}
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
@@ -72,7 +71,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
[env:mega2560-no-HAL]
platform = atmelavr
@@ -84,7 +83,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -DIO_NO_HAL
build_flags = -DIO_NO_HAL
[env:mega2560-I2C-wire]
platform = atmelavr
@@ -108,7 +107,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = ; -DDIAG_LOOPTIMES
build_flags =
[env:mega328]
platform = atmelavr
@@ -173,6 +172,8 @@ board = esp32dev
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F411RE]
platform = ststm32
@@ -188,11 +189,76 @@ platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
[env:Teensy3.2]
; Experimental - no reason this should not work, but not
; tested as yet
;
[env:Nucleo-F401RE]
platform = ststm32
board = nucleo_f401re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
; Commented out by default as the F13ZH has variant files
; but NOT the nucleo_f413zh.json file which needs to be
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F413ZH]
; platform = ststm32
; board = nucleo_f413zh
; framework = arduino
; lib_deps = ${env.lib_deps}
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; Commented out by default as the F446ZE needs variant files
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F446ZE]
; platform = ststm32
; board = nucleo_f446ze
; framework = arduino
; lib_deps = ${env.lib_deps}
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; Commented out by default as the F412ZG needs variant files
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F412ZG]
; platform = ststm32
; board = blah_f412zg
; framework = arduino
; lib_deps = ${env.lib_deps}
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; upload_protocol = stlink
; Experimental - Ethernet work still in progress
;
; [env:Nucleo-F429ZI]
; platform = ststm32
; board = nucleo_f429zi
; framework = arduino
; lib_deps = ${env.lib_deps}
; arduino-libraries/Ethernet @ ^2.0.1
; stm32duino/STM32Ethernet @ ^1.3.0
; stm32duino/STM32duino LwIP @ ^2.1.2
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; upload_protocol = stlink
[env:Teensy3_2]
platform = teensy
board = teensy31
framework = arduino
@@ -200,7 +266,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy3.5]
[env:Teensy3_5]
platform = teensy
board = teensy35
framework = arduino
@@ -208,7 +274,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy3.6]
[env:Teensy3_6]
platform = teensy
board = teensy36
framework = arduino
@@ -216,7 +282,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy4.0]
[env:Teensy4_0]
platform = teensy
board = teensy40
framework = arduino
@@ -224,11 +290,10 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy4.1]
[env:Teensy4_1]
platform = teensy
board = teensy41
framework = arduino
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

117
version.h
View File

@@ -3,8 +3,117 @@
#include "StringFormatter.h"
#define VERSION "4.2.18"
#define VERSION "5.2.3"
// 5.2.3 - Bugfix: Catch stange input to parser
// 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h
// 5.2.1 - Trackmanager rework for simpler structure
// 5.2.0 - ESP32: Autoreverse and booster mode support
// 5.1.21 - EXRAIL invoke multiple ON handlers for same event
// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION
// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
// 5.1.18 - TURNOUTL bugfix
// 5.1.17 - Divide out C for config and D for diag commands
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
// 5.1.14 - Fixed IFTTPOSITION
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
// Added EXRAIL SET_POWER(track, ON/OFF)
// Fixed a problem whereby <1 MAIN> also powered on PROG track
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
// - ARM architecture brakeCanPWM now uses digitalPinHasPWM()
// - STM32F4xx shadowpin extensions to handle pins on ports D, E and F
// 5.1.9 - Fixed IO_PCA9555'h to work with PCA9548 mux, tested OK
// 5.1.8 - STM32Fxx ADCee extension to support ADCs #2 and #3
// 5.1.7 - Fix turntable broadcasts for non-movement activities and <JP> result
// 5.1.6 - STM32F4xx native I2C driver added
// 5.1.5 - Added turntable object and EXRAIL commands
// - <I ...>, <JO ...>, <JP ...> - turntable commands
// - DCC_TURNTABLE, EXTT_TURNTABLE, IFTTPOSITION, ONROTATE, ROTATE, ROTATE_DCC, TT_ADDPOSITION, WAITFORTT EXRAIL
// 5.1.4 - Added ONOVERLOAD & AFTEROVERLOAD to EXRAIL
// 5.1.3 - Make parser more fool proof
// 5.1.2 - Bugfix: ESP32 30ms off time
// 5.1.1 - Check bad AT firmware version
// - Update IO_PCA9555.h reflecting IO_MCP23017.h changes to support PCA9548 mux
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
// 5.0.0 - Make 4.2.69 the 5.0.0 release
// 4.2.69 - Bugfix: Make <!> work in DC mode
// 4.2.68 - Rename track mode OFF to NONE
// 4.2.67 - AVR: Pin specific timer register seting
// - Protect Uno user from choosing DC(X)
// - More Nucleo variant defines
// - GPIO PCA9555 / TCA9555 support
// 4.2.66 - Throttle inrush current by applying PWM to brake pin when
// fault pin goes active
// 4.2.65 - new config WIFI_FORCE_AP option
// 4.2.63 - completely new overcurrent detection
// - ESP32 protect from race in RMT code
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
// - Update IO_EXTurntable.h to remove forced I2C clock speed
// - Show device offline if EX-Turntable not connected
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
// 4.2.58 - Start motordriver as soon as possible but without waveform
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)
// - Optimize analog read STM32
// 4.2.56 - Update IO_RotaryEncoder.h:
// - Improved I2C communication, non-blocking reads
// - Enable sending positions to the encoder from EXRAIL via SERVO()
// 4.2.55 - Optimize analog read for AVR
// 4.2.54 - EX8874 shield in config.example.h
// - Fix: Better warnings for pin number errors
// - Fix: Default roster list possible in Withrottle and <jR>
// - Fix: Pin handling supports pins up to 254
// 4.2.53 - Fix: Fault pin handling made more straight forward
// 4.2.52 - Experimental support for sabertooth motor controller on ESP32
// 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash
// 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check
// 4.2.49 - Exrail SPEED take notice of external direction change
// 4.2.48 - BROADCAST/WITHROTTLE Exrail macros
// 4.2.47 - Correct response to <JA 0>
// 4.2.46 - Support boards with inverted fault pin
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events
// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe
// 4.2.43 - Fix STM32 set right port mode bits for analog
// 4.2.42 - Added EXRAIL TURNOUTL Macro definition
// 4.2.41 - Move HAl startup to ASAP in setup()
// - Fix DNOU8 output pin setup to all LOW
// 4.2.40 - Automatically detect conflicting default I2C devices and disable
// 4.2.39 - DFplayer driver now polls device to detect failures and errors.
// 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included
// 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL;
// - Fix diag display of high VPINs within IODevice class.
// 4.2.36 - do not broadcast a turnout state that has not changed
// - Use A2/A3 for current sensing on ESP32 + Motor Shield
// 4.2.35 - add <z> direct pin manipulation command
// 4.2.34 - Completely fix EX-IOExpander analogue inputs
// 4.2.33 - Fix EX-IOExpander non-working analogue inputs
// 4.2.32 - Fix LCD/Display bugfixes from 4.2.29
// 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!)
// Just add AUTOSTART to the top of your myAutomation.h to restore this function.
// 4.2.30 - Fixes/enhancements to EX-IOExpander device driver.
// 4.2.29 - Bugfix Scroll LCD without empty lines and consistent
// 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode.
// - Update IO_DFPlayer to work with MP3-TF-16P rev3.
// 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2
// 4.2.26 - EX-IOExpander device driver enhancements
// - Enhance I2C error checking
// - Introduce delays to _loop to allow room for other I2C device comms
// - Improve analogue read reliability
// 4.2.25 - Bugfix SAMD21 Exrail odd byte boundary
// 4.2.24 - Bugfix Ethernet shield: Static IP now possible
// 4.2.23 - Bugfix signalpin2 was not set up in shadow port
// 4.2.22 - Implement broadcast of Track Manager changes
// 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver
// 4.2.20 - <JG> & <JI> commands for multi-track gauges
// - Reinstate <c> but remember its a bit useless when TM involved.
// 4.2.19 - Bugfix for analog reading of track current sensor offset.
// 4.2.18 - I2C Multiplexer support through Extended Addresses,
// added for Wire, 4209 and AVR I2C drivers.
// - I2C retries when an operation fails.
@@ -40,6 +149,10 @@
// 4.2.11 Exrail IFLOCO feature added
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
// IO_EXIOExpander.h input speed optimisation
// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock
// <JC> Serial command added for EX-FastClock
// <jC> Broadcast added for EX-FastClock
// IO_EXFastClock.h added for I2C FastClock connection
// 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements