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

Compare commits

...

364 Commits

Author SHA1 Message Date
Asbelos
67c8366512 Fix auto rejoin after prog cmd (needs version n umber!) (#148)
* ack down flank double check

* ack gap properly reported

* zero gap count; tolerate 2 samples per gap

* Fix auto rejoin after prog cmd

Moved more setup out of the BASELINE loop so its not checked every time while waiting for reset counter.
Added REJOIN diag..

* Stable 100mS and off 30mS

* Init powerOff after flag.

Co-authored-by: Harald Barth <haba@kth.se>
2021-05-07 18:24:34 +01:00
Neil McKechnie
ebbe698e51 Committing a SHA 2021-05-06 23:12:48 +00:00
Neil McKechnie
107e9d1d62 Fix turnout handling of EEPROM (#147)
On activation, Turnout code was saving entire EEPROM twice, even if EEPROM save was switched off with the <e> command.  It's now been changed so that only the tStatus byte is updated, and only if the turnout has previously been saved into EEPROM.
2021-05-07 00:12:33 +01:00
LarryD
9b4c374cd4 Committing a SHA 2021-04-27 15:02:30 +00:00
LarryD
d721ed5184 Rename file to all lower case. 2021-04-27 10:02:02 -05:00
LarryD
9073aadab7 Committing a SHA 2021-04-27 15:01:27 +00:00
LarryD
d9a7eeeef3 Rename file to all lower case 2021-04-27 10:01:08 -05:00
Asbelos
1d6e6ec10e Committing a SHA 2021-04-27 14:45:45 +00:00
Asbelos
bded5d3588 3.0.12 Fix Functions >127 (just a bug) (#146)
* Fix Functions >127

* Update version.h

* avoid freds fix

Co-authored-by: Fred <fndecker@gmail.com>
2021-04-27 10:45:26 -04:00
LarryD
87481209ec Committing a SHA 2021-04-27 14:38:50 +00:00
LarryD
dbe682e5ba Add Release v3.1.0 draft content 2021-04-27 09:38:26 -05:00
Fred
83e4e4f6ee Committing a SHA 2021-04-27 14:38:18 +00:00
Fred
45eb7c80b6 Frightrisk hostname fix (#144)
* Fix esp8266 hostname in STA mode by checking for new version of the AT cmd instead of old cmd since some firmware still allows old commands

* Add more old firmware checks and set oldCmd earlier

* increment version number
2021-04-27 10:37:54 -04:00
LarryD
b541614a19 Committing a SHA 2021-04-27 14:35:32 +00:00
LarryD
4756e767cf Add Release Note text to .md file for historical purposes 2021-04-27 09:35:16 -05:00
LarryD
9ef0189ae8 Committing a SHA 2021-04-27 14:30:28 +00:00
LarryD
e866fd1bd7 Delete Release_Notes_v3.10.0.md
Delete Release_Notes file with accidental wrong version v3.10.0.
2021-04-27 09:30:10 -05:00
LarryD
51491ac1e0 Committing a SHA 2021-04-27 14:28:06 +00:00
LarryD
48524b1175 Create initial draft of Release_Notes_v3.0.0.md
Will populate later from GitHub Release section for historical purposes.
2021-04-27 09:27:41 -05:00
LarryD
dc200aab75 Committing a SHA 2021-04-27 14:26:21 +00:00
LarryD
3954e058c7 Create initial version of Release_Notes_v3.1.0 2021-04-27 09:26:03 -05:00
LarryD
5d0da81377 Committing a SHA 2021-04-27 14:23:45 +00:00
LarryD
6a5a8acd17 Add Release Notes Folder to CommandStationEX Repo and Release_Notes_v3.10.0.md file 2021-04-27 09:23:25 -05:00
Fred
c27aa3a2d2 Committing a SHA 2021-04-20 13:31:38 +00:00
Fred
d12714d51e Update config.example.h
Fix spelling of contain
2021-04-20 09:31:17 -04:00
Neil McKechnie
ca7d728b81 Committing a SHA 2021-04-13 23:11:08 +00:00
Neil McKechnie
c4f45ddc36 Update SSD1306Ascii.cpp
Fix handling of clear screen for the nanoEvery and nanoWifi.
2021-04-13 23:13:27 +01:00
Asbelos
b8b9b6d354 Committing a SHA 2021-04-12 08:19:20 +00:00
Asbelos
8197e2bffa Teensy/nanoEvery compiler warnings
No functional change, just avoiding compiler warnings for un used parameters in some architectures.
2021-04-12 09:18:48 +01:00
Harald Barth
813ad7e6a4 Committing a SHA 2021-04-06 20:11:40 +00:00
Harald Barth
a7d0042403 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-04-06 22:10:46 +02:00
Harald Barth
3c30ef3c9d 3.0.11 2021-04-06 22:10:33 +02:00
Harald Barth
2651934a75 Committing a SHA 2021-04-06 20:10:17 +00:00
Harald Barth
30c13190a4 merge 28speedsteps 2021-04-06 22:04:16 +02:00
Harald Barth
0020ec2b71 more keywords 2021-04-06 22:02:20 +02:00
Harald Barth
7a1b363954 needs fixed sized int 2021-04-06 22:00:33 +02:00
Neil McKechnie
a86f0094a6 Committing a SHA 2021-04-03 22:22:38 +00:00
Neil McKechnie
e7e8e84829 Update LiquidCrystal_I2C.cpp
Reinstate write method for LCD.
2021-04-03 23:22:13 +01:00
Neil McKechnie
192e8d9917 Committing a SHA 2021-04-03 10:39:34 +00:00
Neil McKechnie
5e5f994e48 Merge pull request #140 from DCC-EX/neil-i2c2
Add I2C support functions
2021-04-03 11:39:16 +01:00
Neil McKechnie
ee9b195867 Update main.yml 2021-03-31 21:07:18 +01:00
Neil McKechnie
07862ff933 Committing a SHA 2021-03-31 20:00:36 +00:00
Neil McKechnie
119662ddee Update main.yml to use up-to-date pio for builds. 2021-03-31 21:00:07 +01:00
Neil McKechnie
5f375c57c0 Update LCD_OLED.h
Remove newline from DIAG.
2021-03-31 12:24:32 +01:00
Neil McKechnie
43319fd3dd Add I2C support functions
Add new read/write functions to I2CManager class, and modify the LCD, OLED and PWM classes to use them effectively.
2021-03-31 12:19:55 +01:00
Asbelos
b1d3f3200a Committing a SHA 2021-03-30 22:19:48 +00:00
Asbelos
0f3e4576e4 Update version.h
3.0.10 Teensy merge
2021-03-30 23:19:32 +01:00
Asbelos
0f5d1e7a51 Committing a SHA 2021-03-30 22:18:09 +00:00
Asbelos
44ca3bc7b9 Merge pull request #139 from mjs513/Teensy-Revisions
Added support for Teensy 3.2, 3.5, 3.6, 4.0 and 4.1
2021-03-30 23:17:52 +01:00
Mike S
dd97c4ba49 Some additional fixes for typos. 2021-03-30 18:11:22 -04:00
Asbelos
2361704f0d Committing a SHA 2021-03-30 21:20:23 +00:00
Asbelos
a0538ca61b 3.0.9 newlines 2021-03-30 22:01:37 +01:00
Mike S
c70ef3ffaa Final Fix for T4 interrupts 2021-03-30 16:12:47 -04:00
Mike S
f5cdd88854 Cleanup of extra commented lines 2021-03-29 15:06:06 -04:00
Mike S
8839eb293c Update for T3.x to use SREG analogReads.
Still a bunch of commented out code but its a start.
2021-03-29 13:00:56 -04:00
Mike S
136e993418 Changed to Continuous analogReads for Teensy 2021-03-27 07:17:47 -04:00
Mike S
54773297bf Minor changes to Arduino Type List & DCCWaveform 2021-03-26 07:37:17 -04:00
Mike S
8e63c452b2 Fix a few bugs as a result of latest version 2021-03-25 14:02:33 -04:00
Mike S
8141311e66 Revisions to support Teensy 3.x and Teensy 4.x 2021-03-25 13:16:12 -04:00
Asbelos
76c2b5ae91 Committing a SHA 2021-03-25 15:18:44 +00:00
Asbelos
85a2b9231b <* *> wrapped diags
And lots of \n cleanups.
2021-03-25 14:23:38 +00:00
Asbelos
dd2260709d Committing a SHA 2021-03-24 10:37:04 +00:00
Asbelos
c61d8772e3 3.0.7 2021-03-24 10:36:26 +00:00
Asbelos
cfee1057c4 Committing a SHA 2021-03-24 10:02:39 +00:00
Asbelos
a8c9c2f98d ACK MANAGER lazy decoders
Double check lazy decoders that do not ACK when writing.
2021-03-24 09:51:31 +00:00
Fred
f8f80b18ca Committing a SHA 2021-03-23 14:37:31 +00:00
Fred
d7b2cf3d76 Assorted bits (#138)
* LCN

* Prevent deprecated compiler warning

* Implement huge function numbers

* new commands

<! [cab]> forget locos.
<9> ESTOP ALL.
<D RESET> reboot arduino

* Waveform accuracy msg

* Drop post-write verify

* UNUSED_PIN current measure

and callback -2 for cv actions.

* Correct diags

* ESTOP a forget loco

* ESTOP loco on forget

* Avoid compiler warning

* current sensor offset

* Restore <1 JOIN> after prog track operation

* <!> ESTOP <-> FORGET

* Auto current offset detection

* manage current offset and diagnostics

* neater msg at startup

* Add startup message to LCN master

* DCC::setJoinRelayPin

Co-authored-by: Asbelos <asbelos@btinternet.com>
2021-03-23 10:37:05 -04:00
Harald Barth
f556cc5e1c Committing a SHA 2021-03-22 22:49:04 +00:00
Harald Barth
ec4455ae93 remove angles 2021-03-22 23:47:32 +01:00
Fred
180d5f5abb Committing a SHA 2021-03-19 02:48:33 +00:00
Fred
fc3b21e5c5 Update release_notes.md 2021-03-18 22:48:14 -04:00
Fred
2f9d4429bc Committing a SHA 2021-03-19 02:46:48 +00:00
Fred
aaa1eb5385 Update release_notes.md
Add nanoEvery2 changes when it merged to master
2021-03-18 22:46:29 -04:00
Fred
8b3ca6c2ff Committing a SHA 2021-03-17 00:47:12 +00:00
Fred
92ef42b596 Update release_notes.md 2021-03-16 20:46:54 -04:00
Harald Barth
2f860e594c clearer binary arith. 2021-03-16 23:20:30 +01:00
Neil McKechnie
174f8f209c Committing a SHA 2021-03-16 22:04:14 +00:00
Neil McKechnie
42fdf4fed3 Merge pull request #137 from DCC-EX/neil-LCDfix
Ensure loop2() doesn't run during initialisation if there's no display.
2021-03-16 22:03:56 +00:00
Neil McKechnie
1cc147cc98 Ensure loop2() doesn't run during initialisation if there's no display.
Prevent loop2 from doing anything when lcdDisplay not set up.  If there's a display configured but not one attached, then the OLED code will set lcdDisplay to null and no display will be configured or used by the loop() function.  However, because of the way the initialisation code works, loop2() is called during initialisation.
2021-03-16 22:02:39 +00:00
Harald Barth
46d0304ce0 28 speed steps as global debug option 2021-03-16 22:51:33 +01:00
Fred
05b225c352 Committing a SHA 2021-03-16 13:05:28 +00:00
Fred
c9ade73376 Update release_notes.md 2021-03-16 09:05:10 -04:00
Harald Barth
55cdbbbb66 28 speed step prototype 2021-03-16 10:59:14 +01:00
Harald Barth
086336158f Committing a SHA 2021-03-14 21:17:25 +00:00
Harald Barth
f2891ee348 make generates MAC addr compliant with the IEEE standard by setting the Locally Administered bit and clearing the Group Adress bit 2021-03-14 22:15:48 +01:00
Harald Barth
25c2f06574 Committing a SHA 2021-03-12 21:14:36 +00:00
Harald Barth
98071602c3 Bugfix: Transmit packages of size MAX_PACKET_SIZE (5) as well 2021-03-12 22:12:13 +01:00
mstevetodd
d35529e94a Committing a SHA 2021-03-12 14:32:30 +00:00
mstevetodd
9e49167be9 Merge pull request #134 from DCC-EX/EDdriveaway
ED driveaway
2021-03-12 09:31:21 -05:00
Asbelos
cec26c47e2 Catch up with Steves change 2021-03-12 10:38:30 +00:00
Fred
fcd54b3a80 Committing a SHA 2021-03-11 19:54:48 +00:00
Fred
ad4095fb04 Update release_notes.md 2021-03-11 14:54:29 -05:00
Asbelos
a8bd3df992 Committing a SHA 2021-03-11 14:00:43 +00:00
Asbelos
933eab5f2d Ban mixed wifi/ethernet 2021-03-11 13:58:58 +00:00
Asbelos
c51b445e41 unjoin automatically 2021-03-11 13:35:47 +00:00
Neil McKechnie
f2c2e7ecaa Committing a SHA 2021-03-10 17:56:16 +00:00
Neil McKechnie
62b17d4a71 Merge pull request #133 from DCC-EX/neil-updates
Protect minimum memory threshold against interrupts.
2021-03-10 17:55:55 +00:00
Neil McKechnie
0b3c0bfe9e Update freeMemory.cpp
Add explanatory comment.
2021-03-10 10:33:42 +00:00
Neil McKechnie
eb54c78d74 Change initial value for free memory.
Change initial value from 32767 (maximum value of a 16-bit signed integer) to __INT_MAX__ (compiler-defined maximum value for an int).
2021-03-09 23:41:33 +00:00
Neil McKechnie
def6c24bac Remove redundant option for memory monitoring.
Memory monitoring now enabled always.
2021-03-09 23:39:25 +00:00
Neil McKechnie
163dd270e8 Memory monitoring updates
Split update from read value;
Inhibit interrupts when reading (normally done from loop code);
Don't inhibit interrupts when updating (normally done from interupt code);
Make freeMemory() local and ask for inline code generation.
2021-03-09 22:43:41 +00:00
Asbelos
4f7d3a5cfc ED Drive away from prog track featuire 2021-03-09 20:44:44 +00:00
Neil McKechnie
0880507d89 Make memory monitoring non-optional. 2021-03-09 10:38:48 +00:00
Neil McKechnie
62f1c04ee3 Revert "Add optional loop time monitor."
This reverts commit 7a2beda2a9.
2021-03-09 10:30:20 +00:00
Neil McKechnie
7954c85b7d Update freeMemory.cpp 2021-03-09 10:27:38 +00:00
Neil McKechnie
fab05bac79 Update freeMemory.cpp
Inhibit interrupts while updating/reading minimum_free_memory as it is accessed from interrupt handler.
2021-03-09 10:13:04 +00:00
Fred
6866216dfc Committing a SHA 2021-03-09 02:50:24 +00:00
Fred
e67ab2b05f Update version.h 2021-03-08 21:50:11 -05:00
mstevetodd
5d27da58b8 Committing a SHA 2021-03-09 02:46:24 +00:00
mstevetodd
79a318b455 fix initial fn strings, return value of F16-F28 (#132)
* Committing a SHA

* fix initial fn strings, return value of F16-F28
2021-03-08 21:46:08 -05:00
Fred
5f34fc396a Committing a SHA 2021-03-09 02:32:06 +00:00
Fred
c34c93c2cc Update platformio.ini
Remove dependency on DIO2 in PlatformIO.ini
2021-03-08 21:31:48 -05:00
Neil McKechnie
7a2beda2a9 Add optional loop time monitor.
By defining ENABLE_LOOP_MEASUREMENT as true in config.h, the loop measurement will be enabled.  This measures the time between successive executions of the main CS loop to help identify if something is taking too long and holding up the other loop functions.
2021-03-08 15:32:40 +00:00
Asbelos
f3d7851467 Committing a SHA 2021-03-08 13:53:35 +00:00
Asbelos
809b54d9f0 Merge pull request #131 from DCC-EX/neil-freemem
More conservative memory monitoring
2021-03-08 13:53:17 +00:00
Neil McKechnie
609d3d13de Mark minimum_free_memory volatile. 2021-03-08 13:25:13 +00:00
Neil McKechnie
ddc55690f3 More conservative memory monitoring
Add function to maintain a  minimum value seen of free memory.  Add call to it in DCCWaveform interrupt handler (assumed to be the likely worst case for stack usage).  Report this minimum value in main loop.
2021-03-08 13:09:09 +00:00
Asbelos
9562d1a3b9 Committing a SHA 2021-03-08 10:41:15 +00:00
Asbelos
36e38bf861 AYSNC prog cmds from Wifi/Ethernet
prog track commands from wifi/ethernet will no longer block loop while waiting
for ACK
2021-03-08 10:40:32 +00:00
mstevetodd
df4bae365d Committing a SHA 2021-03-07 21:57:19 +00:00
mstevetodd
7706e6560b Update GITHUB_SHA.h 2021-03-07 16:57:05 -05:00
mstevetodd
090ece6e59 Committing a SHA 2021-03-07 21:51:23 +00:00
mstevetodd
5a5702a5b5 Merge branch 'master' into master 2021-03-07 16:51:08 -05:00
Fred
a072f3222b Committing a SHA 2021-03-07 20:58:54 +00:00
Fred
4861e592c7 Nano every2 (#129)
* Start adding back unowifi stuffz

* Uno Wifi compiling

* Fixes for compile arduino unowifi r2

* FlasString and Timers for Uno Wifi

ALL these changes should be portable back to master

* Remove extra timer that was already added

* Changed to EveryTimerB

* Add everytimerb.h

* Cleanup

* Linear address <a> cmd

* Allow lower case keywords

* Add the F define to be on safe side if it is not present in the library core code

* Clean simple Timer interface

Removes overkill files, puts all timer in a single small file. (DCCTimer)

* Timer port

* Timer working

And slow wave command removed

* Correcting non-portables merged from master

* Wave-state machine ( part 11)

* Microtuning waveform

Significant reduction in code paths and call overheads

* Current check cleanup

* Fix no-loco id

Has to handle -1 correctly

* fix wrong format letter

* redo flow through wifisetup again

* version++

* bugfixes wifi setup

* Retry harder for AP mode

* Remove unued if

* DIO2 replacement

Currently for writing signal pins during waveform.

* Drop analogReadFast (see DCCTimer)

AnalogRead speed set in DCCTimer for ease of porting.
Code tidy and diagnostics in MotorDriver

* UNTESTED fast power,brake,fault pins

* Distunguish between in/out of FASTPIN

* minor performance tweaks

* Config comments and example use

* Config/example loading

* IP/PORT on LCD

* Ethernet simulated mac

Plus fixed listening port

* Github SHA

* Committing a SHA

* Fix for nano compile

* Comments and a reliability fix.

* UnoRev2 protection

* PWM pin implementation

* fix wifi setup issue

* Reinstate IP_PORT

* Wifi channel and code cleaninga

* Reduce duplicated F() macros

Compiler isn't as clever as one might expect

* Committing a SHA

* Update config.example.h

Add comment to wifi channel section

* Committing a SHA

* Handle shields with common fault pins (Pololu)

* Committing a SHA

* remove warning

* Committing a SHA

* only do the sha generation on master

* yaml syntax

* Fast SSD1306 OLED driver

Incorporate code from SSD1306Ascii library to speed up OLED screen updates, reduce memory requirements and eliminate some library dependences.

* Fix auto-configure from cold boot.

Add call to Wire.begin().

* Update comment for OLED_DRIVER define.

* Update MotorDrivers.h

Add a motor board definition for using the IBT_2 board for a high current to the main track and keep the Arduino Motor Shield for operating the programming track.

* Committing a SHA

* Fix missing F  in motor drivers

* JOIN relay pin

* Swap Join Relay high/low

* Hide WIFI_CONNECT_TIMEOUT

This is not what the config suggests it is...  The timeout is in the ES and defaults to 15 seconds. Abandoning it early leads to confused setup.

* Enhance OLED/LCD speed

Write one character or position command per loop entry so as not to hold up the loop.  Add support for SH1106 OLED as 132x64 size option.

* Enhance OLED/LCD speed

* Delete comment about OLED on UNO.

* Trim unwanted code

* Handle display types correctly

* Update comments

* Speed up OLED writes

Add new flushDisplay() to end any in-progress I2C transaction.  Previously, an redundant command was sent that ended the in-progress transaction but also sent another complete, but unnecessary, transaction.

* Comments and copyright update

* Reduce RAM and flash requirement a few more bytes.

* Move statics into LCDDisplay class, and reduce RAM.

Some state variables were static in LCDDisplay.write().  Moved to class members.  Also, types of data items like row, column & character position changed to int8_t to save a few bytes of RAM.

* Type lcdCols and lcdRows to unsigned.

Since lcdCols is normally 128, it needs to be uint8_t, not int8_t.

* remove timeout from user config

* faultpin is common only if it exists ; make code prettier

* Rationalisation of SSD1306 driver

Merge SSD1306AsciiWire.cpp into SSD1306Ascii.cpp and rename SSD1306AsciiWire.h as SSD1306Ascii.h.
Merge allFonts.h into System5x7.h and rename as SSD1306font.h.
Move all SSD1306 files into root folder to facilitate compilation in Arduino IDE.

* Fix some font attributes as const.

* Remove unused initialisation sequences for tiny oled screens

* Add m_ to variables

* Bump up I2C speed

Speed was 100kHz (default).  Max for OLEDis 400kHz.

* Revert "Bump up I2C speed"

This reverts commit 1c1168f433.

* Bump up I2C speed

Speed was 100kHz (default). Max for OLEDis 400kHz.

* Drop duplicate DIAG

* ignore mySetup.h files

* Restore uno to default_envs

Restore uno (previously commented out) to default_envs.

* Update objdump.bat

Allows other editors as Notepad is very slow on large files

* Prog Track overload during cv read

* Faster LCD Driver

Extract LCD driver from library;
Trim unused functionality;
Reduce I2C communications to minimum;
Speed up I2C clock to 400kHz.

* Update config.example.h

Add IBT_2_WITH_ARDUINO to example config

* Update config.example.h

* Screen enhancements (#126)

* Add I2CManager to coordinate I2C shared parameters.

* Add use of I2CManager, and experimental scrolling strategies.

New scrolling capability by defining SCROLLMODE in Config.h to 0 (original), 1 (by page) or 2 (by line).  If not defined, defaults to 0.

* Scrolling updates

New scrolling capability by defining SCROLLMODE in Config.h to 0 (original), 1 (by page) or 2 (by line). If not defined, defaults to 0.
Reformat.

* Add I2CManager calls. Remove unnecessary delays.

* Add I2CManager calls, remove unnecessary I2C delays.

* SSD1306: Move methods from .h to .cpp and reformat.

* Fix compiler warning in LiquidCrystal_I2C

* Allow forcing of I2C clock speed.

New method forceClock allows the I2C speed to be overridden.  For example, if the I2C bus is long then the speed can be forced lower.  It can also be forced higher to gain performance if devices are capable.

* Make Config.h conditionally included.

Allow for non-existence of Config.h.

* Correct scrolling and allow longer messages

Correct the handling of scrolling in scrollmode 1 to avoid a blank page being displayed.  Also, allow MAX_MSG_SIZE to be optionally configured to override maximum message length on screens.

* compiler warning on uno

Co-authored-by: dexslab <dex35803@gmail.com>
Co-authored-by: Asbelos <asbelos@btinternet.com>
Co-authored-by: Harald Barth <haba@kth.se>
Co-authored-by: Neil McKechnie <neilmck999@gmail.com>
Co-authored-by: Neil McKechnie <75813993+Neil-McK@users.noreply.github.com>
2021-03-07 15:58:35 -05:00
mstevetodd
4e2bb445d1 Committing a SHA 2021-02-16 01:26:09 +00:00
mstevetodd
ae6958b636 Merge pull request #20 from DCC-EX/master
merge upstream changes
2021-02-15 20:25:51 -05:00
Harald Barth
781d0325af Committing a SHA 2021-02-15 09:52:46 +00:00
Harald Barth
62d1f46a03 yaml syntax 2021-02-15 10:52:19 +01:00
Harald Barth
5860ad3f1d do the sha generation on master 2021-02-15 10:41:08 +01:00
mstevetodd
8aacb6dc5c Merge pull request #19 from DCC-EX/master
merge upstream changes
2021-02-10 10:52:28 -05:00
Fred
92fb06c691 Rename release-notes.md to release_notes.md 2021-02-07 12:43:15 -05:00
Fred
bf52f99a3a Create release-notes.md
Add release notes to main sketch folder
2021-02-07 12:41:03 -05:00
mstevetodd
336a6479e4 Merge pull request #18 from DCC-EX/master
merge upstream changes
2021-02-02 19:58:28 -05:00
Harald Barth
6cc5550927 result should be as is (can be -1 to indicate fail) 2021-01-30 22:54:38 +01:00
Harald Barth
0b3e904ffb correct logic in setLocoId 2021-01-30 22:53:05 +01:00
Fred
f646f12c65 Update Prod-Release-Notes.md 2021-01-21 10:19:34 -05:00
Fred
a91dc98184 Update Prod-Release-Notes.md 2021-01-21 10:13:38 -05:00
Fred
7c7305ba1d Update Prod-Release-Notes.md 2021-01-21 10:08:35 -05:00
Asbelos
3818a16808 Merge pull request #121 from DCC-EX/ConsistR
Startup commands and < R > and < W > commands
2021-01-21 11:16:15 +00:00
Asbelos
2ce4c8066e Update version.h 2021-01-21 11:10:52 +00:00
Asbelos
d71c95e9d2 Merge branch 'master' into ConsistR 2021-01-21 11:09:09 +00:00
Asbelos
fa2b740bb4 Merge branch 'startupCommands' into ConsistR 2021-01-21 11:00:01 +00:00
mstevetodd
271d453b99 Merge pull request #17 from DCC-EX/master
add warn/trip level to meter response (#120)
2021-01-19 14:55:06 -05:00
mstevetodd
611838d60c add warn/trip level to meter response (#120)
* send milliAmps and meter setup for new JMRI Meter function

* add warn/trip level to meter response

provides support for separate max vs trip levels
2021-01-18 17:46:41 -05:00
SteveT
de4bf42923 add warn/trip level to meter response
provides support for separate max vs trip levels
2021-01-18 09:14:41 -05:00
Asbelos
7d90e4241a Add <W locoid> command
Automatically clears consist and manages short/long addresses
2021-01-18 10:06:46 +00:00
Asbelos
b537d7a318 <R> command consist support
R command will return address suitable for throttle if consist has been setup.
2021-01-17 13:22:16 +00:00
mstevetodd
bf97adfe2d Merge pull request #16 from DCC-EX/master
pull upstream changes
2021-01-11 17:10:53 -05:00
Fred
82a4b48808 Update Prod-Release-Notes.md 2021-01-08 17:03:43 -05:00
Fred
7b4e5546b6 Update version.h 2021-01-08 16:58:22 -05:00
mstevetodd
418d8eb1b2 send milliAmps and meter setup for new JMRI Meter function (#113)
* send milliAmps and meter setup for new JMRI Meter function
2021-01-08 16:57:32 -05:00
Asbelos
895b2aaaaa Implement mySetup.h facility 2021-01-07 20:58:23 +00:00
SteveT
f1116ffba4 send milliAmps and meter setup for new JMRI Meter function 2021-01-06 16:27:14 -05:00
SteveT
da31e9cbc5 send milliAmps and meter setup for new JMRI Meter function 2021-01-06 16:13:58 -05:00
SteveT
7f27cfc9cb send milliAmps and meter setup for new JMRI Meter function 2021-01-06 16:09:26 -05:00
SteveT
e7ada19c97 send milliAmps and meter setup for new JMRI Meter function 2021-01-06 16:05:31 -05:00
mstevetodd
ad72e2f697 Merge pull request #15 from DCC-EX/master
pull upstream changes
2021-01-06 15:32:11 -05:00
Fred
0618a0bd72 RMFT Hooks (#112)
These hooks do NOT require RMFT code to be present.... but they offer the hooks that RMFT will need when available.

authored-by: Asbelos <asbelos@btinternet.com>
2021-01-05 13:05:17 -05:00
mstevetodd
42075f838e <T> should send turnout definitions, not just states (#110)
* use int, not byte for witSpeed

* add turnout, sensor and output states to 's'tatus message

* <T> should send turnout definitions, not just states
2021-01-04 10:57:03 -05:00
SteveT
98d6ff7709 undo inadvertent commit 2020-12-28 21:02:04 -05:00
SteveT
7e7435eafa <T> should send turnout definitions, not just states 2020-12-28 21:00:18 -05:00
mstevetodd
f134d87c85 Merge pull request #14 from DCC-EX/master
merge upstream changes
2020-12-28 19:48:47 -05:00
ADA
d70f76e80e Adanrg ethernet shield fixes (#107)
* Ethernet Shield can be configured with static IP

* Use MAC address in config.h, if configured.

* Fix library name typos

* Update EthernetInterface.h

Remove MAC address define here and remind people with a compile time error that it must be defined in the config.h

Co-authored-by: Fred <fndecker@gmail.com>
2020-12-27 18:06:31 -05:00
Fred
7963b02839 Update config.example.h
Modify example config for Ethernet Shield use
2020-12-27 17:27:22 -05:00
Asbelos
c8bf4347c2 Update DCCWaveform.cpp (#109)
Anti-jitter
Prevents main track interrupt taking a variable time which causes prog track jitter.
2020-12-27 16:41:00 -05:00
Harald Barth
883ac61fc7 spell names like on github 2020-12-27 21:43:05 +01:00
mstevetodd
5ee59e5f4b Merge pull request #13 from DCC-EX/master
merge from upstream
2020-12-27 12:28:57 -05:00
Fred
fd6784a2c2 Update Prod-Release-Notes.md 2020-12-27 10:48:17 -05:00
Fred
b674869427 Update Prod-Release-Notes.md 2020-12-27 10:43:17 -05:00
Fred
a34b51a63d Update Prod-Release-Notes.md
Fix formatting.
2020-12-27 10:41:14 -05:00
Fred
72aa0f2c2b Update Prod-Release-Notes.md 2020-12-27 10:38:51 -05:00
mstevetodd
9d92fd9451 add turnout, sensor and output states to 's'tatus message (#108)
* add support for FireBox_Mk1, reduce heartbeat, separate eStop time

* make match master

* make match master

* Update defines.h

* FIX: return WiThrottle speedstep, not DCC speedstep, in response to speed change request.

Should close #104

* use int, not byte for witSpeed

* add turnout, sensor and output states to 's'tatus message
2020-12-27 10:20:11 -05:00
SteveT
bc14cb176f add turnout, sensor and output states to 's'tatus message 2020-12-23 20:01:11 -05:00
mstevetodd
0fee057b1b Merge pull request #12 from DCC-EX/master
Update Prod-Release-Notes.md
2020-12-23 09:38:54 -05:00
Fred
5f21716055 Update Prod-Release-Notes.md
Add Matt H, a reference to WebThrottle-EX and add more feature notes
2020-12-21 20:12:47 -05:00
mstevetodd
f26f5ab40b Merge pull request #11 from DCC-EX/master
Add LCD and OLED libs (#102)
2020-12-18 09:02:42 -05:00
ggee
040dc35b93 Add LCD and OLED libs (#102)
Add the example platformio configuration file
2020-12-17 17:33:29 -05:00
mstevetodd
9d0dbf7878 Merge pull request #10 from DCC-EX/master
merge upstream changes
2020-12-16 11:39:23 -05:00
Harald Barth
e1ad1f0ced Merge branch 'ethernetdefine' 2020-12-11 23:24:26 +01:00
Harald Barth
a1b802d91b Merge branch 'currentvalues' 2020-12-11 22:04:39 +01:00
Harald Barth
5333b7889f bugfix patches go in now 2020-12-11 22:02:11 +01:00
Harald Barth
fd292e50b6 Merge branch 'wifispeed' 2020-12-11 22:01:01 +01:00
mstevetodd
b9fdfdd71c FIX: return WiThrottle speedstep, not DCC speedstep, in response to speed change request (#105)
* FIX: return WiThrottle speedstep, not DCC speedstep, in response to speed change request.

Should close #104

* use int, not byte for witSpeed
2020-12-11 14:20:13 -05:00
SteveT
47641a4b01 use int, not byte for witSpeed 2020-12-11 13:29:55 -05:00
Harald Barth
89073bd311 give back witspeed not converted (DCC) locospeed 2020-12-11 19:11:17 +01:00
Harald Barth
73b5325085 better speed step comment 2020-12-11 19:03:05 +01:00
SteveT
ef95e98a44 FIX: return WiThrottle speedstep, not DCC speedstep, in response to speed change request.
Should close #104
2020-12-11 09:33:18 -05:00
mstevetodd
8803dc0ea3 Merge pull request #9 from DCC-EX/master
Create CONTRIBUTING.md
2020-12-11 08:59:00 -05:00
Fred
d67290b579 Create CONTRIBUTING.md
Create a contributing file
2020-12-08 07:34:59 -05:00
Harald Barth
a1f7d06508 Report current as 1/1024 as expected by JMRI 2020-12-06 21:43:37 +01:00
Harald Barth
01a6d1c8f4 Ethernet should need a #define in config(.example).h 2020-11-29 10:48:39 +01:00
mstevetodd
3ae8ce30ff Merge pull request #8 from DCC-EX/master
pull upstream commits
2020-11-26 19:41:25 -05:00
Harald Barth
f2db288102 Merge branch 'ackdiag' into candidate 2020-11-26 16:20:34 +01:00
Harald Barth
89fd98e4af Merge branch 'pluscommand-trackpoweroff' into candidate 2020-11-26 16:20:18 +01:00
Harald Barth
c57add11e3 Merge branch 'wifitimeout' into candidate 2020-11-26 16:17:17 +01:00
Harald Barth
21b3d28038 Merge branch 'master' into candidate 2020-11-26 15:58:40 +01:00
Harald Barth
e4eecae846 do not touch CR or CRLF of svg files 2020-11-26 15:55:52 +01:00
LarryD
56569a9b44 Partially fixed alignment issue. 2020-11-25 19:02:14 -06:00
mstevetodd
abfd63eb0d Merge pull request #7 from DCC-EX/master
pull upstream changes
2020-11-25 19:57:29 -05:00
LarryD
e026fc1db2 Added list of key contributors to v3.0.0 2020-11-25 18:51:31 -06:00
LarryD
d48f827fa1 Add CommandStation-EX Arch files in Visio, PDF & SVG 2020-11-25 18:29:07 -06:00
LarryD
7bda3e7efc Added Production Release Notes from Release to Release-Doc 2020-11-25 17:50:25 -06:00
LarryD
2cdd0d20cc Moved Rough Release Notes to Release Doc Folder 2020-11-25 17:47:06 -06:00
LarryD
20891968ed Add Folder for Release Doc and Architecutre Structure 2020-11-25 17:42:25 -06:00
mstevetodd
aa550ec3e6 Merge pull request #6 from DCC-EX/master
Mcommand (#100)
2020-11-24 22:14:21 -05:00
Harald Barth
edc39e7342 ack pulse length configurable from diag 2020-11-24 21:39:21 +01:00
Harald Barth
3faa48476c ack diag better messages 2020-11-24 21:12:55 +01:00
Asbelos
2ea8bfdd7c Mcommand (#100) 2020-11-24 07:49:15 -05:00
mstevetodd
57d90d679a Merge pull request #5 from DCC-EX/master
Create release-notes.md
2020-11-23 16:19:56 -05:00
Harald Barth
beca0b3368 Safety measure: Turm power off at + command 2020-11-23 22:13:36 +01:00
David Cutting
29edc65295 Create release-notes.md 2020-11-22 20:19:52 -07:00
Harald Barth
31022094c1 adjust timeouts 2020-11-23 00:46:14 +01:00
Harald Barth
a3ddcb059a make connection timeout configurable 2020-11-21 23:23:27 +01:00
Harald Barth
89b158f3d1 cut timeouts shorter 2020-11-21 22:20:15 +01:00
mstevetodd
7d460e5ef1 Merge pull request #4 from DCC-EX/master
update from upstream
2020-11-21 11:39:41 -05:00
Harald Barth
7753f6dbb5 many typos fixed 2020-11-18 23:53:06 +01:00
Harald Barth
df2e40fa11 empty SSID is "unconfigured" as well 2020-11-18 23:42:37 +01:00
Asbelos
258113c580 Fix T commands (#59)
Fix <H ..> response giving 128 instead of 1 for active status
2020-11-18 07:34:02 -05:00
Harald Barth
612cb95f85 make AP password setable 2020-11-17 15:16:45 +01:00
David Cutting
9ee5dc1600 Merge pull request #50 from DCC-EX/remove-boards
Remove all boards but Uno, Mega, and Nano
2020-11-16 15:20:17 -07:00
David Cutting
ed5031cbf6 Remove all boards but Uno, Mega, and Nano 2020-11-15 16:21:25 -07:00
Harald Barth
58550a68c8 surpress gcc warnings 2020-11-14 20:13:57 +01:00
mstevetodd
3ccae75e37 Merge pull request #3 from DCC-EX/master
update from upstream
2020-11-14 08:50:17 -05:00
Harald Barth
48d03cc82f Merge branch 'dont-touch-wifi' 2020-11-14 13:21:55 +01:00
Harald Barth
e809a460cc DONT_TOUCH_WIFI_CONFIG feature 2020-11-14 13:17:47 +01:00
David Cutting
266a8728d3 Update README.md
Rewrite to reflect website text and latest release info (originally said we were on v3.0.1). Removed old text referring to BaseStation-Classic.
2020-11-14 04:24:07 -07:00
mstevetodd
6b4199be27 Merge pull request #2 from DCC-EX/master
update from head
2020-11-10 13:57:03 -05:00
Asbelos
b1ac7feb01 Merge branch 'master' into Ethernewt- 2020-11-06 08:54:25 +00:00
Asbelos
6dd4ab0004 Working Ethernet!! 2020-11-05 10:54:16 +00:00
Asbelos
eeac7893a6 Compiler warning 2020-11-05 10:53:55 +00:00
Harald Barth
d248cccf81 special treatment for Pololu board 2020-11-01 10:16:29 +01:00
Harald Barth
34f3abedaf different approach to Polplu board and UNUSED_PIN=127 2020-10-31 22:45:06 +01:00
Harald Barth
6f68adbfe9 comment negative/inverted pin 2020-10-31 11:29:34 +01:00
Harald Barth
5ad396fbf4 fix indent 2020-10-31 11:27:29 +01:00
Harald Barth
7a5b84ef18 handle motorboards with invereted brake pin (like pololu) 2020-10-31 11:26:23 +01:00
Asbelos
a85131ee17 experimental 2020-10-30 13:00:02 +00:00
Asbelos
4dca656fd2 Merge branch 'master' into Ethernewt- 2020-10-30 10:02:45 +00:00
SteveT
0d51294ea5 Update defines.h 2020-10-29 12:53:07 -04:00
SteveT
9e0dcb6fc8 make match master 2020-10-29 12:52:10 -04:00
SteveT
052178970b make match master 2020-10-29 12:47:44 -04:00
SteveT
c53dea018f Merge branch 'master' of https://github.com/mstevetodd/CommandStation-EX 2020-10-29 12:39:42 -04:00
mstevetodd
06ace2484f Merge pull request #1 from DCC-EX/master
merge from upstream
2020-10-29 12:37:55 -04:00
Harald Barth
50e85d0b79 better isprint instead 2020-10-28 23:19:55 +01:00
Harald Barth
df9b7813a9 include ctype and add license notice 2020-10-28 22:08:51 +01:00
Harald Barth
bf4190063a forgotten end of setup function 2020-10-28 21:16:41 +01:00
Harald Barth
1a1dbb42bc Merge branch 'frightrisk-outputs' of https://github.com/DCC-EX/CommandStation-EX into frightrisk-outputs
Conflicts:
	DCCEXParser.cpp
2020-10-28 20:41:25 +01:00
Harald Barth
27f3842ef5 do second try to set AP ESSID and PASSWORD if necessary 2020-10-28 20:32:20 +01:00
Harald Barth
43755b054f Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2020-10-28 20:25:54 +01:00
Harald Barth
1038be8709 Merge branch 'master' into sensor-activate 2020-10-28 20:23:47 +01:00
Fred
177bf202a8 Update Outputs.cpp
remove extra blank line
2020-10-28 14:27:16 -04:00
Fred
8ed8db1127 Update DCC.h 2020-10-28 14:22:34 -04:00
Fred
7537f4c3d6 Update CommandStation-EX.ino 2020-10-28 14:14:11 -04:00
Harald Barth
c6cff3b4bd wifi setup tristate 2020-10-27 21:50:46 +01:00
Asbelos
e077bc123a Merge branch 'wifi-reliability2' into Ethernewt- 2020-10-27 16:49:53 +00:00
Asbelos
d61273a7b0 Merge branch 'master' into Ethernewt- 2020-10-27 16:48:53 +00:00
Asbelos
6e9f584f72 Merge branch 'master' into wifi-reliability2 2020-10-27 12:52:02 +00:00
Asbelos
96fba12d59 ONLY 1 Call to WifiInboundHandler::setup!
Or it wastes 1.5kb each time!
2020-10-27 12:50:01 +00:00
Asbelos
a65ca0b704 Cleaner purge after error or connect fail 2020-10-27 12:33:01 +00:00
Asbelos
138851f45f Cleaner purge on ERROR 2020-10-27 11:04:51 +00:00
Asbelos
6c0b5d82be Ring count improvements
Count is stashed at start of message so can be got without counting.
Commit discards empty replies automatically.
2020-10-27 10:38:30 +00:00
Harald Barth
59a1a8eb1b turn off tcp server to reset connections 2020-10-27 07:33:04 +01:00
Harald Barth
019001675e return false if parseS does not find any sensors 2020-10-27 07:24:48 +01:00
FrightRisk
8101e75dae Remove unneccessary break command 2020-10-26 14:00:25 -04:00
FrightRisk
babdad06ca Remove commands that were called twice 2020-10-26 13:28:47 -04:00
FrightRisk
369a75f958 Fix return values for outputs and sensors (<X> and <O> responses 2020-10-26 12:57:37 -04:00
Asbelos
b7fc055953 Passing Strteamer correctly
Previous method copied entire buffer!!!!
2020-10-26 13:58:25 +00:00
Asbelos
8ff947f895 Passing outbound Ring to Withrottle
This will allow Withrottle to send to other clients and broadcast messages.
2020-10-26 13:31:51 +00:00
Asbelos
6833d82789 Buffer overflow resilience
And diag output of replies
2020-10-26 12:59:40 +00:00
Harald Barth
9149bc0ee2 no float in Sensor no more 2020-10-26 09:10:45 +01:00
Harald Barth
ecd176042e checkAll() only checks one now (and should be renamed) 2020-10-26 00:56:25 +01:00
Harald Barth
9099af3188 Sensors show their status at change (as in classic) 2020-10-25 23:52:22 +01:00
Asbelos
a16b3a1fa6 Drop obsolete reference 2020-10-22 20:24:47 +01:00
Harald Barth
c882c2edfc prepare version for release 2020-10-22 20:38:10 +02:00
Asbelos
8c8a484671 License details correction 2020-10-20 12:48:32 +01:00
Asbelos
742f10bf0a Merge branch 'master' into wifi-reliability2 2020-10-20 12:31:52 +01:00
Harald Barth
4dee6da29e make volatile because of interrupt routine and optimzer in PIO 2020-10-20 10:55:12 +02:00
Harald Barth
7dca6db1c5 Bit shuffling for function groups 2020-10-13 23:08:19 +02:00
Asbelos
ae77c5b7d6 Send all functions 2020-10-13 18:01:11 +01:00
Asbelos
11e22c5d1d Working Wifi ringbuffer implementation
Notice 1kb output buffer
Aslo no need to copy command in Withrottle
2020-10-13 17:37:40 +01:00
Asbelos
b98c853a1b Withrotthe Functions 2020-10-12 22:24:37 +01:00
Asbelos
83e4b29a78 Ckean lost buffer 2020-10-12 22:21:37 +01:00
Asbelos
3fe04c1a0e DCC getFn 2020-10-12 22:18:55 +01:00
Asbelos
576288ba9e DIAG LCD 2020-10-12 22:18:09 +01:00
Harald Barth
ac0f7e9356 Merge branch 'ackdiag' into mergetest 2020-10-12 23:10:31 +02:00
Asbelos
0e3046e24f Lcd experimental (#46)
* LCD/OLED Implementation
* OLED basic working 32
*132 display
* LCD/OLED startup ok
* Simplified setup
* Missing freememory include
* Format Width
Allow right padding number width in String format
* Intermediate scroll
* Compile issues with no LCD
* Clean buffers at startup
* Support for format left padded numbers
* Smarter Scrolling
And forced start messages
Free Ram in slot 2
* LCD tidying
Neater setup block in .ino.
Dropped unnecessary code
No-scroll if display not full
* Missing %E format support
This is used in WifiInterface checkForOK
* Wifi correction and memory guard
2020-10-12 14:32:47 -04:00
Harald Barth
1c2df3fe22 Ack current setable by debug statement D ACK LIMIT n 2020-10-08 23:39:04 +02:00
SteveT
e112be7087 Merge branch 'master' of https://github.com/mstevetodd/CommandStation-EX 2020-10-07 08:53:02 -04:00
Asbelos
3f06fb08df Wifi reliability (#45)
* First pass at wifi inbound FSA
* Fixup detector loop
* Remove asyncBanned
Unused, uninitialized
* Move wifi setup loop out of .ino
Wifi auto detect Serial 1,2,3
* Correct capitalization
* Uno compiles clean
* Command distributor
Moved command execution routing out of Wifi code for future use by Ethernet interface.
Co-authored-by: Fred <fndecker@gmail.com>
2020-10-05 13:42:31 -04:00
Harald Barth
abd83bf7d6 Merge branch 'turnouteeprom' into mergetest, conflicts in DCC.cpp resolved
Conflicts:
	DCC.cpp
2020-10-05 00:13:04 +02:00
Harald Barth
6dc4bcdb71 D EEPROM command 2020-10-04 21:20:13 +02:00
dexslab
f0314e96af Changes for compatibility with Installer 2020-10-04 11:31:24 -04:00
dexslab
a8000aae54 Changes for compatibility with Installer 2020-10-04 11:29:48 -04:00
Harald Barth
75d7547f11 Turnouts stored to EEPROM without trashing other stuff 2020-10-03 14:07:25 +02:00
Asbelos
6feda4e217 startup Info on USB (#44)
Add status info to CS startup and ash Git SHA for version tracking
2020-10-02 12:12:48 -04:00
FrightRisk
a28792312a add back config.example.h file 2020-09-29 17:28:03 -04:00
FrightRisk
7c47bb1562 Fix Turnout and Output return value 2020-09-29 17:19:02 -04:00
mstevetodd
1b802cc600 add support for FireBox_Mk1, reduce heartbeat, separate eStop time (#43) 2020-09-29 11:51:01 -04:00
mstevetodd
6e45b3a434 Merge pull request #3 from DCC-EX/master
get upstream changes
2020-09-28 19:09:02 -04:00
SteveT
f6b5a47975 add support for FireBox_Mk1, reduce heartbeat, separate eStop time 2020-09-28 19:07:27 -04:00
Dex
2495edbd25 Merge pull request #42 from DCC-EX/feature/config
revert of timer.cpp
2020-09-28 15:15:36 -04:00
dexslab
81c695168e revert of timer.cpp 2020-09-28 15:13:48 -04:00
mstevetodd
fee0a75b36 Merge pull request #2 from DCC-EX/master
merge upstream changes
2020-09-28 14:09:20 -04:00
Harald Barth
f6853e5abd Merge branch 'progcurlimit' 2020-09-27 13:26:41 +02:00
Harald Barth
7cd9e68717 turn PROGBOOST off at PROG off 2020-09-27 13:26:29 +02:00
Harald Barth
1801366aa3 Merge branch 'progcurlimit' 2020-09-27 13:12:27 +02:00
Harald Barth
1f03d9759d cap overcurrent off time to 10sec 2020-09-27 13:12:02 +02:00
Harald Barth
85d60dfbbd D PROGBOOST ON command 2020-09-27 13:03:46 +02:00
Harald Barth
87fcfa0823 Merge branch 'progcurlimit' 2020-09-27 12:22:31 +02:00
Harald Barth
c14596a252 current trip values on PROG depending on state 2020-09-27 12:14:25 +02:00
Gregor Baues
e02f29d132 plaformio.ini with monitor flags 2020-09-27 11:03:05 +02:00
Harald Barth
6911d9ed01 better howto in README.md 2020-09-27 00:44:45 +02:00
Harald Barth
60413b00e3 platformio conf 2020-09-26 23:46:28 +02:00
Harald Barth
9917035534 PROG_TRIP_CURRENT defined here until made runtime changable 2020-09-26 23:33:47 +02:00
Harald Barth
527cc92eac Merge branch 'feature/config' into mergetest
Conflicts:
	platformio.ini
2020-09-26 23:28:02 +02:00
Harald Barth
d45d35f32f Merge branch 'feature/config-saveram' into feature/config
Conflicts:
	objdump.bat
2020-09-26 23:04:19 +02:00
Harald Barth
a91d127256 Merge branch 'feature/config-saveram' of https://github.com/DCC-EX/CommandStation-EX into feature/config-saveram 2020-09-26 22:57:34 +02:00
Harald Barth
00202e7966 remove unused configs 2020-09-26 22:56:35 +02:00
Fred
59383dd6d5 Update platformio.ini
Update platformio build check to include the Ethernet and SPI libraries for a mega when ethernet is used
2020-09-26 16:52:13 -04:00
Harald Barth
79201a911c fix objdump.sh 2020-09-26 22:28:29 +02:00
Fred
6fb11d5ac9 Update platformio.ini
Add dependency for Ethernet library for Mega
2020-09-26 15:06:27 -04:00
Fred
36a59c5c8a Update platformio.ini
Add dependency for Ethernet library for Mega
2020-09-26 15:03:22 -04:00
Fred
c52610ae9c Update platformio.ini 2020-09-26 14:55:19 -04:00
Fred
2929ce167b Update platformio.ini
Add Ethernet library dependency when that is enabled
2020-09-26 14:34:59 -04:00
Asbelos
38fe65fcca Drop advanced feature samples from basic ino 2020-09-26 16:29:11 +01:00
Asbelos
c3e17fcf04 Add EthernetInterface (UNTESTED, UNCALLED)
Thanks to Gregor....I have modified his originals to eliminate the static initialisation memory loss. These do not show up in the object code if they are not referenced.
2020-09-26 12:01:00 +01:00
Asbelos
36e6c3cd48 Decouple WifiInterface from Parser
This removes the need for WifiInterfrace <+> command processing to be included in the link. so parser does not need to see the config settings for wifi.
If Wifi doesnt set the At command callback, parser will return <X> for a <+> command
2020-09-26 10:54:11 +01:00
Asbelos
162c7e6e11 UNTESTED Reduce #ifdef hell on wifi
This fix to the structure of the Wifi code alows for a better link optimisation and so it is no longer necessary for the wifi code or header to read values from the config.h file.

Compilation shows that >11Kb of code is eliminated by the linker if the setup and loop are not called.
~including the interface does not really change anything
2020-09-26 09:40:49 +01:00
Asbelos
411a6b1130 Move bulk includes out of .ino
I left config.h in the .ino so it shows the user that this will be necessary.
2020-09-26 08:49:51 +01:00
Asbelos
59d77f2362 Remove unnecessary #iddefs
These are only avoiding files that alrerady self-destruct... so they don't need to be avoided.
2020-09-26 08:42:24 +01:00
Asbelos
f76fb8d6c5 Merge branch 'feature/config' of https://github.com/DCC-EX/CommandStation-EX into feature/config 2020-09-26 08:10:55 +01:00
Asbelos
9ebe13828a Correct objdump 2020-09-26 08:10:43 +01:00
Harald Barth
bff7555102 NUM_SERIAL tells us how many serial lines to check 2020-09-25 22:49:53 +02:00
Harald Barth
6035e4d9ad Set preamblebits main to 16 and remove all preamble config from config.h 2020-09-25 22:27:37 +02:00
Harald Barth
32f1d7d890 Make all Wifi code disappear if Wifi is not enabled. 2020-09-25 22:14:20 +02:00
dexslab
bc8b01fbd6 Minor fixes start Uno Wifi Rev 2 fixes 2020-09-25 13:51:08 -04:00
Asbelos
b6b8bfb04d Fix Auto-poweron in blocking mode 2020-09-25 12:37:30 +01:00
Harald Barth
10495957dd remove things that do not need to be configurable 2020-09-24 11:59:01 +02:00
Harald Barth
9c5396bbc2 not worth the hassle 2020-09-24 11:27:21 +02:00
Harald Barth
812cee364f compiler warning 2020-09-24 11:06:57 +02:00
Harald Barth
1d47536501 Merge branch 'feature/config' of https://github.com/DCC-EX/CommandStation-EX into feature/config 2020-09-24 10:58:17 +02:00
Harald Barth
b810fb45c7 config.h only in .ino 2020-09-24 10:58:08 +02:00
Asbelos
caef013d35 Supply motor shield name for <s> 2020-09-24 09:51:09 +01:00
dexslab
0b603d4dfd Change motorshield back to whats in config, change from 1.2 version needed 2020-09-23 22:49:18 -04:00
FrightRisk
3d94d8347a Add support for 2 or 4 line LCD Display 2020-09-23 21:54:01 -04:00
FrightRisk
3df0ae16df Move version to its own .h file 2020-09-23 21:53:33 -04:00
dexslab
ff7fb3870b Fix for uno compile with wifi interface that is not needed and fix for compile on both motorshield type not being used properly 2020-09-23 17:54:47 -04:00
FrightRisk
ba79668a8f Add Ardunio and Motor Board types to <s> cmd 2020-09-23 15:17:01 -04:00
FrightRisk
00fb7c6d04 Merge branch 'feature/config' of https://github.com/DCC-EX/CommandStation-EX into feature/config
merge latest changes from remote
2020-09-23 12:05:56 -04:00
FrightRisk
dce241d1ae more experiments with dccexparser 2020-09-23 11:54:09 -04:00
Asbelos
737300796d Correct <+> command return 2020-09-23 16:22:20 +01:00
Harald Barth
da979a4492 Merge branch 'feature/config' of https://github.com/DCC-EX/CommandStation-EX into feature/config 2020-09-22 23:24:54 +02:00
Harald Barth
9e1d85f9bb make number of slots in Uno configurable 2020-09-22 23:24:30 +02:00
dexslab
623187281d Wifi baud how dare you 2020-09-22 17:15:27 -04:00
Harald Barth
9bb9fa5e84 rewrite freeMemory code 2020-09-22 23:10:52 +02:00
Harald Barth
cbd11e6099 Merge branch 'feature/config' of https://github.com/DCC-EX/CommandStation-EX into feature/config 2020-09-22 22:12:52 +02:00
Harald Barth
03c23abea2 Set default values meaning unconfigured 2020-09-22 22:11:50 +02:00
dexslab
d0ecfbc71c parenthesis are your firends 2020-09-22 16:02:11 -04:00
dexslab
c9bc63e659 Cleanup for wifi enabled and if on a mega 2020-09-22 15:59:34 -04:00
dexslab
37ab4d222f Fix for flash memory usage per Haba 2020-09-22 15:57:26 -04:00
dexslab
eb887fdbd2 Finish wifi interface fixes from serial123 branch, Fixes for flash string missing 2020-09-22 15:45:06 -04:00
dexslab
9ad8dd6cbd Implement Serial 123 Branch by hand due to merge conflicts 2020-09-22 15:20:33 -04:00
dexslab
28efc0ffef the start of new feature the config.h template including fix for github build action 2020-09-22 14:57:11 -04:00
mstevetodd
c8a4323a4f Merge pull request #1 from DCC-EX/master
changes from upstream
2020-09-21 18:56:46 -04:00
91 changed files with 7308 additions and 2878 deletions

1
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto
*.svg -text

View File

@@ -12,6 +12,8 @@ jobs:
- name: Install Python Wheel
run: pip install wheel
- name: Install PlatformIO Core
run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip
run: pip install -U platformio
- name: Copy generic config over
run: cp config.example.h config.h
- name: Compile Command Station (AVR)
run: python -m platformio run

34
.github/workflows/sha.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: SHA
# Run this workflow ever time code is pushed to a branch
# other than `main` in your repository
on: push
jobs:
# Set the job key. The key is displayed as the job name
# when a job name is not provided
sha:
# Name the Job
name: Commit SHA
# Set the type of machine to run on
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
uses: actions/checkout@v2
- name: Create SHA File
run: |
sha=$(git rev-parse --short "$GITHUB_SHA")
echo "#define GITHUB_SHA \"$sha\"" > GITHUB_SHA.h
- uses: EndBug/add-and-commit@v4 # You can change this to use a specific version
with:
add: 'GITHUB_SHA.h'
message: 'Committing a SHA'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged

3
.gitignore vendored
View File

@@ -6,3 +6,6 @@ Release/*
.gcc-flags.json
.pio/
.vscode/
config.h
.vscode/extensions.json
mySetup.h

View File

@@ -1,194 +0,0 @@
#ifndef ATMEGA2560Timer_h
#define ATMEGA2560Timer_h
#include "../VirtualTimer.h"
#include <Arduino.h>
class Timer : public VirtualTimer {
private:
int pwmPeriod;
unsigned long timer_resolution;
unsigned char clockSelectBits;
int timer_num;
unsigned long lastMicroseconds;
public:
void (*isrCallback)();
Timer(int timer_num) {
switch (timer_num)
{
case 1:
case 3:
case 4:
case 5:
timer_resolution = 65536;
break;
}
this->timer_num = timer_num;
lastMicroseconds = 0;
}
void initialize() {
switch (timer_num)
{
case 1:
TCCR1B = _BV(WGM13) | _BV(WGM12);
TCCR1A = _BV(WGM11);
break;
case 3:
TCCR3B = _BV(WGM33) | _BV(WGM32);
TCCR3A = _BV(WGM31);
break;
case 4:
TCCR4B = _BV(WGM43) | _BV(WGM42);
TCCR4A = _BV(WGM41);
break;
case 5:
TCCR5B = _BV(WGM53) | _BV(WGM52);
TCCR5A = _BV(WGM51);
break;
}
}
void setPeriod(unsigned long microseconds) {
if(microseconds == lastMicroseconds)
return;
lastMicroseconds = microseconds;
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
if (cycles < timer_resolution) {
clockSelectBits = 1 << 0;
pwmPeriod = cycles;
} else
if (cycles < timer_resolution * 8) {
clockSelectBits = 1 << 1;
pwmPeriod = cycles / 8;
} else
if (cycles < timer_resolution * 64) {
clockSelectBits = (1 << 0) | (1 << 1);
pwmPeriod = cycles / 64;
} else
if (cycles < timer_resolution * 256) {
clockSelectBits = 1 << 2;
pwmPeriod = cycles / 256;
} else
if (cycles < timer_resolution * 1024) {
clockSelectBits = (1 << 2) | (1 << 0);
pwmPeriod = cycles / 1024;
} else {
clockSelectBits = (1 << 2) | (1 << 0);
pwmPeriod = timer_resolution - 1;
}
switch (timer_num)
{
case 1:
ICR1 = pwmPeriod;
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
break;
case 3:
ICR3 = pwmPeriod;
TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits;
break;
case 4:
ICR4 = pwmPeriod;
TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits;
break;
case 5:
ICR5 = pwmPeriod;
TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits;
break;
}
}
void start() {
switch (timer_num)
{
case 1:
TCCR1B = 0;
TCNT1 = 0; // TODO: does this cause an undesired interrupt?
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
break;
case 3:
TCCR3B = 0;
TCNT3 = 0; // TODO: does this cause an undesired interrupt?
TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits;
break;
case 4:
TCCR4B = 0;
TCNT4 = 0; // TODO: does this cause an undesired interrupt?
TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits;
break;
case 5:
TCCR5B = 0;
TCNT5 = 0; // TODO: does this cause an undesired interrupt?
TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits;
break;
}
}
void stop() {
switch (timer_num)
{
case 1:
TCCR1B = _BV(WGM13) | _BV(WGM12);
break;
case 3:
TCCR3B = _BV(WGM33) | _BV(WGM32);
break;
case 4:
TCCR4B = _BV(WGM43) | _BV(WGM42);
break;
case 5:
TCCR5B = _BV(WGM53) | _BV(WGM52);
break;
}
}
void attachInterrupt(void (*isr)()) {
isrCallback = isr;
switch (timer_num)
{
case 1:
TIMSK1 = _BV(TOIE1);
break;
case 3:
TIMSK3 = _BV(TOIE3);
break;
case 4:
TIMSK4 = _BV(TOIE4);
break;
case 5:
TIMSK5 = _BV(TOIE5);
break;
}
}
void detachInterrupt() {
switch (timer_num)
{
case 1:
TIMSK1 = 0;
break;
case 3:
TIMSK3 = 0;
break;
case 4:
TIMSK4 = 0;
break;
case 5:
TIMSK5 = 0;
break;
}
}
};
extern Timer TimerA;
extern Timer TimerB;
extern Timer TimerC;
extern Timer TimerD;
#endif

View File

@@ -1,208 +0,0 @@
#ifndef ATMEGA328Timer_h
#define ATMEGA328Timer_h
#include "../VirtualTimer.h"
#include <Arduino.h>
class Timer : public VirtualTimer {
private:
int pwmPeriod;
unsigned long timer_resolution;
unsigned char clockSelectBits;
int timer_num;
unsigned long lastMicroseconds;
public:
void (*isrCallback)();
Timer(int timer_num) {
switch (timer_num)
{
//case 0:
case 2:
timer_resolution = 256;
break;
case 1:
timer_resolution = 65536;
break;
}
this->timer_num = timer_num;
lastMicroseconds = 0;
}
void initialize() {
switch (timer_num)
{
// case 0:
// TCCR0B = _BV(WGM02);
// TCCR0A = _BV(WGM00) | _BV(WGM01);
// break;
case 1:
TCCR1B = _BV(WGM13) | _BV(WGM12);
TCCR1A = _BV(WGM11);
break;
case 2:
TCCR2B = _BV(WGM22);
TCCR2A = _BV(WGM20) | _BV(WGM21);
break;
}
}
void setPeriod(unsigned long microseconds) {
if(microseconds == lastMicroseconds)
return;
lastMicroseconds = microseconds;
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
switch(timer_num) {
case 2:
if (cycles < timer_resolution) {
clockSelectBits = 1 << 0;
pwmPeriod = cycles;
} else
if (cycles < timer_resolution * 8) {
clockSelectBits = 1 << 1;
pwmPeriod = cycles / 8;
} else
if (cycles < timer_resolution * 32) {
clockSelectBits = 1 << 0 | 1 << 1;
pwmPeriod = cycles / 32;
} else
if (cycles < timer_resolution * 64) {
clockSelectBits = 1 << 2;
pwmPeriod = cycles / 64;
} else
if (cycles < timer_resolution * 128) {
clockSelectBits = 1 << 2 | 1 << 0;
pwmPeriod = cycles / 128;
} else
if (cycles < timer_resolution * 256) {
clockSelectBits = 1 << 2 | 1 << 1;
pwmPeriod = cycles / 256;
} else
if (cycles < timer_resolution * 1024) {
clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0;
pwmPeriod = cycles / 1024;
} else {
clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0;
pwmPeriod = timer_resolution - 1;
}
break;
//case 0:
case 1:
if (cycles < timer_resolution) {
clockSelectBits = 1 << 0;
pwmPeriod = cycles;
} else
if (cycles < timer_resolution * 8) {
clockSelectBits = 1 << 1;
pwmPeriod = cycles / 8;
} else
if (cycles < timer_resolution * 64) {
clockSelectBits = (1 << 0) | (1 << 1);
pwmPeriod = cycles / 64;
} else
if (cycles < timer_resolution * 256) {
clockSelectBits = 1 << 2;
pwmPeriod = cycles / 256;
} else
if (cycles < timer_resolution * 1024) {
clockSelectBits = (1 << 2) | (1 << 0);
pwmPeriod = cycles / 1024;
} else {
clockSelectBits = (1 << 2) | (1 << 0);
pwmPeriod = timer_resolution - 1;
}
break;
}
switch (timer_num)
{
// case 0:
// OCR0A = pwmPeriod;
// TCCR0B = _BV(WGM02) | clockSelectBits;
// break;
case 1:
ICR1 = pwmPeriod;
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
break;
case 2:
OCR2A = pwmPeriod;
TCCR2B = _BV(WGM22) | clockSelectBits;
break;
}
}
void start() {
switch (timer_num)
{
// case 0:
// TCCR0B = 0;
// TCNT0 = 0; // TODO: does this cause an undesired interrupt?
// TCCR0B = _BV(WGM02) | clockSelectBits;
// break;
case 1:
TCCR1B = 0;
TCNT1 = 0; // TODO: does this cause an undesired interrupt?
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
break;
case 2:
TCCR2B = 0;
TCNT2 = 0; // TODO: does this cause an undesired interrupt?
TCCR2B = _BV(WGM22) | clockSelectBits;
break;
}
}
void stop() {
switch (timer_num)
{
// case 0:
// TCCR0B = _BV(WGM02);
// break;
case 1:
TCCR1B = _BV(WGM13) | _BV(WGM12);
break;
case 2:
TCCR2B = _BV(WGM22);
break;
}
}
void attachInterrupt(void (*isr)()) {
isrCallback = isr;
switch (timer_num)
{
// case 0:
// TIMSK0 = _BV(TOIE0);
// break;
case 1:
TIMSK1 = _BV(TOIE1);
break;
case 2:
TIMSK2 = _BV(TOIE2);
break;
}
}
void detachInterrupt() {
switch (timer_num)
{
// case 0:
// TIMSK0 = 0;
// break;
case 1:
TIMSK1 = 0;
break;
case 2:
TIMSK2 = 0;
break;
}
}
};
extern Timer TimerA;
extern Timer TimerB;
#endif

View File

@@ -1,131 +0,0 @@
#ifndef ATMEGA328Timer_h
#define ATMEGA328Timer_h
#include "../VirtualTimer.h"
#include <Arduino.h>
// We only define behavior for timer 0 (TCA0), because TCB0 is very limited in functionality.
class Timer : public VirtualTimer {
private:
int pwmPeriod;
unsigned long timer_resolution;
unsigned char clockSelectBits;
int timer_num;
unsigned long lastMicroseconds;
public:
void (*isrCallback)();
Timer(int timer_num) {
switch (timer_num)
{
case 0:
timer_resolution = 65536;
break;
}
this->timer_num = timer_num;
lastMicroseconds = 0;
}
void initialize() {
switch (timer_num)
{
case 0:
break;
}
}
void setPeriod(unsigned long microseconds) {
if(microseconds == lastMicroseconds)
return;
lastMicroseconds = microseconds;
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
switch(timer_num) {
case 0:
if (cycles < timer_resolution) {
clockSelectBits = 0x0;
pwmPeriod = cycles;
} else
if (cycles < timer_resolution * 2) {
clockSelectBits = 0x1;
pwmPeriod = cycles / 8;
} else
if (cycles < timer_resolution * 4) {
clockSelectBits = 0x2;
pwmPeriod = cycles / 32;
} else
if (cycles < timer_resolution * 8) {
clockSelectBits = 0x3;
pwmPeriod = cycles / 64;
} else
if (cycles < timer_resolution * 64) {
clockSelectBits = 0x5;
pwmPeriod = cycles / 128;
} else
if (cycles < timer_resolution * 256) {
clockSelectBits = 0x6;
pwmPeriod = cycles / 256;
} else
if (cycles < timer_resolution * 1024) {
clockSelectBits = 0x7;
pwmPeriod = cycles / 1024;
} else {
clockSelectBits = 0x7;
pwmPeriod = timer_resolution - 1;
}
break;
}
switch (timer_num)
{
case 0:
TCA0.SINGLE.PER = pwmPeriod;
TCA0.SINGLE.CTRLA = clockSelectBits << 1;
break;
}
}
void start() {
switch (timer_num)
{
case 0:
bitSet(TCA0.SINGLE.CTRLA, 0);
break;
}
}
void stop() {
switch (timer_num)
{
case 0:
bitClear(TCA0.SINGLE.CTRLA, 0);
break;
}
}
void attachInterrupt(void (*isr)()) {
isrCallback = isr;
switch (timer_num)
{
case 0:
TCA0.SINGLE.INTCTRL = 0x1;
break;
}
}
void detachInterrupt() {
switch (timer_num)
{
case 0:
TCA0.SINGLE.INTCTRL = 0x0;
break;
}
}
};
extern Timer TimerA;
#endif

View File

@@ -1,129 +0,0 @@
#ifndef ATSAMC21Timer_h
#define ATSAMC21Timer_h
#include "../VirtualTimer.h"
#include <Arduino.h>
class Timer : public VirtualTimer
{
private:
int pwmPeriod;
unsigned long timer_resolution;
unsigned long lastMicroseconds;
public:
void (*isrCallback)();
Tcc* timer;
Timer(Tcc* timer) {
this->timer = timer;
if(timer == TCC0 || timer == TCC1) {
timer_resolution = 16777216;
} else {
timer_resolution = 65536;
}
lastMicroseconds = 0;
}
void initialize() {
if(timer == TCC0 || timer == TCC1) {
MCLK->APBCMASK.bit.TCC0_ = 1;
MCLK->APBCMASK.bit.TCC1_ = 1;
GCLK->GENCTRL[4].reg = ( GCLK_GENCTRL_DIV(2) | GCLK_GENCTRL_SRC_DPLL96M | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_OE );
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 4) & 1); // Wait for synchronization
GCLK->PCHCTRL[28].reg = ( GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(4) ); // 28 = TCC0_TCC1
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 4) & 1); // Wait for synchronization
}
else if (timer == TCC2) {
MCLK->APBCMASK.bit.TCC2_ = 1;
GCLK->GENCTRL[5].reg = ( GCLK_GENCTRL_DIV(2) | GCLK_GENCTRL_SRC_DPLL96M | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_OE );
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 5) & 1); // Wait for synchronization
GCLK->PCHCTRL[29].reg = ( GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(5) ); // 29 = TCC2
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 5) & 1); // Wait for synchronization
}
timer->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
while (timer->SYNCBUSY.bit.WAVE); // Wait for synchronization
}
void setPeriod(unsigned long microseconds) {
if(microseconds == lastMicroseconds)
return;
lastMicroseconds = microseconds;
const unsigned long cycles = F_CPU / 1000000 * microseconds; // cycles corresponds to how many clock ticks per microsecond times number of microseconds we want
timer->CTRLA.bit.PRESCALER = 0;
if(cycles < timer_resolution) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
pwmPeriod = cycles;
} else
if(cycles < timer_resolution * 2) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV2_Val);
pwmPeriod = cycles / 2;
} else
if(cycles < timer_resolution * 4) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV4_Val);
pwmPeriod = cycles / 4;
} else
if(cycles < timer_resolution * 8) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV8_Val);
pwmPeriod = cycles / 8;
} else
if(cycles < timer_resolution * 16) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV16_Val);
pwmPeriod = cycles / 16;
} else
if(cycles < timer_resolution * 64) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV64_Val);
pwmPeriod = cycles / 64;
} else
if(cycles < timer_resolution * 1024) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1024_Val);
pwmPeriod = cycles / 1024;
}
timer->PER.reg = pwmPeriod;
while (timer->SYNCBUSY.bit.PER);
}
void start() {
timer->CTRLA.bit.ENABLE = 1; // Turn on the output
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void stop() {
timer->CTRLA.bit.ENABLE = 0; // Turn on the output
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void attachInterrupt(void (*isr)()) {
isrCallback = isr; // Store the interrupt callback function
timer->INTENSET.reg = TCC_INTENSET_OVF; // Set the interrupt to occur on overflow
if(timer == TCC0) {
NVIC_EnableIRQ((IRQn_Type) TCC0_IRQn); // Enable the interrupt (clock is still off)
}
else if(timer == TCC1) {
NVIC_EnableIRQ((IRQn_Type) TCC1_IRQn); // Enable the interrupt (clock is still off)
}
else if(timer == TCC2) {
NVIC_EnableIRQ((IRQn_Type) TCC2_IRQn); // Enable the interrupt (clock is still off)
}
}
void detachInterrupt() {
if(timer == TCC0) {
NVIC_DisableIRQ((IRQn_Type) TCC0_IRQn); // Disable the interrupt
}
else if(timer == TCC1) {
NVIC_DisableIRQ((IRQn_Type) TCC1_IRQn); // Disable the interrupt
}
else if(timer == TCC2) {
NVIC_DisableIRQ((IRQn_Type) TCC2_IRQn); // Disable the interrupt
}
}
};
extern Timer TimerA;
extern Timer TimerB;
extern Timer TimerC;
#endif // ATSAMC21Timer_h

View File

@@ -1,144 +0,0 @@
#ifndef ATSAMD21GTimer_h
#define ATSAMD21GTimer_h
#include "../VirtualTimer.h"
#include <Arduino.h>
class Timer : public VirtualTimer
{
private:
int pwmPeriod;
unsigned long timer_resolution;
unsigned long lastMicroseconds;
public:
void (*isrCallback)();
Tcc* timer;
Timer(Tcc* timer) {
this->timer = timer;
if(timer == TCC0 || timer == TCC1) {
timer_resolution = 16777216;
} else {
timer_resolution = 65536;
}
lastMicroseconds = 0;
}
void initialize() {
if(timer == TCC0 || timer == TCC1) {
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
GCLK_GENDIV_ID(4); // Apply to GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
}
else if (timer == TCC2) {
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
GCLK_GENDIV_ID(5); // Apply to GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(5); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
5 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed GCLK to TCC0/1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
}
timer->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
while (timer->SYNCBUSY.bit.WAVE); // Wait for synchronization
}
void setPeriod(unsigned long microseconds) {
if(microseconds == lastMicroseconds)
return;
lastMicroseconds = microseconds;
const unsigned long cycles = F_CPU / 1000000 * microseconds; // cycles corresponds to how many clock ticks per microsecond times number of microseconds we want
if(cycles < timer_resolution) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
pwmPeriod = cycles;
} else
if(cycles < timer_resolution * 2) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV2_Val);
pwmPeriod = cycles / 2;
} else
if(cycles < timer_resolution * 4) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV4_Val);
pwmPeriod = cycles / 4;
} else
if(cycles < timer_resolution * 8) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV8_Val);
pwmPeriod = cycles / 8;
} else
if(cycles < timer_resolution * 16) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV16_Val);
pwmPeriod = cycles / 16;
} else
if(cycles < timer_resolution * 64) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV64_Val);
pwmPeriod = cycles / 64;
} else
if(cycles < timer_resolution * 1024) {
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1024_Val);
pwmPeriod = cycles / 1024;
}
timer->PER.reg = pwmPeriod;
while (timer->SYNCBUSY.bit.PER);
}
void start() {
timer->CTRLA.bit.ENABLE = 1; // Turn on the output
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void stop() {
timer->CTRLA.bit.ENABLE = 0; // Turn on the output
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void attachInterrupt(void (*isr)()) {
isrCallback = isr; // Store the interrupt callback function
timer->INTENSET.reg = TCC_INTENSET_OVF; // Set the interrupt to occur on overflow
if(timer == TCC0) {
NVIC_EnableIRQ((IRQn_Type) TCC0_IRQn); // Enable the interrupt (clock is still off)
}
else if(timer == TCC1) {
NVIC_EnableIRQ((IRQn_Type) TCC1_IRQn); // Enable the interrupt (clock is still off)
}
else if(timer == TCC2) {
NVIC_EnableIRQ((IRQn_Type) TCC2_IRQn); // Enable the interrupt (clock is still off)
}
}
void detachInterrupt() {
if(timer == TCC0) {
NVIC_DisableIRQ((IRQn_Type) TCC0_IRQn); // Disable the interrupt
}
else if(timer == TCC1) {
NVIC_DisableIRQ((IRQn_Type) TCC1_IRQn); // Disable the interrupt
}
else if(timer == TCC2) {
NVIC_DisableIRQ((IRQn_Type) TCC2_IRQn); // Disable the interrupt
}
}
};
extern Timer TimerA;
extern Timer TimerB;
extern Timer TimerC;
#endif

View File

@@ -1,112 +0,0 @@
/*
* AnalogReadFast.h
*
* Copyright (C) 2016 Albert van Dalen http://www.avdweb.nl
*
* This file is part of CommandStation.
*
* CommandStation 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.
*
* CommandStation 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 COMMANDSTATION_DCC_ANALOGREADFAST_H_
#define COMMANDSTATION_DCC_ANALOGREADFAST_H_
#include <Arduino.h>
int inline analogReadFast(uint8_t ADCpin);
#if defined(ARDUINO_ARCH_SAMD)
int inline analogReadFast(uint8_t ADCpin)
{ ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
int CTRLBoriginal = ADC->CTRLB.reg;
int AVGCTRLoriginal = ADC->AVGCTRL.reg;
int SAMPCTRLoriginal = ADC->SAMPCTRL.reg;
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64; // divide Clock by 64
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization
int adc = analogRead(ADCpin);
ADC->CTRLB.reg = CTRLBoriginal;
ADC->AVGCTRL.reg = AVGCTRLoriginal;
ADC->SAMPCTRL.reg = SAMPCTRLoriginal;
return adc;
}
#elif defined(ARDUINO_ARCH_SAMC)
int inline analogReadFast(uint8_t ADCpin)
{
Adc* ADC;
if ( (g_APinDescription[ADCpin].ulPeripheralAttribute & PER_ATTR_ADC_MASK) == PER_ATTR_ADC_STD ) {
ADC = ADC0;
} else {
ADC = ADC1;
}
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->SYNCBUSY.bit.ENABLE == 1 ); // wait for synchronization
int CTRLBoriginal = ADC->CTRLB.reg;
int AVGCTRLoriginal = ADC->AVGCTRL.reg;
int SAMPCTRLoriginal = ADC->SAMPCTRL.reg;
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64; // divide Clock by 64
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while(ADC->SYNCBUSY.bit.ENABLE == 1); // wait for synchronization
int adc = analogRead(ADCpin);
ADC->CTRLB.reg = CTRLBoriginal;
ADC->AVGCTRL.reg = AVGCTRLoriginal;
ADC->SAMPCTRL.reg = SAMPCTRLoriginal;
return adc;
}
#elif defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_AVR_NANO_EVERY)
int inline analogReadFast(uint8_t ADCpin)
{ byte ADC0CTRLCoriginal = ADC0.CTRLC;
ADC0.CTRLC = (ADC0CTRLCoriginal & 0b00110000) + 0b01000011;
int adc = analogRead(ADCpin);
ADC0.CTRLC = ADC0CTRLCoriginal;
return adc;
}
#else
int inline analogReadFast(uint8_t ADCpin)
{ byte ADCSRAoriginal = ADCSRA;
ADCSRA = (ADCSRA & B11111000) | 4;
int adc = analogRead(ADCpin);
ADCSRA = ADCSRAoriginal;
return adc;
}
#endif
#endif // COMMANDSTATION_DCC_ANALOGREADFAST_H_

View File

@@ -1,24 +0,0 @@
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
// All Credit and copyright David Cutting
// The files included below come from the same source.
// This library had been included with the DCC code to avoid issues with
// library management for inexperienced users. "It just works (TM)"
#ifndef ArduinoTimers_h
#define ArduinoTimers_h
#if defined(SAMC21)
#include "ATSAMC21G/Timer.h"
#elif defined(ARDUINO_SAMD_ZERO)
#include "ATSAMD21G/Timer.h"
#elif defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#include "ATMEGA2560/Timer.h"
#elif defined(ARDUINO_AVR_UNO)
#include "ATMEGA328/Timer.h"
#elif defined(ARDUINO_ARCH_MEGAAVR)
#include "ATMEGA4809/Timer.h"
#else
#error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags."
#endif
#endif

62
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,62 @@
# Contributing
Thanks for considering contributing to our project. Here is a guide for how to get started and and a list of our conventions. We will also walk you through the Github command line and Desktop commands necessary to download the code, make changes, and get it included in a next version of the sofware.
Before contributing to this repository, please first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change.
Find us on our website at https://dcc-ex.com, on our Discord https://discord.gg/y2sB4Fp or on Trainboard: https://www.trainboard.com/highball/index.php?threads/dcc-update-project-2020.130071/
# Development Environment
We recommend using PlatformIO IDE for VSCode. If you haven't yet used it, it is an easy to learn and easy to use IDE that really shines for embedded development and the Arduino based hardware we use. For more information go to https://platformio.org/
* Download and install the latest version of the Arduino IDE
* Download and install the latest version of Visual Studio Code from Microsoft
* Run VSCode and click on the "extensions" icon on the left. Install "PlatformIO IDE for VSCode" and the "Arduino Framework" support
If you don't see C/C++ Installed in the list, install that too. We also recomment installing the Gitlens extension to make working with Git and GitHub even easier.
You may ask if you can use the Arduino IDE, Visual Studio, or even a text editor and the answer is "of course" if you know what you are doing. Since you are just changing text files, you can use whatever you like as long as your commits and pull requests can be merged in GitHub. However, it will be much easier to follow our coding standards if you have an IDE that can automatically format things for you.
# Coding Style Guidelines
We have adopted the Google style guidlines. In particular please make sure to adhere to these standards:
1. All header files should have #define guards to prevent multiple inclusion.
2. Use Unix style line endings
3. We indent using two spaces (soft tabs)
4. Braces
For more information just check our code or read https://google.github.io/styleguide/cppguide.html#C++_Version
## Using the Repository
1. Clone the repository on your local machine
2. Create a working branch using the format "username-featurename" ex: "git branch -b frightrisk-turnouts"
3. Commit offen, ex: "git add ." and then "git commit -m "description of your changes"
4. Push your changes to our repository "git push"
5. When you are ready, issue a pull request for your changes to be merged into the main branch
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
## Code of Conduct
Be Nice
### Enforcement
Contributors who do not follow the be nice rule in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## How Can I Contribute?
The DCC-EX Team has several projects and sub teams where you can help donate your epertise. See the sections below for the project or projects you are interested in.
### Development
### Documentation
### WebThrottle-EX
### Web Support
### Organization/Coordination
Links to external documentation goes here XXX

31
CommandDistributor.cpp Normal file
View File

@@ -0,0 +1,31 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "CommandDistributor.h"
#include "WiThrottle.h"
DCCEXParser * CommandDistributor::parser=0;
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
if (buffer[0] == '<') {
if (!parser) parser = new DCCEXParser();
parser->parse(streamer, buffer, streamer);
}
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
}

32
CommandDistributor.h Normal file
View File

@@ -0,0 +1,32 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef CommandDistributor_h
#define CommandDistributor_h
#include "DCCEXParser.h"
#include "RingStream.h"
class CommandDistributor {
public :
static void parse(byte clientId,byte* buffer, RingStream * streamer);
private:
static DCCEXParser * parser;
};
#endif

View File

@@ -1,74 +1,144 @@
////////////////////////////////////////////////////////////////////////////////////
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
//
// This file is the main sketch for the Command Station.
//
// CONFIGURATION:
// Configuration is normally performed by editing a file called config.h.
// This file is NOT shipped with the code so that if you pull a later version
// of the code, your configuration will not be overwritten.
//
// If you used the automatic installer program, config.h will have been created automatically.
//
// To obtain a starting copy of config.h please copy the file config.example.h which is
// shipped with the code and may be updated as new features are added.
//
// If config.h is not found, config.example.h will be used with all defaults.
////////////////////////////////////////////////////////////////////////////////////
#if __has_include ( "config.h")
#include "config.h"
#else
#warning config.h not found. Using defaults from config.example.h
#include "config.example.h"
#endif
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is a demonstattion of setting up a DCC-EX
* Command station to support direct connection of WiThrottle devices
* such as "Engine Driver". If you contriol your layout through JMRI
* then DON'T connect throttles to this wifi, connect them to JMRI.
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
*
* This is just 3 statements longer than the basic setup.
*
* THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT.
* REFER TO SEPARATE EXAMPLE.
*
* 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 "DCCEX.h"
#ifdef ARDUINO_AVR_UNO
#include <SoftwareSerial.h>
SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO
#define WIFI_BAUD 9600
#else
#define WIFI_BAUD 115200
#endif
// Create a serial command parser... Enables certain diagnostics and commands
// to be issued from the USB serial console
// This is NOT intended for JMRI....
DCCEXParser serialParser;
void setup() {
// Create a serial command parser for the USB connection,
// This supports JMRI or manual diagnostics and commands
// to be issued from the USB serial console.
DCCEXParser serialParser;
void setup()
{
// The main sketch has responsibilities during setup()
// Responsibility 1: Start the usb connection for diagnostics
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
Serial.begin(115200);
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
// NOTE: References to Serial1 are for the serial port used to connect
// your wifi chip/shield.
Serial1.begin(WIFI_BAUD);
WifiInterface::setup(Serial1, F("Your network name"), F("your network password"),F("DCCEX"),3532);
// 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
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
DCC::begin(STANDARD_MOTOR_SHIELD);
CONDITIONAL_LCD_START {
// This block is still executed for DIAGS if LCD not in use
LCD(0,F("DCC++ EX v%S"),F(VERSION));
LCD(1,F("Starting"));
}
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
#if WIFI_ON
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
#endif // WIFI_ON
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// 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
DCC::begin(MOTOR_SHIELD_TYPE);
#if defined(RMFT_ACTIVE)
RMFT::begin();
#endif
#if __has_include ( "mySetup.h")
#define SETUP(cmd) serialParser.parse(F(cmd))
#include "mySetup.h"
#undef SETUP
#endif
#if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#endif
LCD(1,F("Ready"));
}
void loop() {
void loop()
{
// The main sketch has responsibilities during loop()
// Responsibility 1: Handle DCC background processes
// (loco reminders and power checks)
DCC::loop();
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic
// Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON
WifiInterface::loop();
#endif
#if ETHERNET_ON
EthernetInterface::loop();
#endif
#if defined(RMFT_ACTIVE)
RMFT::loop();
#endif
#if defined(LCN_SERIAL)
LCN::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use
// Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = minimumFreeMemory();
if (freeNow < ramLowWatermark)
{
ramLowWatermark = freeNow;
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
}
}

464
DCC.cpp
View File

@@ -17,10 +17,13 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DIAG.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "DIAG.h"
#include "EEStore.h"
#include "GITHUB_SHA.h"
#include "version.h"
#include "FSH.h"
// This module is responsible for converting API calls into
// messages to be sent to the waveform generator.
@@ -41,9 +44,27 @@ const byte FN_GROUP_3=0x04;
const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
byte DCC::joinRelay=UNUSED_PIN;
byte DCC::globalSpeedsteps=128;
void DCC::begin(MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
shieldName=(FSH *)motorShieldName;
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
// Load stuff from EEprom
(void)EEPROM; // tell compiler not to warn this is unused
EEStore::init();
DCCWaveform::begin(mainDriver,progDriver);
}
void DCC::setJoinRelayPin(byte joinRelayPin) {
joinRelay=joinRelayPin;
if (joinRelay!=UNUSED_PIN) {
pinMode(joinRelay,OUTPUT);
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
}
}
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
@@ -57,19 +78,45 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
uint8_t b[4];
uint8_t nB = 0;
// DIAG(F("\nsetSpeedInternal %d %x"),cab,speedCode);
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
if (cab > 127)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
b[nB++] = SET_SPEED; // 128-step speed control byte
b[nB++] = speedCode; // for encoding see setThrottle
if (globalSpeedsteps <= 28) {
uint8_t speed128 = speedCode & 0x7F;
uint8_t speed28;
uint8_t code28;
if (speed128 == 0 || speed128 == 1) { // stop or emergency stop
code28 = speed128;
} else {
speed28= (speed128*10+36)/46; // convert 2-127 to 1-28
/*
if (globalSpeedsteps <= 14) // Don't want to do 14 steps, to get F0 there is ugly
code28 = (speed28+3)/2 | (Value of F0); // convert 1-28 to DCC 14 step speed code
else
*/
code28 = (speed28+3)/2 | ( (speed28 & 1) ? 0 : 0b00010000 ); // convert 1-28 to DCC 28 step speed code
}
// Construct command byte from:
// command speed direction
b[nB++] = 0b01000000 | code28 | ((speedCode & 0x80) ? 0b00100000 : 0);
} else { // 128 speedsteps
b[nB++] = SET_SPEED; // 128-step speed control byte
b[nB++] = speedCode; // for encoding see setThrottle
}
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
}
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
// DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2);
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
byte b[4];
byte nB = 0;
@@ -95,8 +142,29 @@ bool DCC::getThrottleDirection(int cab) {
}
// Set function to value on or off
void DCC::setFn( int cab, byte functionNumber, bool on) {
if (cab<=0 || functionNumber>28) return;
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
if (cab<=0 ) return;
if (functionNumber>28) {
//non reminding advanced binary bit set
byte b[5];
byte nB = 0;
if (cab > 127)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
if (functionNumber <= 127) {
b[nB++] = 0b11011101; // Binary State Control Instruction short form
b[nB++] = functionNumber | (on ? 0x80 : 0);
}
else {
b[nB++] = 0b11000000; // Binary State Control Instruction long form
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
b[nB++] = functionNumber >>7 ; // high order bits
}
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
return;
}
int reg = lookupSpeedTable(cab);
if (reg<0) return;
@@ -115,7 +183,7 @@ void DCC::setFn( int cab, byte functionNumber, bool on) {
// Change function according to how button was pressed,
// typically in WiThrottle.
// Returns new state or -1 if nothing was changed.
int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
int funcstate = -1;
if (cab<=0 || functionNumber>28) return funcstate;
int reg = lookupSpeedTable(cab);
@@ -137,17 +205,26 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
} else {
// toggle function on press, ignore release
if (pressed) {
speedTable[reg].functions ^= funcmask;
speedTable[reg].functions ^= funcmask;
}
funcstate = speedTable[reg].functions & funcmask;
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
}
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
return funcstate;
}
int DCC::getFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28) return -1; // unknown
int reg = lookupSpeedTable(cab);
if (reg<0) return -1;
unsigned long funcmask = (1UL<<functionNumber);
return (speedTable[reg].functions & funcmask)? 1 : 0;
}
// Set the group flag to say we have touched the particular group.
// A group will be reminded only if it has been touched.
void DCC::updateGroupflags(byte & flags, int functionNumber) {
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
byte groupMask;
if (functionNumber<=4) groupMask=FN_GROUP_1;
else if (functionNumber<=8) groupMask=FN_GROUP_2;
@@ -171,6 +248,10 @@ void DCC::setAccessory(int address, byte number, bool activate) {
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
}
//
// writeCVByteMain: Write a byte with PoM on main. This writes
// the 5 byte sized packet to implement this DCC function
//
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
byte b[5];
byte nB = 0;
@@ -185,6 +266,10 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
}
//
// writeCVBitMain: Write a bit of a byte with PoM on main. This writes
// the 5 byte sized packet to implement this DCC function
//
void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
byte b[5];
byte nB = 0;
@@ -203,17 +288,25 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
}
void DCC::setProgTrackSyncMain(bool on) {
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
DCCWaveform::progTrackSyncMain=on;
}
void DCC::setProgTrackBoost(bool on) {
DCCWaveform::progTrackBoosted=on;
}
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
FSH* DCC::getMotorShieldName() {
return shieldName;
}
const ackOp FLASH WRITE_BIT0_PROG[] = {
BASELINE,
W0,WACK,
V0, WACK, // validate bit is 0
ITC1, // if acked, callback(1)
FAIL // callback (-1)
};
const ackOp PROGMEM WRITE_BIT1_PROG[] = {
const ackOp FLASH WRITE_BIT1_PROG[] = {
BASELINE,
W1,WACK,
V1, WACK, // validate bit is 1
@@ -221,7 +314,7 @@ const ackOp PROGMEM WRITE_BIT1_PROG[] = {
FAIL // callback (-1)
};
const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
const ackOp FLASH VERIFY_BIT0_PROG[] = {
BASELINE,
V0, WACK, // validate bit is 0
ITC0, // if acked, callback(0)
@@ -229,7 +322,7 @@ const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
ITC1,
FAIL // callback (-1)
};
const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
const ackOp FLASH VERIFY_BIT1_PROG[] = {
BASELINE,
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
@@ -238,7 +331,7 @@ const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
FAIL // callback (-1)
};
const ackOp PROGMEM READ_BIT_PROG[] = {
const ackOp FLASH READ_BIT_PROG[] = {
BASELINE,
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
@@ -247,15 +340,15 @@ const ackOp PROGMEM READ_BIT_PROG[] = {
FAIL // bit not readable
};
const ackOp PROGMEM WRITE_BYTE_PROG[] = {
const ackOp FLASH WRITE_BYTE_PROG[] = {
BASELINE,
WB,WACK, // Write
VB,WACK, // validate byte
ITC1, // if ok callback (1)
WB,WACK,ITC1, // Write and callback(1) if ACK
// handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct
FAIL // callback (-1)
};
const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
const ackOp FLASH VERIFY_BYTE_PROG[] = {
BASELINE,
VB,WACK, // validate byte
ITCB, // if ok callback value
@@ -280,7 +373,7 @@ const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
FAIL };
const ackOp PROGMEM READ_CV_PROG[] = {
const ackOp FLASH READ_CV_PROG[] = {
BASELINE,
STARTMERGE, //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE
@@ -303,12 +396,33 @@ const ackOp PROGMEM READ_CV_PROG[] = {
FAIL }; // verification failed
const ackOp PROGMEM LOCO_ID_PROG[] = {
const ackOp FLASH LOCO_ID_PROG[] = {
BASELINE,
SETCV, (ackOp)1,
SETBIT, (ackOp)7,
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
SETCV, (ackOp)19, // CV 19 is consist setting
SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
SETBYTE, (ackOp)128,
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
STARTMERGE, // Setup to read cv 19
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
V1, WACK, NAKFAIL, // fast fail if no loco on track
// Long locoid
SETCV, (ackOp)17, // CV 17 is part of locoid
STARTMERGE,
@@ -340,7 +454,7 @@ const ackOp PROGMEM LOCO_ID_PROG[] = {
SKIPTARGET,
SETCV, (ackOp)1,
STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc
SETBIT, (ackOp)6, // skip over first bit as we know its a zero
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
@@ -352,61 +466,106 @@ const ackOp PROGMEM LOCO_ID_PROG[] = {
FAIL
};
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE,
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore dedcoder without cv19 support
// Turn off long address flag
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W0,WACK,
V0,WACK,NAKFAIL,
SETCV, (ackOp)1,
SETBYTEL, // low byte of word
WB,WACK, // some decoders don't ACK writes
VB,WACK,ITCB,
FAIL
};
// On the following prog-track functions blocking defaults to false.
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
// During that wait, other parts of the system will be unresponsive.
// blocking =false means the callback will be called some time after the API returns (typically a few tenths of a second)
// but that would be very inconvenient in a Wifi situaltion where the stream becomes
// unuavailable immediately after the API rerturns.
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore decoder without cv19 support
// Turn on long address flag cv29 bit 5
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W1,WACK,
V1,WACK,NAKFAIL,
// Store high byte of address in cv 17
SETCV, (ackOp)17,
SETBYTEH, // high byte of word
WB,WACK,
VB,WACK,NAKFAIL,
// store
SETCV, (ackOp)18,
SETBYTEL, // low byte of word
WB,WACK,
VB,WACK,ITC1, // callback(1) means Ok
FAIL
};
void DCC::writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback, blocking);
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
}
void DCC::writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback, blocking);
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
}
void DCC::verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback, blocking);
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
}
void DCC::verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback, blocking);
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
}
void DCC::readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking) {
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
if (bitNum >= 8) callback(-1);
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback, blocking);
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
}
void DCC::readCV(int cv, ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(cv, 0,READ_CV_PROG, callback, blocking);
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
}
void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
void DCC::getLocoId(ACK_CALLBACK callback) {
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
if (id<1 || id>10239) { //0x27FF according to standard
callback(-1);
return;
}
if (id<=127)
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
else
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, 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
}
void DCC::forgetAllLocos() { // removes all speed reminders
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
setThrottle2(0,1); // ESTOP all locos still on track
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
}
byte DCC::loopStatus=0;
void DCC::loop() {
DCCWaveform::loop(); // power overload checks
ackManagerLoop(false); // maintain prog track ack manager
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
ackManagerLoop(); // maintain prog track ack manager
issueReminders();
}
@@ -434,20 +593,20 @@ bool DCC::issueReminder(int reg) {
switch (loopStatus) {
case 0:
// DIAG(F("\nReminder %d speed %d"),loco,speedTable[reg].speedCode);
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
setThrottle2(loco, speedTable[reg].speedCode);
break;
case 1: // remind function group 1 (F0-F4)
if (flags & FN_GROUP_1)
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4));
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
break;
case 2: // remind function group 2 F5-F8
if (flags & FN_GROUP_2)
setFunctionInternal(loco,0, 176 + ((functions>>5)& 0x0F));
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
break;
case 3: // remind function group 3 F9-F12
if (flags & FN_GROUP_3)
setFunctionInternal(loco,0, 160 + ((functions>>9)& 0x0F));
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
break;
case 4: // remind function group 4 F13-F20
if (flags & FN_GROUP_4)
@@ -492,7 +651,7 @@ int DCC::lookupSpeedTable(int locoId) {
}
if (reg == MAX_LOCOS) reg = firstEmpty;
if (reg >= MAX_LOCOS) {
DIAG(F("\nToo many locos\n"));
DIAG(F("Too many locos"));
return -1;
}
if (reg==firstEmpty){
@@ -526,79 +685,97 @@ int DCC::nextLoco = 0;
ackOp const * DCC::ackManagerProg;
byte DCC::ackManagerByte;
byte DCC::ackManagerStash;
int DCC::ackManagerCv;
int DCC::ackManagerWord;
int DCC::ackManagerCv;
byte DCC::ackManagerBitNum;
bool DCC::ackReceived;
bool DCC::ackManagerRejoin;
CALLBACK_STATE DCC::callbackState=READY;
ACK_CALLBACK DCC::ackManagerCallback;
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
callback(-2);
return;
}
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
if (ackManagerRejoin ) {
// Change from JOIN must zero resets packet.
setProgTrackSyncMain(false);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
DCCWaveform::progTrack.autoPowerOff=false;
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on"));
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
ackManagerCv = cv;
ackManagerProg = program;
ackManagerByte = byteValueOrBitnum;
ackManagerBitNum=byteValueOrBitnum;
ackManagerCallback = callback;
if (blocking) ackManagerLoop(blocking);
}
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
ackManagerWord=wordval;
ackManagerSetup(0, 0, program, callback);
}
const byte RESET_MIN=8; // tuning of reset counter before sending message
// checkRessets return true if the caller should yield back to loop and try later.
bool DCC::checkResets(bool blocking, uint8_t numResets) {
if (blocking) {
// must block waiting for restest to be issued
while(DCCWaveform::progTrack.sentResetsSincePacket < numResets);
return false; // caller need not yield
}
bool DCC::checkResets(uint8_t numResets) {
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
}
void DCC::ackManagerLoop(bool blocking) {
void DCC::ackManagerLoop() {
while (ackManagerProg) {
byte opcode=pgm_read_byte_near(ackManagerProg);
byte opcode=GETFLASH(ackManagerProg);
// breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
// if blocking then we must ONLY return AFTER callback issued
switch (opcode) {
case BASELINE:
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
if (Diag::ACK) DIAG(F("\nAuto Prog power on"));
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
DCCWaveform::progTrack.autoPowerOff=true;
return;
}
if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
DCCWaveform::progTrack.setAckBaseline();
callbackState=READY;
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
if (checkResets(blocking, RESET_MIN)) return;
if (Diag::ACK) DIAG(F("\nW%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case WB: // write byte
{
if (checkResets(blocking, RESET_MIN)) return;
if (Diag::ACK) DIAG(F("\nWB cv=%d value=%d"),ackManagerCv,ackManagerByte);
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets(blocking, RESET_MIN)) return;
if (Diag::ACK) DIAG(F("\nVB cv=%d value=%d"),ackManagerCv,ackManagerByte);
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
@@ -608,8 +785,8 @@ void DCC::ackManagerLoop(bool blocking) {
case V0:
case V1: // Issue validate bit=0 or bit=1 packet
{
if (checkResets(blocking, RESET_MIN)) return;
if (Diag::ACK) DIAG(F("\nV%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
@@ -620,44 +797,43 @@ void DCC::ackManagerLoop(bool blocking) {
case WACK: // wait for ack (or absence of ack)
{
byte ackState=2; // keep polling
if (blocking) {
while(ackState==2) ackState=DCCWaveform::progTrack.getAck();
}
else {
ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling
}
ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling
ackReceived=ackState==1;
break; // we have a genuine ACK result
}
case ITC0:
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(opcode==ITC0?0:1);
callback(opcode==ITC0?0:1);
return;
}
break;
case ITCB: // If True callback(byte)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(ackManagerByte);
callback(ackManagerByte);
return;
}
break;
case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) {
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
ackManagerProg = NULL; // all done now
callback(-1);
callback(-1);
return;
}
break;
case FAIL: // callback(-1)
ackManagerProg = NULL;
callback(-1);
callback(-1);
return;
case STARTMERGE:
@@ -674,12 +850,25 @@ void DCC::ackManagerLoop(bool blocking) {
case SETBIT:
ackManagerProg++;
ackManagerBitNum=pgm_read_byte_near(ackManagerProg);
ackManagerBitNum=GETFLASH(ackManagerProg);
break;
case SETCV:
ackManagerProg++;
ackManagerCv=pgm_read_byte_near(ackManagerProg);
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
@@ -688,7 +877,6 @@ void DCC::ackManagerLoop(bool blocking) {
case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18
ackManagerProg=NULL;
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
return;
@@ -697,14 +885,13 @@ void DCC::ackManagerLoop(bool blocking) {
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=pgm_read_byte_near(ackManagerProg);
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:
DIAG(F("\n!! ackOp %d FAULT!!"),opcode);
ackManagerProg=NULL;
DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1);
return;
@@ -712,25 +899,70 @@ void DCC::ackManagerLoop(bool blocking) {
ackManagerProg++;
}
}
void DCC::callback(int value) {
if (DCCWaveform::progTrack.autoPowerOff) {
if (Diag::ACK) DIAG(F("\nAuto Prog power off"));
DCCWaveform::progTrack.doAutoPowerOff();
static unsigned long callbackStart;
// We are about to leave programming mode
// Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) {
case AFTER_WRITE: // first attempt to callback after a write operation
callbackStart=millis();
callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS"));
break;
case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS
// If we are going to power off anyway, it doesnt matter
// but if we will keep the power on, we must off it for 30mS
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
}
break;
case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break;
//power has been off for 30mS
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
callbackState=READY;
break;
case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on
if (DCCWaveform::progTrack.autoPowerOff) {
if (Diag::ACK) DIAG(F("Auto Prog power off"));
DCCWaveform::progTrack.doAutoPowerOff();
}
// Restore <1 JOIN> to state before BASELINE
if (ackManagerRejoin) {
setProgTrackSyncMain(true);
if (Diag::ACK) DIAG(F("Auto JOIN"));
}
ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value);
}
if (Diag::ACK) DIAG(F("\nCallback(%d)\n"),value);
(ackManagerCallback)( value);
}
void DCC::displayCabList(Print * stream) {
void DCC::displayCabList(Print * stream) {
int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("\ncab=%d, speed=%d, dir=%c "),
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
}
}
StringFormatter::send(stream,F("\nUsed=%d, max=%d\n"),used,MAX_LOCOS);
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
}

222
DCC.h
View File

@@ -21,121 +21,179 @@
#include <Arduino.h>
#include "MotorDriver.h"
#include "MotorDrivers.h"
#include "FSH.h"
typedef void (*ACK_CALLBACK)(int result);
typedef void (*ACK_CALLBACK)(int16_t result);
enum ackOp { // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
SETBIT, // sets bit number to next prog byte
SETCV, // sets cv number to next prog byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
SKIPTARGET=0xFF // jump to target
enum ackOp : byte
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
SETBIT, // sets bit number to next prog byte
SETCV, // sets cv number to next prog byte
SETBYTE, // sets current byte to next prog byte
SETBYTEH, // sets current byte to word high byte
SETBYTEL, // sets current byte to word low byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
SKIPTARGET = 0xFF // jump to target
};
enum CALLBACK_STATE : byte {
AFTER_WRITE, // Start callback sequence after something was written to the decoder
WAITING_100, // Waiting for 100mS of stable power
WAITING_30, // waiting to 30ms of power off gap.
READY, // Ready to complete callback
};
// Allocations with memory implications..!
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
#ifdef ARDUINO_AVR_UNO
const byte MAX_LOCOS=20;
#else
const byte MAX_LOCOS=50;
#endif
#ifdef ARDUINO_AVR_UNO
const byte MAX_LOCOS = 20;
#else
const byte MAX_LOCOS = 50;
#endif
class DCC {
public:
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1);
class DCC
{
public:
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
static void setJoinRelayPin(byte joinRelayPin);
static void loop();
// Public DCC API functions
static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection);
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
static uint8_t getThrottleSpeed(int cab);
static bool getThrottleDirection(int cab);
static void writeCVByteMain(int cab, int cv, byte bValue);
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
static void setFunction( int cab, byte fByte, byte eByte);
static void setFn( int cab, byte functionNumber, bool on);
static int changeFn( int cab, byte functionNumber, bool pressed);
static void updateGroupflags(byte & flags, int functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate) ;
static bool writeTextPacket( byte *b, int nBytes);
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
static void readCV(int cv, ACK_CALLBACK callback, bool blocking=false);
static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking=false); // -1 for error
static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ;
static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false);
static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ;
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false);
static void getLocoId(ACK_CALLBACK callback, bool blocking=false);
static void setFunction(int cab, byte fByte, byte eByte);
static void setFn(int cab, int16_t functionNumber, bool on);
static int changeFn(int cab, int16_t functionNumber, bool pressed);
static int getFn(int cab, int16_t functionNumber);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate);
static bool writeTextPacket(byte *b, int nBytes);
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
static void readCV(int16_t cv, ACK_CALLBACK callback);
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
static void writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
static void getLocoId(ACK_CALLBACK callback);
static void setLocoId(int id,ACK_CALLBACK callback);
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders
static void displayCabList(Print * stream);
private:
struct LOCO {
int loco;
byte speedCode;
byte groupFlags;
unsigned long functions;
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders
static void displayCabList(Print *stream);
static FSH *getMotorShieldName();
static inline void setGlobalSpeedsteps(byte s) {
globalSpeedsteps = s;
};
private:
struct LOCO
{
int loco;
byte speedCode;
byte groupFlags;
unsigned long functions;
};
static byte joinRelay;
static byte loopStatus;
static void setThrottle2( uint16_t cab, uint8_t speedCode);
static void setThrottle2(uint16_t cab, uint8_t speedCode);
static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal( int cab, byte fByte, byte eByte);
static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg);
static int nextLoco;
static FSH *shieldName;
static byte globalSpeedsteps;
static LOCO speedTable[MAX_LOCOS];
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
static int lookupSpeedTable(int locoId);
static void issueReminders();
static void callback(int value);
// ACK MANAGER
static ackOp const * ackManagerProg;
static byte ackManagerByte;
static byte ackManagerBitNum;
static int ackManagerCv;
static byte ackManagerStash;
// ACK MANAGER
static ackOp const *ackManagerProg;
static byte ackManagerByte;
static byte ackManagerBitNum;
static int ackManagerCv;
static int ackManagerWord;
static byte ackManagerStash;
static bool ackReceived;
static ACK_CALLBACK ackManagerCallback;
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
static void ackManagerLoop(bool blocking);
static bool checkResets(bool blocking, uint8_t numResets);
static const int PROG_REPEATS=8; // repeats of programming commands (some decoders need at least 8 to be reliable)
static bool ackManagerRejoin;
static ACK_CALLBACK ackManagerCallback;
static CALLBACK_STATE callbackState;
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
static void ackManagerLoop();
static bool checkResets( uint8_t numResets);
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
// NMRA codes #
static const byte SET_SPEED=0x3f;
static const byte SET_SPEED = 0x3f;
static const byte WRITE_BYTE_MAIN = 0xEC;
static const byte WRITE_BIT_MAIN = 0xE8;
static const byte WRITE_BYTE = 0x7C;
static const byte VERIFY_BYTE= 0x74;
static const byte BIT_MANIPULATE=0x78;
static const byte WRITE_BIT=0xF0;
static const byte VERIFY_BIT=0xE0;
static const byte BIT_ON=0x08;
static const byte BIT_OFF=0x00;
static const byte VERIFY_BYTE = 0x74;
static const byte BIT_MANIPULATE = 0x78;
static const byte WRITE_BIT = 0xF0;
static const byte VERIFY_BIT = 0xE0;
static const byte BIT_ON = 0x08;
static const byte BIT_OFF = 0x00;
};
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
#define ARDUINO_AVR_MEGA2560
#endif
#if defined(ARDUINO_AVR_UNO)
#define ARDUINO_TYPE "UNO"
#elif defined(ARDUINO_AVR_NANO)
#define ARDUINO_TYPE "NANO"
#elif defined(ARDUINO_AVR_MEGA2560)
#define ARDUINO_TYPE "MEGA"
#elif defined(ARDUINO_ARCH_MEGAAVR)
#define ARDUINO_TYPE "MEGAAVR"
#elif defined(ARDUINO_TEENSY32)
#define ARDUINO_TYPE "TEENSY32"
#elif defined(ARDUINO_TEENSY35)
#define ARDUINO_TYPE "TEENSY35"
#elif defined(ARDUINO_TEENSY36)
#define ARDUINO_TYPE "TEENSY36"
#elif defined(ARDUINO_TEENSY40)
#define ARDUINO_TYPE "TEENSY40"
#elif defined(ARDUINO_TEENSY41)
#define ARDUINO_TYPE "TEENSY41"
#else
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
#endif
#endif

20
DCCEX.h
View File

@@ -1,7 +1,25 @@
// This include is intended to visually simplify the .ino for the end users.
// If there were any #ifdefs required they are much better handled in here.
#ifndef DCCEX_h
#define DCCEX_h
#include "defines.h"
#include "DCC.h"
#include "DIAG.h"
#include "DCCEXParser.h"
#include "version.h"
#include "WifiInterface.h"
#endif
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#endif
#include "LCD_Implementation.h"
#include "LCN.h"
#include "freeMemory.h"
#if __has_include ( "myAutomation.h")
#include "RMFT.h"
#define RMFT_ACTIVE
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -19,47 +19,60 @@
#ifndef DCCEXParser_h
#define DCCEXParser_h
#include <Arduino.h>
#include "FSH.h"
#include "RingStream.h"
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
struct DCCEXParser
{
DCCEXParser();
void loop(Stream & stream);
void parse(Print * stream, byte * command, bool blocking);
void parse(Print * stream, byte * command, RingStream * ringStream);
void parse(const FSH * cmd);
void flush();
static void setFilter(FILTER_CALLBACK filter);
static const int MAX_PARAMS=10; // Must not exceed this
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
private:
static const int MAX_BUFFER=50; // longest command sent in
static const int16_t MAX_BUFFER=50; // longest command sent in
byte bufferLength=0;
bool inCommandPayload=false;
bool asyncBanned; // true when called with stream that must complete before returning
byte buffer[MAX_BUFFER+2];
int splitValues( int result[MAX_PARAMS], const byte * command);
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
bool parseT(Print * stream, int params, int p[]);
bool parseZ(Print * stream, int params, int p[]);
bool parseS(Print * stream, int params, int p[]);
bool parsef(Print * stream, int params, int p[]);
bool parseD(Print * stream, int params, int p[]);
bool parseT(Print * stream, int16_t params, int16_t p[]);
bool parseZ(Print * stream, int16_t params, int16_t p[]);
bool parseS(Print * stream, int16_t params, int16_t p[]);
bool parsef(Print * stream, int16_t params, int16_t p[]);
bool parseD(Print * stream, int16_t params, int16_t p[]);
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
static bool stashBusy;
static byte stashTarget;
static Print * stashStream;
static int stashP[MAX_PARAMS];
bool stashCallback(Print * stream, int p[MAX_PARAMS]);
static void callback_W(int result);
static void callback_B(int result);
static void callback_R(int result);
static void callback_Rloco(int result);
static void callback_Vbit(int result);
static void callback_Vbyte(int result);
static RingStream * stashRingStream;
static int16_t stashP[MAX_COMMAND_PARAMS];
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
static void callback_W(int16_t result);
static void callback_B(int16_t result);
static void callback_R(int16_t result);
static void callback_Rloco(int16_t result);
static void callback_Wloco(int16_t result);
static void callback_Vbit(int16_t result);
static void callback_Vbyte(int16_t result);
static FILTER_CALLBACK filterCallback;
static void funcmap(int cab, byte value, byte fstart, byte fstop);
static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
};

213
DCCTimer.cpp Normal file
View File

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

20
DCCTimer.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef DCCTimer_h
#define DCCTimer_h
#include "Arduino.h"
typedef void (*INTERRUPT_CALLBACK)();
class DCCTimer {
public:
static void begin(INTERRUPT_CALLBACK interrupt);
static void getSimulatedMacAddress(byte mac[6]);
static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high);
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
static void read_mac(byte mac[6]);
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
#endif
private:
};
#endif

View File

@@ -17,66 +17,67 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma GCC optimize ("-O3")
#include <Arduino.h>
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "DIAG.h"
const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;
#include "freeMemory.h"
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
bool DCCWaveform::progTrackSyncMain=false;
VirtualTimer * DCCWaveform::interruptTimer=NULL;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
bool DCCWaveform::progTrackBoosted=false;
int DCCWaveform::progTripValue=0;
volatile uint8_t DCCWaveform::numAckGaps=0;
volatile uint8_t DCCWaveform::numAckSamples=0;
uint8_t DCCWaveform::trailingEdgeCounter=0;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
mainTrack.motorDriver=mainDriver;
progTrack.motorDriver=progDriver;
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
mainTrack.setPowerMode(POWERMODE::OFF);
progTrack.setPowerMode(POWERMODE::OFF);
switch (timerNumber) {
case 1: interruptTimer= &TimerA; break;
case 2: interruptTimer= &TimerB; break;
#ifndef ARDUINO_AVR_UNO
case 3: interruptTimer= &TimerC; break;
#endif
default:
DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber);
return;
}
interruptTimer->initialize();
interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
interruptTimer->attachInterrupt(interruptHandler);
interruptTimer->start();
}
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
interruptTimer->start();
DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET"));
// Fault pin config for odd motor boards (example pololu)
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
&& (mainDriver->getFaultPin() != UNUSED_PIN));
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
if (MotorDriver::usePWM)
DIAG(F("Signal pin config: high accuracy waveform"));
else
DIAG(F("Signal pin config: normal accuracy waveform"));
DCCTimer::begin(DCCWaveform::interruptHandler);
}
void DCCWaveform::loop() {
mainTrack.checkPowerOverload();
progTrack.checkPowerOverload();
void DCCWaveform::loop(bool ackManagerActive) {
mainTrack.checkPowerOverload(false);
progTrack.checkPowerOverload(ackManagerActive);
}
// static //
void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack
bool mainCall2 = mainTrack.interrupt1();
bool progCall2 = progTrack.interrupt1();
// member functions would be cleaner but have more overhead
byte sigMain=signalTransform[mainTrack.state];
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
// Set the signal state for both tracks
mainTrack.motorDriver->setSignal(sigMain);
progTrack.motorDriver->setSignal(sigProg);
// Move on in the state engine
mainTrack.state=stateTransform[mainTrack.state];
progTrack.state=stateTransform[progTrack.state];
// WAVE_PENDING means we dont yet know what the next bit is
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
else if (progTrack.ackPending) progTrack.checkAck();
// call (if necessary) the procs to get the current bits
// these must complete within 50microsecs of the interrupt
// but they are only called ONCE PER BIT TRANSMITTED
// after the rising edge of the signal
if (mainCall2) mainTrack.interrupt2();
if (progCall2) progTrack.interrupt2();
}
@@ -91,13 +92,12 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
// establish appropriate pins
isMainTrack = isMain;
packetPending = false;
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
state = 0;
state = WAVE_START;
// The +1 below is to allow the preamble generator to create the stop bit
// fpr the previous packet.
// for the previous packet.
requiredPreambles = preambleBits+1;
bytes_sent = 0;
bits_sent = 0;
@@ -111,22 +111,18 @@ POWERMODE DCCWaveform::getPowerMode() {
}
void DCCWaveform::setPowerMode(POWERMODE mode) {
// Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away.
if (!interruptTimer) return;
powerMode = mode;
bool ison = (mode == POWERMODE::ON);
motorDriver->setPower( ison);
}
void DCCWaveform::checkPowerOverload() {
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= motorDriver->rawCurrentTripValue;
if (!isMainTrack && (ackPending || progTrackSyncMain)) tripValue=ACK_CURRENT_TRIP;
int tripValue= motorDriver->getRawCurrentTripValue();
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
tripValue=progTripValue;
switch (powerMode) {
case POWERMODE::OFF:
@@ -134,8 +130,26 @@ void DCCWaveform::checkPowerOverload() {
break;
case POWERMODE::ON:
// Check current
lastCurrent = motorDriver->getCurrentRaw();
if (lastCurrent <= tripValue) {
lastCurrent=motorDriver->getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (MotorDriver::commonFaultPin) {
if (lastCurrent <= tripValue) {
setPowerMode(POWERMODE::ON); // maybe other track
}
// Write this after the fact as we want to turn on as fast as possible
// because we don't know which output actually triggered the fault pin
DIAG(F("*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***"), isMainTrack ? F("MAIN") : F("PROG"));
} else {
DIAG(F("*** %S FAULT PIN ACTIVE - OVERLOAD ***"), isMainTrack ? F("MAIN") : F("PROG"));
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
}
}
if (lastCurrent < tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
@@ -143,85 +157,63 @@ void DCCWaveform::checkPowerOverload() {
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
} else {
setPowerMode(POWERMODE::OVERLOAD);
unsigned int mA=motorDriver->convertToMilliamps(lastCurrent);
unsigned int maxmA=motorDriver->convertToMilliamps(tripValue);
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait);
unsigned int mA=motorDriver->raw2mA(lastCurrent);
unsigned int maxmA=motorDriver->raw2mA(tripValue);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
power_sample_overload_wait *= 2;
DIAG(F("*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("*** %S TRACK POWER RESET delay=%d ***"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay);
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
}
// For each state of the wave nextState=stateTransform[currentState]
const WAVE_STATE DCCWaveform::stateTransform[]={
/* WAVE_START -> */ WAVE_PENDING,
/* WAVE_MID_1 -> */ WAVE_START,
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
/* WAVE_MID_0 -> */ WAVE_LOW_0,
/* WAVE_LOW_0 -> */ WAVE_START,
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
// process time-edge sensitive part of interrupt
// return true if second level required
bool DCCWaveform::interrupt1() {
// NOTE: this must consume transmission buffers even if the power is off
// otherwise can cause hangs in main loop waiting for the pendingBuffer.
switch (state) {
case 0: // start of bit transmission
setSignal(HIGH);
state = 1;
return true; // must call interrupt2 to set currentBit
case 1: // 58us after case 0
if (currentBit) {
setSignal(LOW);
state = 0;
}
else state = 2;
break;
case 2: // 116us after case 0
setSignal(LOW);
state = 3;
break;
case 3: // finished sending zero bit
state = 0;
break;
}
// ACK check is prog track only and will only be checked if
// this is not case(0) which needs relatively expensive packet change code to be called.
if (ackPending) checkAck();
return false;
}
void DCCWaveform::setSignal(bool high) {
if (progTrackSyncMain) {
if (!isMainTrack) return; // ignore PROG track waveform while in sync
// set both tracks to same signal
motorDriver->setSignal(high);
progTrack.motorDriver->setSignal(high);
return;
}
motorDriver->setSignal(high);
}
// For each state of the wave, signal pin is HIGH or LOW
const bool DCCWaveform::signalTransform[]={
/* WAVE_START -> */ HIGH,
/* WAVE_MID_1 -> */ LOW,
/* WAVE_HIGH_0 -> */ HIGH,
/* WAVE_MID_0 -> */ LOW,
/* WAVE_LOW_0 -> */ LOW,
/* WAVE_PENDING (should not happen) -> */ LOW};
void DCCWaveform::interrupt2() {
// set currentBit to be the next bit to be sent.
// calculate the next bit to be sent:
// set state WAVE_MID_1 for a 1=bit
// or WAVE_HIGH_0 for a 0 bit.
if (remainingPreambles > 0 ) {
currentBit = true;
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
remainingPreambles--;
// Update free memory diagnostic as we don't have anything else to do this time.
// Allow for checkAck and its called functions using 22 bytes more.
updateMinimumFreeMemory(22);
return;
}
// Wave has gone HIGH but what happens next depends on the bit to be transmitted
// beware OF 9-BIT MASK generating a zero to start each byte
currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0;
bits_sent++;
// If this is the last bit of a byte, prepare for the next byte
@@ -241,7 +233,10 @@ void DCCWaveform::interrupt2() {
}
else if (packetPending) {
// Copy pending packet to transmit packet
for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
// a fixed length memcpy is faster than a variable length loop for these small lengths
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
transmitLength = pendingLength;
transmitRepeats = pendingRepeats;
packetPending = false;
@@ -262,14 +257,15 @@ void DCCWaveform::interrupt2() {
// Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
while (packetPending);
byte checksum = 0;
for (int b = 0; b < byteCount; b++) {
for (byte b = 0; b < byteCount; b++) {
checksum ^= buffer[b];
pendingPacket[b] = buffer[b];
}
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1;
pendingRepeats = repeats;
@@ -277,17 +273,17 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
sentResetsSincePacket=0;
}
int DCCWaveform::getLastCurrent() {
return lastCurrent;
}
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
void DCCWaveform::setAckBaseline() {
if (isMainTrack) return;
ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor);
if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold));
if (isMainTrack) return;
int baseline=motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %dus and %dus"),
baseline,motorDriver->raw2mA(baseline),
ackThreshold,motorDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
}
void DCCWaveform::setAckPending() {
@@ -297,31 +293,37 @@ void DCCWaveform::setAckPending() {
ackPulseDuration=0;
ackDetected=false;
ackCheckStart=millis();
numAckSamples=0;
numAckGaps=0;
ackPending=true; // interrupt routines will now take note
}
byte DCCWaveform::getAck() {
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration,
ackMaxCurrent,motorDriver->convertToMilliamps(ackMaxCurrent), ackPulseDuration);
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.
}
void DCCWaveform::checkAck() {
// This function operates in interrupt() time so must be fast and can't DIAG
if (sentResetsSincePacket > 6) { //ACK timeout
ackCheckDuration=millis()-ackCheckStart;
ackPending = false;
return;
}
lastCurrent=motorDriver->getCurrentRaw();
if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
// An ACK is a pulse lasting between MIN_ACK_PULSE_DURATION and MAX_ACK_PULSE_DURATION uSecs (refer @haba)
int current=motorDriver->getCurrentRaw();
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
if (lastCurrent>ackThreshold) {
if (current>ackThreshold) {
if (trailingEdgeCounter > 0) {
numAckGaps++;
trailingEdgeCounter = 0;
}
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
@@ -329,10 +331,22 @@ void DCCWaveform::checkAck() {
// not in pulse
if (ackPulseStart==0) return; // keep waiting for leading edge
// if we reach to this point, we have
// detected trailing edge of pulse
ackPulseDuration=micros()-ackPulseStart;
if (ackPulseDuration>=MIN_ACK_PULSE_DURATION && ackPulseDuration<=MAX_ACK_PULSE_DURATION) {
if (trailingEdgeCounter == 0) {
ackPulseDuration=micros()-ackPulseStart;
}
// but we do not trust it yet and return (which will force another
// measurement) and first the third time around with low current
// the ack detection will be finalized.
if (trailingEdgeCounter < 2) {
trailingEdgeCounter++;
return;
}
trailingEdgeCounter = 0;
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;

View File

@@ -19,28 +19,29 @@
*/
#ifndef DCCWaveform_h
#define DCCWaveform_h
#include "MotorDriver.h"
#include "ArduinoTimers.h"
#include "MotorDriver.h"
// Wait times for power management. Unit: milliseconds
const int POWER_SAMPLE_ON_WAIT = 100;
const int POWER_SAMPLE_OFF_WAIT = 1000;
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
const int MIN_ACK_PULSE_DURATION = 2000;
const int MAX_ACK_PULSE_DURATION = 8500;
const int PREAMBLE_BITS_MAIN = 20;
// Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22;
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
// to the transform array.
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
const byte MAX_PACKET_SIZE = 12;
// NOTE: static functions are used for the overall controller, then
// one instance is created for each track.
enum class POWERMODE { OFF, ON, OVERLOAD };
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
const byte resetPacket[] = {0x00, 0x00, 0x00};
@@ -48,17 +49,37 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
class DCCWaveform {
public:
DCCWaveform( byte preambleBits, bool isMain);
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
static void setDiagnosticSlowWave(bool slow);
static void loop();
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
static void loop(bool ackManagerActive);
static DCCWaveform mainTrack;
static DCCWaveform progTrack;
void beginTrack();
void setPowerMode(POWERMODE);
POWERMODE getPowerMode();
void checkPowerOverload();
int getLastCurrent();
void checkPowerOverload(bool ackManagerActive);
inline int get1024Current() {
if (powerMode == POWERMODE::ON)
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
return 0;
}
inline int getCurrentmA() {
if (powerMode == POWERMODE::ON)
return motorDriver->raw2mA(lastCurrent);
return 0;
}
inline int getMaxmA() {
if (maxmA == 0) { //only calculate this for first request, it doesn't change
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
}
return maxmA;
}
inline int getTripmA() {
if (tripmA == 0) { //only calculate this for first request, it doesn't change
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
}
return tripmA;
}
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending;
volatile byte sentResetsSincePacket;
@@ -67,58 +88,84 @@ class DCCWaveform {
void setAckPending(); //prog track only
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
inline void doAutoPowerOff() {
if (autoPowerOff) {
setPowerMode(POWERMODE::OFF);
autoPowerOff=false;
}
};
inline bool canMeasureCurrent() {
return motorDriver->canMeasureCurrent();
};
inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
inline void setMinAckPulseDuration(unsigned int i) {
minAckPulseDuration = i;
}
inline void setMaxAckPulseDuration(unsigned int i) {
maxAckPulseDuration = i;
}
private:
static VirtualTimer * interruptTimer;
// For each state of the wave nextState=stateTransform[currentState]
static const WAVE_STATE stateTransform[6];
// For each state of the wave, signal pin is HIGH or LOW
static const bool signalTransform[6];
static void interruptHandler();
bool interrupt1();
void interrupt2();
void checkAck();
void setSignal(bool high);
bool isMainTrack;
MotorDriver* motorDriver;
// Transmission controller
byte transmitPacket[MAX_PACKET_SIZE]; // packet being transmitted
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte transmitLength;
byte transmitRepeats; // remaining repeats of transmission
byte remainingPreambles;
byte requiredPreambles;
bool currentBit; // bit to be transmitted
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
byte bytes_sent; // number of bytes sent from transmitPacket
byte state; // wave generator state machine
byte pendingPacket[MAX_PACKET_SIZE];
WAVE_STATE state; // wave generator state machine
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte pendingLength;
byte pendingRepeats;
int lastCurrent;
int lastCurrent;
static int progTripValue;
int maxmA;
int tripmA;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
static const int ACK_CURRENT_TRIP=1000; // During ACK processing limit can be higher
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
// ACK management (Prog track only)
bool ackPending;
bool ackDetected;
volatile bool ackPending;
volatile bool ackDetected;
int ackThreshold;
int ackLimitmA = 60;
int ackMaxCurrent;
unsigned long ackCheckStart; // millis
unsigned int ackCheckDuration; // millis
unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros
unsigned int minAckPulseDuration = 4000; // micros
unsigned int maxAckPulseDuration = 8500; // micros
volatile static uint8_t numAckGaps;
volatile static uint8_t numAckSamples;
static uint8_t trailingEdgeCounter;
};
#endif

2
DIAG.h
View File

@@ -18,6 +18,8 @@
*/
#ifndef DIAG_h
#define DIAG_h
#include "StringFormatter.h"
#define DIAG StringFormatter::diag
#define LCD StringFormatter::lcd
#endif

View File

@@ -1,8 +1,28 @@
/*
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of Asbelos DCC API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "EEStore.h"
#include "Turnouts.h"
#include "Sensors.h"
#include "Outputs.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_SAMD)
ExternalEEPROM EEPROM;
@@ -72,5 +92,15 @@ int EEStore::pointer(){
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::dump(int num) {
byte b;
DIAG(F("Addr 0x char"));
for (int n=0 ; n<num; n++) {
EEPROM.get(n, b);
DIAG(F("%d %x %c"),n,b,isprint(b) ? b : ' ');
}
}
///////////////////////////////////////////////////////////////////////////////
EEStore *EEStore::eeStore=NULL;
int EEStore::eeAddress=0;

View File

@@ -29,6 +29,7 @@ struct EEStore{
static void advance(int);
static void store();
static void clear();
static void dump(int);
};
#endif

182
EthernetInterface.cpp Normal file
View File

@@ -0,0 +1,182 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX/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/>.
*
*/
#if __has_include ( "config.h")
#include "config.h"
#else
#warning config.h not found. Using defaults from config.example.h
#include "config.example.h"
#endif
#include "defines.h"
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#include "DIAG.h"
#include "CommandDistributor.h"
#include "DCCTimer.h"
EthernetInterface * EthernetInterface::singleton=NULL;
/**
* @brief Setup Ethernet Connection
*
*/
void EthernetInterface::setup()
{
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
};
/**
* @brief Aquire IP Address from DHCP and start server
*
* @return true
* @return false
*/
EthernetInterface::EthernetInterface()
{
byte mac[6];
DCCTimer::getSimulatedMacAddress(mac);
connected=false;
#ifdef IP_ADDRESS
Ethernet.begin(mac, IP_ADDRESS);
#else
if (Ethernet.begin(mac) == 0)
{
DIAG(F("Ethernet.begin FAILED"));
return;
}
#endif
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found"));
return;
}
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("Ethernet cable not connected"));
return;
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
/**
* @brief Main loop for the EthernetInterface
*
*/
void EthernetInterface::loop()
{
if (!singleton) return;
switch (Ethernet.maintain())
{
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
}
void EthernetInterface::loop2()
{
// get client from the server
EthernetClient client = server->accept();
// check for new client
if (client)
{
if (Diag::ETHERNET) DIAG(F("Ethernet: New client "));
byte socket;
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
{
if (!clients[socket])
{
// On accept() the EthernetServer doesn't track the client anymore
// so we store it in our client array
if (Diag::ETHERNET) DIAG(F("Socket %d"),socket);
clients[socket] = client;
break;
}
}
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW"));
}
// check for incoming data from all possible clients
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
{
if (clients[socket]) {
int available=clients[socket].available();
if (available > 0) {
if (Diag::ETHERNET) DIAG(F("Ethernet: available socket=%d,avail=%d"), socket, available);
// read bytes from a client
int count = clients[socket].read(buffer, MAX_ETH_BUFFER);
buffer[count] = '\0'; // terminate the string properly
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
// execute with data going directly back
outboundRing->mark(socket);
CommandDistributor::parse(socket,buffer,outboundRing);
outboundRing->commit();
return; // limit the amount of processing that takes place within 1 loop() cycle.
}
}
}
// stop any clients which disconnect
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
if (clients[socket] && !clients[socket].connected()) {
clients[socket].stop();
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
}
}
// handle at most 1 outbound transmission
int socketOut=outboundRing->read();
if (socketOut>=0) {
int count=outboundRing->count();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}
#endif

69
EthernetInterface.h Normal file
View File

@@ -0,0 +1,69 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX/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/>.
*
* Ethernet Interface added by Gregor Baues
*/
#ifndef EthernetInterface_h
#define EthernetInterface_h
#if __has_include ( "config.h")
#include "config.h"
#else
#warning config.h not found. Using defaults from config.example.h
#include "config.example.h"
#endif
#include "DCCEXParser.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
#if defined (ARDUINO_TEENSY41)
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
#include <NativeEthernetUdp.h>
#else
#include "Ethernet.h"
#endif
#include "RingStream.h"
/**
* @brief Network Configuration
*
*/
#define MAX_ETH_BUFFER 512
#define OUTBOUND_RING_SIZE 2048
class EthernetInterface {
public:
static void setup();
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
void loop2();
EthernetServer * server;
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing;
};
#endif

30
FSH.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef FSH_h
#define FSH_h
/* This is an architecture support file to manage the differences
* between the nano/uno.mega and the later nanoEvery, unoWifiRev2 etc
*
* IMPORTANT:
* To maintain portability the main code should NOT contain ANY references
* to the following:
*
* __FlashStringHelper Use FSH instead.
* PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH instead.
*
*/
#include <Arduino.h>
#if defined(ARDUINO_ARCH_MEGAAVR)
#ifdef F
#undef F
#endif
#define F(str) (str)
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define FLASH
#else
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define FLASH PROGMEM
#endif
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "9db6d36"
#define GITHUB_SHA "107e9d1"

129
I2CManager.cpp Normal file
View File

@@ -0,0 +1,129 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <Wire.h>
#include "I2CManager.h"
// If not already initialised, initialise I2C (wire).
void I2CManagerClass::begin(void) {
if (!_beginCompleted) {
Wire.begin();
_beginCompleted = true;
}
}
// Set clock speed to the lowest requested one. If none requested,
// the Wire default is 100kHz.
void I2CManagerClass::setClock(uint32_t speed) {
if (speed < _clockSpeed && !_clockSpeedFixed) {
_clockSpeed = speed;
Wire.setClock(_clockSpeed);
}
}
// Force clock speed to that specified. It can then only
// be overridden by calling Wire.setClock directly.
void I2CManagerClass::forceClock(uint32_t speed) {
if (!_clockSpeedFixed) {
_clockSpeed = speed;
_clockSpeedFixed = true;
Wire.setClock(_clockSpeed);
}
}
// Check if specified I2C address is responding.
// Returns 0 if OK, or error code.
uint8_t I2CManagerClass::checkAddress(uint8_t address) {
begin();
Wire.beginTransmission(address);
return Wire.endTransmission();
}
bool I2CManagerClass::exists(uint8_t address) {
return checkAddress(address)==0;
}
// Write a complete transmission to I2C using a supplied buffer of data
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size) {
Wire.beginTransmission(address);
Wire.write(buffer, size);
return Wire.endTransmission();
}
// Write a complete transmission to I2C using a supplied buffer of data in Flash
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size) {
uint8_t ramBuffer[size];
memcpy_P(ramBuffer, buffer, size);
return write(address, ramBuffer, size);
}
// Write a complete transmission to I2C using a list of data
uint8_t I2CManagerClass::write(uint8_t address, int nBytes, ...) {
uint8_t buffer[nBytes];
va_list args;
va_start(args, nBytes);
for (uint8_t i=0; i<nBytes; i++)
buffer[i] = va_arg(args, int);
va_end(args);
return write(address, buffer, nBytes);
}
// Write a command and read response, returns number of bytes received.
// Different modules use different ways of accessing registers:
// PCF8574 I/O expander justs needs the address (no data);
// PCA9685 needs a two byte command to select the register(s) to be read;
// MCP23016 needs a one-byte command to select the register.
// Some devices use 8-bit registers exclusively and some have 16-bit registers.
// Therefore the following function is general purpose, to apply to any
// type of I2C device.
//
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeBuffer[], uint8_t writeSize) {
if (writeSize > 0) {
Wire.beginTransmission(address);
Wire.write(writeBuffer, writeSize);
Wire.endTransmission(false); // Don't free bus yet
}
Wire.requestFrom(address, readSize);
uint8_t nBytes = 0;
while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read();
return nBytes;
}
// Overload of read() to allow command to be specified as a series of bytes.
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...) {
va_list args;
// Copy the series of bytes into an array.
va_start(args, writeSize);
uint8_t writeBuffer[writeSize];
for (uint8_t i=0; i<writeSize; i++)
writeBuffer[i] = va_arg(args, int);
va_end(args);
return read(address, readBuffer, readSize, writeBuffer, writeSize);
}
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize) {
return read(address, readBuffer, readSize, NULL, 0);
}
I2CManagerClass I2CManager = I2CManagerClass();

76
I2CManager.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef I2CManager_h
#define I2CManager_h
#include "FSH.h"
/*
* Helper class to manage access to the I2C 'Wire' subsystem.
*
* Helps to avoid calling Wire.begin() multiple times (which is not)
* entirely benign as it reinitialises).
*
* Also helps to avoid the Wire clock from being set, by another device
* driver, to a speed which is higher than a device supports.
*
* Thirdly, it provides a convenient way to check whether there is a
* device on a particular I2C address.
*/
class I2CManagerClass {
public:
I2CManagerClass() {}
// If not already initialised, initialise I2C (wire).
void begin(void);
// Set clock speed to the lowest requested one.
void setClock(uint32_t speed);
// Force clock speed
void forceClock(uint32_t speed);
// Check if specified I2C address is responding.
uint8_t checkAddress(uint8_t address);
bool exists(uint8_t address);
// Write a complete transmission to I2C from an array in RAM
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size);
// Write a complete transmission to I2C from an array in Flash
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size);
// Write a transmission to I2C from a list of bytes.
uint8_t write(uint8_t address, int nBytes, ...);
// Write a command from an array in RAM and read response
uint8_t read(uint8_t address, uint8_t writeBuffer[], uint8_t writeSize,
uint8_t readBuffer[], uint8_t readSize);
// Write a command from an arbitrary list of bytes and read response
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...);
// Write a null command and read the response.
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize);
private:
bool _beginCompleted = false;
bool _clockSpeedFixed = false;
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
};
extern I2CManagerClass I2CManager;
#endif

162
LCDDisplay.cpp Normal file
View File

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

80
LCDDisplay.h Normal file
View File

@@ -0,0 +1,80 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef LCDDisplay_h
#define LCDDisplay_h
#include <Arduino.h>
#if __has_include ( "config.h")
#include "config.h"
#endif
// Allow maximum message length to be overridden from config.h
#if !defined(MAX_MSG_SIZE)
#define MAX_MSG_SIZE 16
#endif
// This class is created in LCDisplay_Implementation.h
class LCDDisplay : public Print {
public:
static const int MAX_LCD_ROWS = 8;
static const int MAX_LCD_COLS = MAX_MSG_SIZE;
static const long LCD_SCROLL_TIME = 3000; // 3 seconds
static LCDDisplay* lcdDisplay;
LCDDisplay();
void interfake(int p1, int p2, int p3);
// Internally handled functions
static void loop();
LCDDisplay* loop2(bool force);
void setRow(byte line);
void clear();
virtual size_t write(uint8_t b);
using Print::write;
private:
void moveToNextRow();
void skipBlankRows();
// Relay functions to the live driver
void clearNative();
void displayNative();
void setRowNative(byte line);
void writeNative(char b);
unsigned long lastScrollTime = 0;
int8_t hotRow = 0;
int8_t hotCol = 0;
int8_t topRow = 0;
uint8_t lcdRows;
uint8_t lcdCols;
int8_t slot = 0;
int8_t rowFirst = -1;
int8_t rowNext = 0;
int8_t charIndex = 0;
char buffer[MAX_LCD_COLS + 1];
char* bufferPointer = 0;
bool done = false;
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
};
#endif

55
LCD_Implementation.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////////
// This implementation is designed to be #included ONLY ONCE in the .ino
//
// It will create a driver implemntation and a shim class implementation.
// This means that other classes can reference the shim without knowing
// which library is involved.
////////////////////////////////////////////////////////////////////////////////////
#ifndef LCD_Implementation_h
#define LCD_Implementation_h
#include <Wire.h>
#include "LCDDisplay.h"
LCDDisplay * LCDDisplay::lcdDisplay=0;
// Implement the LCDDisplay shim class as a singleton.
// Notice that the LCDDisplay class declaration (LCDDisplay.h) is independent of the library
// but the implementation is compiled here with dependencies on LCDDriver which is
// specific to the library in use.
// Thats the workaround to the drivers not all implementing a common interface.
#if defined(OLED_DRIVER)
#include "LCD_OLED.h"
#define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true))
#elif defined(LCD_DRIVER)
#include "LCD_LCD.h"
#define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true))
#else
#include "LCD_NONE.h"
#define CONDITIONAL_LCD_START if (true) /* NO LCD CONFIG, but do the LCD macros to get DIAGS */
#endif
#endif // LCD_Implementation_h

33
LCD_LCD.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LiquidCrystal_I2C.h"
LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows
// DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER
LCDDisplay::LCDDisplay() {
lcdDisplay=this;
LCDDriver.init();
LCDDriver.backlight();
interfake(LCD_DRIVER);
clear();
}
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; }
void LCDDisplay::clearNative() {LCDDriver.clear();}
void LCDDisplay::setRowNative(byte row) { LCDDriver.setCursor(0, row); }
void LCDDisplay::writeNative(char b){ LCDDriver.write(b); }
void LCDDisplay::displayNative() { LCDDriver.display(); }

27
LCD_NONE.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// dummy LCD shim to keep linker happy
LCDDisplay::LCDDisplay() {}
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;}
void LCDDisplay::setRowNative(byte row) { (void)row;}
void LCDDisplay::clearNative() {}
void LCDDisplay::writeNative(char b){ (void)b;} //
void LCDDisplay::displayNative(){}

73
LCD_OLED.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// OLED Implementation of LCDDisplay class
// Note: this file is optionally included by LCD_Implementation.h
// It is NOT a .cpp file to prevent it being compiled and demanding libraries
// even when not needed.
#include "I2CManager.h"
#include "SSD1306Ascii.h"
SSD1306AsciiWire LCDDriver;
// DEVICE SPECIFIC LCDDisplay Implementation for OLED
LCDDisplay::LCDDisplay() {
// Scan for device on 0x3c and 0x3d.
I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speed
for (byte address = 0x3c; address <= 0x3d; address++) {
if (I2CManager.exists(address)) {
// Device found
DIAG(F("OLED display found at 0x%x"), address);
interfake(OLED_DRIVER, 0);
const DevType *devType;
if (lcdCols == 132)
devType = &SH1106_128x64; // Actually 132x64 but treated as 128x64
else if (lcdCols == 128 && lcdRows == 4)
devType = &Adafruit128x32;
else
devType = &Adafruit128x64;
LCDDriver.begin(devType, address);
lcdDisplay = this;
LCDDriver.setFont(System5x7); // Normal 1:1 pixel scale, 8 bits high
clear();
return;
}
}
DIAG(F("OLED display not found"));
}
void LCDDisplay::interfake(int p1, int p2, int p3) {
lcdCols = p1;
lcdRows = p2 / 8;
(void)p3;
}
void LCDDisplay::clearNative() { LCDDriver.clear(); }
void LCDDisplay::setRowNative(byte row) {
// Positions text write to start of row 1..n
int y = row;
LCDDriver.setCursor(0, y);
}
void LCDDisplay::writeNative(char b) { LCDDriver.write(b); }
void LCDDisplay::displayNative() {}

74
LCN.cpp Normal file
View File

@@ -0,0 +1,74 @@
/*
* © 2021, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX 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 "LCN.h"
#include "DIAG.h"
#include "Turnouts.h"
#include "Sensors.h"
int LCN::id = 0;
Stream * LCN::stream=NULL;
bool LCN::firstLoop=true;
void LCN::init(Stream & lcnstream) {
stream=&lcnstream;
DIAG(F("LCN connection setup"));
}
// Inbound LCN traffic is postfix notation... nnnX where nnn is an id, X is the opcode
void LCN::loop() {
if (!stream) return;
if (firstLoop) {
firstLoop=false;
stream->println('X');
return;
}
while (stream->available()) {
int ch = stream->read();
if (ch >= 0 && ch <= '9') { // accumulate id value
id = 10 * id + ch - '0';
}
else if (ch == 't' || ch == 'T') { // Turnout opcodes
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
Turnout * tt = Turnout::get(id);
if (!tt) Turnout::create(id, LCN_TURNOUT_ADDRESS, 0);
if (ch == 't') tt->data.tStatus |= STATUS_ACTIVE;
else tt->data.tStatus &= ~STATUS_ACTIVE;
Turnout::turnoutlistHash++; // signals ED update of turnout data
id = 0;
}
else if (ch == 'S' || ch == 's') {
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
Sensor * ss = Sensor::get(id);
if (!ss) ss = Sensor::create(id, 255,0); // impossible pin
ss->active = ch == 'S';
id = 0;
}
else id = 0; // ignore any other garbage from LCN
}
}
void LCN::send(char opcode, int id, bool state) {
if (stream) {
StringFormatter::send(stream,F("%c/%d/%d"), opcode, id , state);
if (Diag::LCN) DIAG(F("LCN OUT %c/%d/%d"), opcode, id , state);
}
}

16
LCN.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef LCN_h
#define LCN_h
#include <Arduino.h>
class LCN {
public:
static void init(Stream & lcnstream);
static void loop();
static void send(char opcode, int id, bool state);
private :
static bool firstLoop;
static Stream * stream;
static int id;
};
#endif

218
LiquidCrystal_I2C.cpp Normal file
View File

@@ -0,0 +1,218 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
*
* 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-EX. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "LiquidCrystal_I2C.h"
#include "I2CManager.h"
// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set:
// DL = 1; 8-bit interface data
// N = 0; 1-line display
// F = 0; 5x8 dot character font
// 3. Display on/off control:
// D = 0; Display off
// C = 0; Cursor off
// B = 0; Blinking off
// 4. Entry mode set:
// I/D = 1; Increment by 1
// S = 0; No shift
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols,
uint8_t lcd_rows) {
_Addr = lcd_Addr;
_cols = lcd_cols;
_rows = lcd_rows;
_backlightval = LCD_NOBACKLIGHT;
}
void LiquidCrystal_I2C::init() { init_priv(); }
void LiquidCrystal_I2C::init_priv() {
I2CManager.begin();
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows);
}
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines) {
if (lines > 1) {
_displayfunction |= LCD_2LINE;
}
_numlines = lines;
(void)cols; // Suppress compiler warning.
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way befer 4.5V so we'll allow
// 100 milliseconds after pulling both RS and R/W and backlight pin low
expanderWrite(
_backlightval); // reset expander and turn backlight off (Bit 8 =1)
delay(100);
// put the LCD into 4 bit mode
// this is according to the hitachi HD44780 datasheet
// figure 24, pg 46
// we start in 8bit mode, try to set 4 bit mode
write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms
// second try
write4bits(0x03 << 4);
delayMicroseconds(4500); // wait min 4.1ms
// third go!
write4bits(0x03 << 4);
delayMicroseconds(150);
// finally, set to 4-bit interface
write4bits(0x02 << 4);
// set # lines, font size, etc.
command(LCD_FUNCTIONSET | _displayfunction);
// turn the display on with no cursor or blinking default
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
display();
// clear it off
clear();
// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);
setCursor(0, 0);
}
/********** high level commands, for the user! */
void LiquidCrystal_I2C::clear() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes 1.52ms
}
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row) {
int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row > _numlines) {
row = _numlines - 1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}
// Turn the display on/off (quickly)
void LiquidCrystal_I2C::noDisplay() {
_displaycontrol &= ~LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval = LCD_NOBACKLIGHT;
expanderWrite(0);
}
void LiquidCrystal_I2C::backlight(void) {
_backlightval = LCD_BACKLIGHT;
expanderWrite(0);
}
size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
return 1;
}
/*********** mid level commands, for sending data/cmds */
inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}
/************ low level data pushing commands **********/
/* According to the NXP Datasheet for the PCF8574 section 8.2:
* "The master (microcontroller) sends the START condition and slave address
* setting the last bit of the address byte to logic 0 for the write mode.
* The PCF8574/74A acknowledges and the master then sends the data byte for
* P7 to P0 to the port register. As the clock line goes HIGH, the 8-bit
* data is presented on the port lines after it has been acknowledged by the
* PCF8574/74A. [...] The master can then send a STOP or ReSTART condition
* or continue sending data. The number of data bytes that can be sent
* successively is not limited and the previous data is overwritten every
* time a data byte has been sent and acknowledged."
*
* This driver takes advantage of this by sending multiple data bytes in succession
* within a single I2C transmission. With a fast clock rate of 400kHz, the time
* between successive updates of the PCF8574 outputs will be at least 2.5us. With
* the default clock rate of 100kHz the time between updates will be at least 10us.
*
* The LCD controller HD44780, according to its datasheet, needs nominally 37us
* (up to 50us) to execute a command (i.e. write to gdram, reposition, etc.). Each
* command is sent in a separate I2C transmission here. The time taken to end a
* transmission and start another one is a stop bit, a start bit, 8 address bits,
* an ack, 8 data bits and another ack; this is at least 20 bits, i.e. >50us
* at 400kHz and >200us at 100kHz. Therefore, we don't need additional delay.
*
* Similarly, the Enable must be set/reset for at least 450ns. This is
* well within the I2C clock cycle time of 2.5us at 400kHz. Data is clocked in
* to the HD44780 on the trailing edge of the Enable pin, so we set the Enable
* as we present the data, then in the next byte we reset Enable without changing
* the data.
*/
// write either command or data (8 bits) to the HD44780 LCD controller as
// a single I2C transmission.
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
mode |= _backlightval;
uint8_t highnib = (value & 0xf0) | mode;
uint8_t lownib = ((value << 4) & 0xf0) | mode;
// Send both nibbles
byte buffer[] = {(byte)(highnib|En), highnib, (byte)(lownib|En), lownib};
I2CManager.write(_Addr, buffer, sizeof(buffer));
}
// write 4 bits to the HD44780 LCD controller.
void LiquidCrystal_I2C::write4bits(uint8_t value) {
uint8_t _data = value | _backlightval;
// Enable must be set/reset for at least 450ns. This is well within the
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
// HD44780 on the trailing edge of the Enable pin.
byte buffer[] = {(byte)(_data|En), _data};
I2CManager.write(_Addr, buffer, sizeof(buffer));
}
// write a byte to the PCF8574 I2C interface. We don't need to set
// the enable pin for this.
void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
I2CManager.write(_Addr, 1, value | _backlightval);
}

102
LiquidCrystal_I2C.h Normal file
View File

@@ -0,0 +1,102 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
*
* 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 LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h
#include <Arduino.h>
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00
#define En 0b00000100 // Enable bit
#define Rw 0b00000010 // Read/Write bit
#define Rs 0b00000001 // Register select bit
class LiquidCrystal_I2C : public Print {
public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(uint8_t cols, uint8_t rows);
void clear();
void noDisplay();
void display();
void noBacklight();
void backlight();
void setCursor(uint8_t, uint8_t);
virtual size_t write(uint8_t);
void command(uint8_t);
void init();
private:
void init_priv();
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
void expanderWrite(uint8_t);
uint8_t _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
uint8_t _cols;
uint8_t _rows;
uint8_t _backlightval;
};
#endif

View File

@@ -1,98 +0,0 @@
/*
(c) 2015 Ingo Fischer
buffer serial device
based on Arduino SoftwareSerial
Constructor warning messages fixed by Chris Harlow.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "MemStream.h"
MemStream::MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len, bool allowWrite)
:_buffer(buffer),_len(len), _buffer_overflow(false), _pos_read(0), _allowWrite(allowWrite)
{
if (content_len==0) memset(_buffer, 0, _len);
_pos_write=(content_len>len)? len: content_len;
}
size_t MemStream::write(uint8_t byte) {
if (! _allowWrite) return -1;
if (_pos_write >= _len) {
_buffer_overflow = true;
return 0;
}
_buffer[_pos_write] = byte;
++_pos_write;
return 1;
}
void MemStream::flush() {
memset(_buffer, 0, _len);
_pos_write = 0;
_pos_read = 0;
}
int MemStream::read() {
if (_pos_read >= _len) {
_buffer_overflow = true;
return -1;
}
if (_pos_read >= _pos_write) {
return -1;
}
return _buffer[_pos_read++];
}
int MemStream::peek() {
if (_pos_read >= _len) {
_buffer_overflow = true;
return -1;
}
if (_pos_read >= _pos_write) {
return -1;
}
return _buffer[_pos_read+1];
}
int MemStream::available() {
int ret=_pos_write-_pos_read;
if (ret<0) ret=0;
return ret;
}
void MemStream::setBufferContent(uint8_t *buffer, uint16_t content_len) {
memset(_buffer, 0, _len);
memcpy(_buffer, buffer, content_len);
_buffer_overflow=false;
_pos_write=content_len;
_pos_read=0;
}
void MemStream::setBufferContentFromProgmem(uint8_t *buffer, uint16_t content_len) {
memset(_buffer, 0, _len);
memcpy_P(_buffer, buffer, content_len);
_buffer_overflow=false;
_pos_write=content_len;
_pos_read=0;
}
void MemStream::setBufferContentPosition(uint16_t read_pos, uint16_t write_pos) {
_pos_write=write_pos;
_pos_read=read_pos;
}

View File

@@ -1,69 +0,0 @@
/*
(c) 2015 Ingo FIscher
buffer serial device
based on Arduino SoftwareSerial
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MemStream_h
#define MemStream_h
#include <inttypes.h>
#include <Stream.h>
#include <avr/pgmspace.h>
class MemStream : public Stream
{
private:
uint8_t * _buffer;
const uint16_t _len;
bool _buffer_overflow;
uint16_t _pos_read;
uint16_t _pos_write;
bool _allowWrite;
public:
// public methods
MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite=true);
~MemStream() {}
operator const uint8_t *() const { return _buffer; }
operator const char *() const { return (const char*)_buffer; }
uint16_t current_length() const { return _pos_write; }
bool listen() { return true; }
void end() {}
bool isListening() { return true; }
bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; }
int peek();
virtual size_t write(uint8_t byte);
virtual int read();
virtual int available();
virtual void flush();
void setBufferContent(uint8_t *buffer, uint16_t content_len);
void setBufferContentFromProgmem(uint8_t *buffer, uint16_t content_len);
void setBufferContentPosition(uint16_t read_pos, uint16_t write_pos);
using Print::write;
};
#endif

View File

@@ -18,64 +18,171 @@
*/
#include <Arduino.h>
#include "MotorDriver.h"
#include "AnalogReadFast.h"
#include "DCCTimer.h"
#include "DIAG.h"
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_ARCH_MEGAAVR)
#define WritePin digitalWrite
#define ReadPin digitalRead
#else
// use the DIO2 libraray for much faster pin access
#define GPIO2_PREFER_SPEED 1
#include <DIO2.h> // use IDE menu Tools..Manage Libraries to locate and install DIO2
#define WritePin digitalWrite2
#define ReadPin digitalRead2
#endif
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin,
bool MotorDriver::usePWM=false;
bool MotorDriver::commonFaultPin=false;
MotorDriver::MotorDriver(byte 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;
signalPin=signal_pin;
signalPin2=signal_pin2;
brakePin=brake_pin;
currentPin=current_pin;
senseFactor=sense_factor;
faultPin=fault_pin;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
powerPin=power_pin;
getFastPin(F("POWER"),powerPin,fastPowerPin);
pinMode(powerPin, OUTPUT);
pinMode(brakePin, OUTPUT);
signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT);
pinMode(currentPin, INPUT);
if (faultPin != UNUSED_PIN) pinMode(faultPin, INPUT);
signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) {
dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT);
}
else dualSignal=false;
brakePin=brake_pin;
if (brake_pin!=UNUSED_PIN){
invertBrake=brake_pin < 0;
brakePin=invertBrake ? 0-brake_pin : brake_pin;
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
pinMode(brakePin, OUTPUT);
setBrake(false);
}
else brakePin=UNUSED_PIN;
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
pinMode(currentPin, INPUT);
senseOffset=analogRead(currentPin); // value of sensor at zero current
}
faultPin=fault_pin;
if (faultPin != UNUSED_PIN) {
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
pinMode(faultPin, INPUT);
}
senseFactor=sense_factor;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
if (currentPin==UNUSED_PIN)
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
else
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d"),
currentPin-A0, senseOffset,rawCurrentTripValue);
}
bool MotorDriver::isPWMCapable() {
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
}
void MotorDriver::setPower(bool on) {
WritePin(powerPin, on ? HIGH : LOW);
if (on) {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
setBrake(true);
setBrake(false);
setHIGH(fastPowerPin);
}
else setLOW(fastPowerPin);
}
void MotorDriver::setBrake( bool on) {
WritePin(brakePin, on ? HIGH : LOW);
// setBrake applies brake if on == true. So to get
// voltage from the motor bride one needs to do a
// setBrake(false).
// If the brakePin is negative that means the sense
// of the brake pin on the motor bridge is inverted
// (HIGH == release brake) and setBrake does
// compensate for that.
//
void MotorDriver::setBrake(bool on) {
if (brakePin == UNUSED_PIN) return;
if (on ^ invertBrake) setHIGH(fastBrakePin);
else setLOW(fastBrakePin);
}
void MotorDriver::setSignal( bool high) {
WritePin(signalPin, high ? HIGH : LOW);
if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH);
if (usePWM) {
DCCTimer::setPWM(signalPin,high);
}
else {
if (high) {
setHIGH(fastSignalPin);
if (dualSignal) setLOW(fastSignalPin2);
}
else {
setLOW(fastSignalPin);
if (dualSignal) setHIGH(fastSignalPin2);
}
}
}
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
volatile unsigned int overflow_count=0;
#endif
bool MotorDriver::canMeasureCurrent() {
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.
* As there is no -0, create a little and return -1 in that case.
*
* senseOffset handles the case where a shield returns values above or below
* a central value depending on direction.
*/
int MotorDriver::getCurrentRaw() {
if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH)
return (int)(32000/senseFactor);
if (currentPin==UNUSED_PIN) return 0;
int current;
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
bool irq = disableInterrupts();
current = analogRead(currentPin)-senseOffset;
enableInterrupts(irq);
#elif defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
unsigned char sreg_backup;
sreg_backup = SREG; /* save interrupt enable/disable state */
cli();
current = analogRead(currentPin)-senseOffset;
overflow_count = 0;
SREG = sreg_backup; /* restore interrupt state */
#else
current = analogRead(currentPin)-senseOffset;
#endif
if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
return (current == 0 ? -1 : -current);
return current;
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
// The default analogRead takes ~100uS which is catastrphic
// so analogReadFast is used here. (-2uS)
return analogReadFast(currentPin);
// so DCCTimer has set the sample time to be much faster.
}
unsigned int MotorDriver::convertToMilliamps( int raw) {
unsigned int MotorDriver::raw2mA( int raw) {
return (unsigned int)(raw * senseFactor);
}
int MotorDriver::mA2raw( unsigned int mA) {
return (int)(mA / senseFactor);
}
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
(void) type; // avoid compiler warning if diag not used above.
uint8_t port = digitalPinToPort(pin);
if (input)
result.inout = portInputRegister(port);
else
result.inout = portOutputRegister(port);
result.maskHIGH = digitalPinToBitMask(pin);
result.maskLOW = ~result.maskHIGH;
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
}

View File

@@ -18,22 +18,71 @@
*/
#ifndef MotorDriver_h
#define MotorDriver_h
#include "FSH.h"
// 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
#endif
#if defined(__IMXRT1062__)
struct FASTPIN {
volatile uint32_t *inout;
uint32_t maskHIGH;
uint32_t maskLOW;
};
#else
struct FASTPIN {
volatile uint8_t *inout;
uint8_t maskHIGH;
uint8_t maskLOW;
};
#endif
class MotorDriver {
public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
virtual void setPower( bool on);
virtual void setSignal( bool high);
virtual void setBrake( bool on);
virtual int getCurrentRaw();
virtual unsigned int convertToMilliamps( int rawValue);
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
float senseFactor;
unsigned int tripMilliamps;
int rawCurrentTripValue;
const byte UNUSED_PIN = 255;
virtual unsigned int raw2mA( int raw);
virtual int mA2raw( unsigned int mA);
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
}
bool isPWMCapable();
bool canMeasureCurrent();
static bool usePWM;
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte getFaultPin() {
return faultPin;
}
private:
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result);
}
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
bool dualSignal; // true to use signalPin2
bool invertBrake; // brake pin passed as negative means pin is inverted
float senseFactor;
int senseOffset;
unsigned int tripMilliamps;
int rawCurrentTripValue;
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
static bool disableInterrupts() {
uint32_t primask;
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
__disable_irq();
return (primask == 0) ? true : false;
}
static void enableInterrupts(bool doit) {
if (doit) __enable_irq();
}
#endif
};
#endif

View File

@@ -1,5 +1,6 @@
#ifndef MotorDrivers_h
#define MotorDrivers_h
#include <Arduino.h>
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
// It will be overwritten if the library is updated.
@@ -7,37 +8,58 @@
// This file contains configurations for known/supported motor shields.
// A configuration defined by macro here can be used in your sketch.
// A custom hardware setup will require your sketch to create MotorDriver instances
// similar to those defined here, WITHOUT editing this file.
// similar to those defined here, WITHOUT editing this file. You can put your
// custom defines in config.h.
const byte UNUSED_PIN = 255;
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
#define UNUSED_PIN 127 // inside int8_t
#endif
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin,
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
//
// If the brakePin is negative that means the sense
// of the brake pin on the motor bridge is inverted
// (HIGH == release brake)
//
// Arduino standard Motor Shield
#define STANDARD_MOTOR_SHIELD \
new MotorDriver(3 , 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN)
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// Pololu Motor Shield
#define POLOLU_MOTOR_SHIELD \
new MotorDriver(4, 7, UNUSED_PIN, 9 , A0, 18, 2000, 12), \
new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 250 , UNUSED_PIN)
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, 12)
//
// Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the
// pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually
// via the power pins we above use 9 and 10 as power pins and 4 as "inverted brake" which in this
// version of the code always will be high. That means this config is not usable for generating
// a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define
// the motor driver like this:
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
// Firebox Mk1
#define FIREBOX_MK1 \
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 250 , UNUSED_PIN)
// Firebox Mk1
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 1000, UNUSED_PIN)
// Firebox Mk1S
#define FIREBOX_MK1S \
new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \
new MotorDriver(30, 27, 28, 31, 29, 5.00, 250 , UNUSED_PIN)
// Firebox Mk1S
#define FIREBOX_MK1S F("FIREBOX_MK1A"), \
new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \
new MotorDriver(30, 27, 28, 31, 29, 5.00, 1000, UNUSED_PIN)
// FunduMoto Motor Shield
#define FUNDUMOTO_SHIELD \
new MotorDriver(10 , 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN)
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
#endif

View File

@@ -83,14 +83,19 @@ the state of any outputs being monitored or controlled by a separate interface o
#include "Outputs.h"
#include "EEStore.h"
#include "StringFormatter.h"
// print all output states to stream
void Output::printAll(Print *stream){
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
StringFormatter::send(stream, F("<Y %d %d>\n"), tt->data.id, tt->data.oStatus);
} // Output::printAll
void Output::activate(int s){
data.oStatus=(s>0); // if s>0, set status to active, else inactive
digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
if(num>0)
EEPROM.put(num,data.oStatus);
}
///////////////////////////////////////////////////////////////////////////////

View File

@@ -39,6 +39,7 @@ class Output{
static Output *firstOutput;
struct OutputData data;
Output *nextOutput;
static void printAll(Print *);
private:
int num; // Chris has no idea what this is all about!

View File

@@ -23,9 +23,9 @@
* BSD license, all text above must be included in any redistribution
*/
#include <Arduino.h>
#include <Wire.h>
#include "PWMServoDriver.h"
#include "DIAG.h"
#include "I2CManager.h"
// REGISTER ADDRESSES
@@ -40,6 +40,7 @@ const byte MODE1_RESTART=0x80; /**< Restart enabled */
const byte PCA9685_I2C_ADDRESS=0x40; /** First PCA9685 I2C Slave Address */
const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
/*!
* @brief Sets the PWM frequency for a chip to 50Hz for servos
@@ -52,19 +53,20 @@ bool PWMServoDriver::setup(int board) {
if (board>3 || (failFlags & (1<<board))) return false;
if (setupFlags & (1<<board)) return true;
Wire.begin();
I2CManager.begin();
I2CManager.setClock(MAX_I2C_SPEED);
uint8_t i2caddr=PCA9685_I2C_ADDRESS + board;
// Terst if device is available
Wire.beginTransmission(i2caddr);
byte error = Wire.endTransmission();
if (error!=0) {
DIAG(F("\nI2C Servo device 0x%x Not Found %d\n"),i2caddr, error);
// Test if device is available
byte error = I2CManager.checkAddress(i2caddr);
if (error) {
DIAG(F("I2C Servo device 0x%x Not Found %d"),i2caddr, error);
failFlags|=1<<board;
return false;
}
//DIAG(F("\nPWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
//DIAG(F("PWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
writeRegister(i2caddr,PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(i2caddr,PCA9685_PRESCALE, PRESCALE_50HZ);
writeRegister(i2caddr,PCA9685_MODE1,MODE1_AI);
@@ -81,22 +83,15 @@ void PWMServoDriver::setServo(byte servoNum, uint16_t value) {
int pin=servoNum%16;
if (setup(board)) {
DIAG(F("\nSetServo %d %d\n"),servoNum,value);
Wire.beginTransmission(PCA9685_I2C_ADDRESS + board);
Wire.write(PCA9685_FIRST_SERVO + 4 * pin); // 4 registers per pin
Wire.write(0);
Wire.write(0);
Wire.write(value);
Wire.write(value >> 8);
byte error=Wire.endTransmission();
if (error!=0) DIAG(F("\nSetServo error %d\n"),error);
DIAG(F("SetServo %d %d"),servoNum,value);
uint8_t buffer[] = {(uint8_t)(PCA9685_FIRST_SERVO + 4 * pin), // 4 registers per pin
0, 0, (uint8_t)(value & 0xff), (uint8_t)(value >> 8)};
if (value == 4095) buffer[2] = 0x10; // Full on
byte error=I2CManager.write(PCA9685_I2C_ADDRESS + board, buffer, sizeof(buffer));
if (error!=0) DIAG(F("SetServo error %d"),error);
}
}
void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) {
Wire.beginTransmission(i2caddr);
Wire.write(hardwareRegister);
Wire.write(d);
Wire.endTransmission();
delay(5); // allow registers to settle before continuing
I2CManager.write(i2caddr, 2, hardwareRegister, d);
}

View File

@@ -1,39 +1,60 @@
# What's DCC++ EX
------------
# What is DCC++ EX?
DCC++ EX is the organization maintaining several codebases that together represent a fully open source DCC system. Currently, this includes the following:
DCC++ EX is an open-source hardware and software system for the operation of DCC-equipped model railroads. It expands on the work of Gregg E. Berman's original DCC++ (which can be found here in the BaseStation-Classic repository)
* [CommandStation-EX](https://github.com/DCC-EX/CommandStation-EX/releases) - the latest take on the DCC++ command station for controlling your trains. Runs on an Arduino board, and includes advanced features such as a WiThrottle server implementation, turnout operation, general purpose inputs and outputs (I/O), and JMRI integration.
* [exWebThrottle](https://github.com/DCC-EX/exWebThrottle) - a simple web based controller for your DCC++ command station.
* [BaseStation-installer](https://github.com/DCC-EX/BaseStation-Installer) - an installer executable that takes care of downloading and installing DCC++ firmware onto your hardware setup.
* [BaseStation-Classic](https://github.com/DCC-EX/BaseStation-Classic) - the original DCC++ software, packaged in a stable release. No active development, bug fixes only.
The system consists of two parts, the DCC++ EX Command Station and one of many front end controllers. These controllers can be hardware controllers (called CABs or Throttles), software applications like JMRI, phone apps like Engine Driver, or our exWebThrottle that is a simple application you run in a browser like a web page and control your model trains.
A basic DCC++ EX hardware setup can use easy to find, widely avalable Arduino boards that you can assemble yourself.
The DCC++ EX Command Station consists of an Arduino micro controller fitted with an Arduino Motor Shield (or other supported motor controllers) that can be connected directly to the tracks of a model railroad.
Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital Command Control (DCC) [standards](http://www.nmra.org/dcc-working-group "NMRA DCC Working Group"), including:
# Whats in this Repository
-------------------------
* simultaneous control of multiple locomotives
* 2-byte and 4-byte locomotive addressing
* 128-step speed throttling
* Activate/de-activate all accessory function addresses 0-2048
* Control of all cab functions F0-F28
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders. More information about the sketch can be found in the included PDF file.
# Whats in this Repository?
To utilize this sketch, you can use our automated installer, or download a zip file from this repository and open the file "CommandStation-EX.ino" after unzipping it to your Arduino IDE projects folder. Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code.
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders.
The latest production release of the Master branch is 3.0.1:
To utilize this sketch, you can use the following:
* Supports the Arduino Uno, Arduino Mega, and Arduino Nano
* Built-in configuration for both the original Arduino Motor Shield, Pololu MC33926 Motor Shield, LMD18200, and BTS7960B
* Built-in configuration and support of Ethernet Shields and the ESP82266 WiFi module (networking for use with Mega only).
1. (beginner) our [automated installer](https://github.com/DCC-EX/BaseStation-Installer)
2. (intermediate) download the latest version from the [releases page](https://github.com/DCC-EX/CommandStation-EX/releases)
3. (advanced) use git clone on this repository
For more information on the overall DCC++ EX system, please follow the links in the PDF file.
Not using the installer? Open the file "CommandStation-EX.ino" in the
Arduino IDE. Please do not rename the folder containing the sketch
code, nor add any files in that folder. The Arduino IDE relies on the
structure and name of the folder to properly display and compile the
code. Rename or copy config.example.h to config.h. If you do not have
the standard setup, you must edit config.h according to the help texts
in config.h.
Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository
## What's new in CommandStation-EX?
The Master branch contains all of the Command Station functionality showed in the DCC-EX YouTube channel.
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station
* WiFi and Ethernet shield support
* No more jumpers or soldering!
* Direct support for all the most popular motor control boards
* I2C Display support
* Improved short circuit detection and automatic reset from an overload
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
* Improved adherence to the NMRA DCC specification
* Complete support for all the old commands and front ends like JMRI
* Railcom cutout (beta)
* Simpler, modular, faster code with an API Library for developers for easy expansion
* New features and functions in JMRI
* Automation (coming soon)
# How to find more information
--------------------------
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
[DCC++ EX WEB Page](https://dcc-ex.github.io "DCC++ EX WEB Page")
# More information
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
[The DCC++ EX Discord and live support](https://discord.gg/y2sB4Fp "The DCC++ EX Discord Server")
[TrainBoard DCC++ Forum](https://www.trainboard.com/highball/index.php?forums/dcc.177/ "TrainBoard DCC++ Forum")
-May 2020
!!
- November 14, 2020

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,93 @@
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
**Known Bugs:**
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
**Summary of the key new features added to CommandStation-EX V3.0.3**
- **<W addr> command to write loco address and clear consist**
- **<R> command will allow for consist address**
- **Startup commands implemented**
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
- **Simultaneously update JMRI to handle new current meter**
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- **Add back fix for jitter**
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**
**Summary of the key new features added to CommandStation-EX V3.0.0:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - ```<#>``` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - - Engine Driver and JMRI Interface
- Scott Catalanno - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - (KCSmith)
**WebThrotle-EX**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H -
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Keith Ledbetter
- BradVan der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA

View File

@@ -0,0 +1,23 @@
# CommandStation-EX Release Notes
## v3.0.0
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Support for more decoders** - Support for 20 (Uno) or 50 (Mega) mobile decoders, number automaticlaly recognized by JMRI.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.

View File

@@ -0,0 +1,85 @@
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. Download the compressed files here:
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)
**Known Bugs:**
- **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket.
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
**Summary of the key new features added to CommandStation-EX V3.0.0:**
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - ```<#>``` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - - Engine Driver and JMRI Interface
- Scott Catalanno - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
-Kevin Smith - (KCSmith)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Keith Ledbetter
- BradVan der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)

View File

@@ -0,0 +1,170 @@
DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more perfromance from the Arduino microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.12.
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
#### Summary of key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Includes support for the Arduino Teensy
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for later
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<!>` command added to release locos from memory.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - - Engine Driver and JMRI Interface
- Scott Catalanno - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - (KCSmith)
**WebThrotle-EX**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Keith Ledbetter
- BradVan der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA

105
RingStream.cpp Normal file
View File

@@ -0,0 +1,105 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX 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 "RingStream.h"
#include "DIAG.h"
RingStream::RingStream( const uint16_t len)
{
_len=len;
_buffer=new byte[len];
_pos_write=0;
_pos_read=0;
_buffer[0]=0;
_overflow=false;
_mark=0;
_count=0;
}
size_t RingStream::write(uint8_t b) {
if (_overflow) return 0;
_buffer[_pos_write] = b;
++_pos_write;
if (_pos_write==_len) _pos_write=0;
if (_pos_write==_pos_read) {
_overflow=true;
return 0;
}
_count++;
return 1;
}
int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=_buffer[_pos_read];
_pos_read++;
if (_pos_read==_len) _pos_read=0;
_overflow=false;
return b;
}
int RingStream::count() {
return (read()<<8) | read();
}
int RingStream::freeSpace() {
// allow space for client flag and length bytes
if (_pos_read>_pos_write) return _pos_read-_pos_write-3;
else return _len - _pos_write + _pos_read-3;
}
// mark start of message with client id (0...9)
void RingStream::mark(uint8_t b) {
_mark=_pos_write;
write(b); // client id
write((uint8_t)0); // count MSB placemarker
write((uint8_t)0); // count LSB placemarker
_count=0;
}
// peekTargetMark is used by the parser stash routines to know which client
// to send a callback response to some time later.
uint8_t RingStream::peekTargetMark() {
return _buffer[_mark];
}
bool RingStream::commit() {
if (_overflow) {
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
// just throw it away
_pos_write=_mark;
_overflow=false;
return false; // commit failed
}
if (_count==0) {
// ignore empty response
_pos_write=_mark;
return true; // true=commit ok
}
// Go back to the _mark and inject the count 1 byte later
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=highByte(_count);
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
return true; // commit worked
}

48
RingStream.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef RingStream_h
#define RingStream_h
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX 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 <Arduino.h>
class RingStream : public Print {
public:
RingStream( const uint16_t len);
virtual size_t write(uint8_t b);
using Print::write;
int read();
int count();
int freeSpace();
void mark(uint8_t b);
bool commit();
uint8_t peekTargetMark();
private:
int _len;
int _pos_write;
int _pos_read;
bool _overflow;
int _mark;
int _count;
byte * _buffer;
};
#endif

108
SSD1306Ascii.cpp Normal file
View File

@@ -0,0 +1,108 @@
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
* Modifications (C) 2021 Neil McKechnie
*
* This Library 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.
*
* This Library 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 the Arduino SSD1306Ascii Library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include "SSD1306Ascii.h"
#include "I2CManager.h"
#include "FSH.h"
// Maximum number of bytes we can send per transmission is 32.
const uint8_t SSD1306AsciiWire::blankPixels[16] =
{0x40, // First byte specifies data mode
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//==============================================================================
// SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------
void SSD1306AsciiWire::clear() {
clear(0, displayWidth() - 1, 0, displayRows() - 1);
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::clear(uint8_t columnStart, uint8_t columnEnd,
uint8_t rowStart, uint8_t rowEnd) {
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire
// Ensure only rows on display will be cleared.
if (rowEnd >= displayRows()) rowEnd = displayRows() - 1;
for (uint8_t r = rowStart; r <= rowEnd; r++) {
setCursor(columnStart, r); // Position at start of row to be erased
for (uint8_t c = columnStart; c <= columnEnd; c += maxBytes-1) {
uint8_t len = min((uint8_t)(columnEnd-c+1), maxBytes-1) + 1;
I2CManager.write(m_i2cAddr, blankPixels, len); // Write up to 15 blank columns
}
}
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
m_i2cAddr = i2cAddr;
m_col = 0;
m_row = 0;
#ifdef __AVR__
const uint8_t* table = (const uint8_t*)pgm_read_word(&dev->initcmds);
#else // __AVR__
const uint8_t* table = dev->initcmds;
#endif // __AVR
uint8_t size = readFontByte(&dev->initSize);
m_displayWidth = readFontByte(&dev->lcdWidth);
m_displayHeight = readFontByte(&dev->lcdHeight);
m_colOffset = readFontByte(&dev->colOffset);
I2CManager.write_P(m_i2cAddr, table, size);
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::setContrast(uint8_t value) {
I2CManager.write(m_i2cAddr, 2,
0x00, // Set to command mode
SSD1306_SETCONTRAST, value);
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::setCursor(uint8_t col, uint8_t row) {
if (row < displayRows() && col < m_displayWidth) {
m_row = row;
m_col = col + m_colOffset;
I2CManager.write(m_i2cAddr, 4,
0x00, // Set to command mode
SSD1306_SETLOWCOLUMN | (col & 0XF),
SSD1306_SETHIGHCOLUMN | (col >> 4),
SSD1306_SETSTARTPAGE | m_row);
}
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::setFont(const uint8_t* font) {
m_font = font;
m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR);
m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT);
}
//------------------------------------------------------------------------------
size_t SSD1306AsciiWire::write(uint8_t ch) {
const uint8_t* base = m_font + FONT_WIDTH_TABLE;
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0;
ch -= m_fontFirstChar;
base += fontWidth * ch;
uint8_t buffer[1+fontWidth+letterSpacing];
buffer[0] = 0x40; // set SSD1306 controller to data mode
uint8_t bufferPos = 1;
// Copy character pixel columns
for (uint8_t i = 0; i < fontWidth; i++)
buffer[bufferPos++] = readFontByte(base++);
// Add blank pixels between letters
for (uint8_t i = 0; i < letterSpacing; i++)
buffer[bufferPos++] = 0;
// Write the data to I2C display
I2CManager.write(m_i2cAddr, buffer, bufferPos);
return 1;
}

97
SSD1306Ascii.h Normal file
View File

@@ -0,0 +1,97 @@
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
* Modifications (C) 2021 Neil McKechnie
*
* This Library 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.
*
* This Library 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 this software. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef SSD1306Ascii_h
#define SSD1306Ascii_h
#include "Arduino.h"
#include "SSD1306font.h"
#include "SSD1306init.h"
class SSD1306AsciiWire : public Print {
public:
using Print::write;
SSD1306AsciiWire() {}
// Initialize the display controller.
void begin(const DevType* dev, uint8_t i2cAddr);
// Clear the display and set the cursor to (0, 0).
void clear();
// Clear a region of the display.
void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1);
// The current column in pixels.
inline uint8_t col() const { return m_col; }
// The display hight in pixels.
inline uint8_t displayHeight() const { return m_displayHeight; }
// The display height in rows with eight pixels to a row.
inline uint8_t displayRows() const { return m_displayHeight / 8; }
// The display width in pixels.
inline uint8_t displayWidth() const { return m_displayWidth; }
// Set the cursor position to (0, 0).
inline void home() { setCursor(0, 0); }
// Initialize the display controller.
void init(const DevType* dev);
// the current row number with eight pixels to a row.
inline uint8_t row() const { return m_row; }
/**
* @brief Set the display contrast.
*
* @param[in] value The contrast level in th range 0 to 255.
*/
void setContrast(uint8_t value);
/**
* @brief Set the cursor position.
*
* @param[in] col The column number in pixels.
* @param[in] row the row number in eight pixel rows.
*/
void setCursor(uint8_t col, uint8_t row);
/**
* @brief Set the current font.
*
* @param[in] font Pointer to a font table.
*/
void setFont(const uint8_t* font);
/**
* @brief Display a character.
*
* @param[in] c The character to display.
* @return one for success else zero.
*/
size_t write(uint8_t c);
private:
uint8_t m_col; // Cursor column.
uint8_t m_row; // Cursor RAM row.
uint8_t m_displayWidth; // Display width.
uint8_t m_displayHeight; // Display height.
uint8_t m_colOffset; // Column offset RAM to SEG.
const uint8_t* m_font = NULL; // Current font.
// Only fixed size 5x7 fonts in a 6x8 cell are supported.
const uint8_t fontWidth = 5;
const uint8_t fontHeight = 7;
const uint8_t letterSpacing = 1;
uint8_t m_fontFirstChar;
uint8_t m_fontCharCount;
uint8_t m_i2cAddr;
static const uint8_t blankPixels[];
};
#endif // SSD1306Ascii_h

180
SSD1306font.h Normal file
View File

@@ -0,0 +1,180 @@
/*
*
* System5x7
*
*
* File Name : System5x7.h
* Date : 28 Oct 2008
* Font size in bytes : 470
* Font width : 5
* Font height : 7
* Font first char : 32
* Font last char : 127
* Font used chars : 94
*
* The font data are defined as
*
* struct _FONT_ {
* uint16_t font_Size_in_Bytes_over_all_included_Size_it_self;
* uint8_t font_Width_in_Pixel_for_fixed_drawing;
* uint8_t font_Height_in_Pixel_for_all_characters;
* unit8_t font_First_Char;
* uint8_t font_Char_Count;
*
* uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1];
* // for each character the separate width in pixels,
* // characters < 128 have an implicit virtual right empty row
*
* uint8_t font_data[];
* // bit field of all characters
*/
#ifndef SSD1306font_H
#define SSD1306font_H
#define SYSTEM5x7_WIDTH 5
#define SYSTEM5x7_HEIGHT 7
#ifdef __AVR__
#include <avr/pgmspace.h>
/** declare a font for AVR. */
#define GLCDFONTDECL(_n) static const uint8_t __attribute__((progmem)) _n[]
#define readFontByte(addr) pgm_read_byte(addr)
#else // __AVR__
/** declare a font. */
#define GLCDFONTDECL(_n) static const uint8_t _n[]
/** Fake read from flash. */
#define readFontByte(addr) (*(const unsigned char *)(addr))
#endif // __AVR__
//------------------------------------------------------------------------------
// Font Indices
/** No longer used Big Endian length field. Now indicates font type.
*
* 00 00 (fixed width font with 1 padding pixel on right and below)
*
* 00 01 (fixed width font with no padding pixels)
*/
#define FONT_LENGTH 0
/** Maximum character width. */
#define FONT_WIDTH 2
/** Font hight in pixels */
#define FONT_HEIGHT 3
/** Ascii value of first character */
#define FONT_FIRST_CHAR 4
/** count of characters in font. */
#define FONT_CHAR_COUNT 5
/** Offset to width table. */
#define FONT_WIDTH_TABLE 6
//------------------------------------------------------------------------------
GLCDFONTDECL(System5x7) = {
0x0, 0x0, // size of zero indicates fixed width font,
0x05, // width
0x07, // height
0x20, // first char
0x61, // char count
// Fixed width; char width table not used !!!!
// font data
0x00, 0x00, 0x00, 0x00, 0x00, // (space)
0x00, 0x00, 0x5F, 0x00, 0x00, // !
0x00, 0x07, 0x00, 0x07, 0x00, // "
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
0x23, 0x13, 0x08, 0x64, 0x62, // %
0x36, 0x49, 0x55, 0x22, 0x50, // &
0x00, 0x05, 0x03, 0x00, 0x00, // '
0x00, 0x1C, 0x22, 0x41, 0x00, // (
0x00, 0x41, 0x22, 0x1C, 0x00, // )
0x08, 0x2A, 0x1C, 0x2A, 0x08, // *
0x08, 0x08, 0x3E, 0x08, 0x08, // +
0x00, 0x50, 0x30, 0x00, 0x00, // ,
0x08, 0x08, 0x08, 0x08, 0x08, // -
0x00, 0x60, 0x60, 0x00, 0x00, // .
0x20, 0x10, 0x08, 0x04, 0x02, // /
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
0x42, 0x61, 0x51, 0x49, 0x46, // 2
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
0x27, 0x45, 0x45, 0x45, 0x39, // 5
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
0x01, 0x71, 0x09, 0x05, 0x03, // 7
0x36, 0x49, 0x49, 0x49, 0x36, // 8
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
0x00, 0x36, 0x36, 0x00, 0x00, // :
0x00, 0x56, 0x36, 0x00, 0x00, // ;
0x00, 0x08, 0x14, 0x22, 0x41, // <
0x14, 0x14, 0x14, 0x14, 0x14, // =
0x41, 0x22, 0x14, 0x08, 0x00, // >
0x02, 0x01, 0x51, 0x09, 0x06, // ?
0x32, 0x49, 0x79, 0x41, 0x3E, // @
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
0x7F, 0x49, 0x49, 0x49, 0x36, // B
0x3E, 0x41, 0x41, 0x41, 0x22, // C
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
0x7F, 0x49, 0x49, 0x49, 0x41, // E
0x7F, 0x09, 0x09, 0x01, 0x01, // F
0x3E, 0x41, 0x41, 0x51, 0x32, // G
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
0x00, 0x41, 0x7F, 0x41, 0x00, // I
0x20, 0x40, 0x41, 0x3F, 0x01, // J
0x7F, 0x08, 0x14, 0x22, 0x41, // K
0x7F, 0x40, 0x40, 0x40, 0x40, // L
0x7F, 0x02, 0x04, 0x02, 0x7F, // M
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
0x7F, 0x09, 0x09, 0x09, 0x06, // P
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
0x7F, 0x09, 0x19, 0x29, 0x46, // R
0x46, 0x49, 0x49, 0x49, 0x31, // S
0x01, 0x01, 0x7F, 0x01, 0x01, // T
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
0x7F, 0x20, 0x18, 0x20, 0x7F, // W
0x63, 0x14, 0x08, 0x14, 0x63, // X
0x03, 0x04, 0x78, 0x04, 0x03, // Y
0x61, 0x51, 0x49, 0x45, 0x43, // Z
0x00, 0x00, 0x7F, 0x41, 0x41, // [
0x02, 0x04, 0x08, 0x10, 0x20, // "\"
0x41, 0x41, 0x7F, 0x00, 0x00, // ]
0x04, 0x02, 0x01, 0x02, 0x04, // ^
0x40, 0x40, 0x40, 0x40, 0x40, // _
0x00, 0x01, 0x02, 0x04, 0x00, // `
0x20, 0x54, 0x54, 0x54, 0x78, // a
0x7F, 0x48, 0x44, 0x44, 0x38, // b
0x38, 0x44, 0x44, 0x44, 0x20, // c
0x38, 0x44, 0x44, 0x48, 0x7F, // d
0x38, 0x54, 0x54, 0x54, 0x18, // e
0x08, 0x7E, 0x09, 0x01, 0x02, // f
0x08, 0x14, 0x54, 0x54, 0x3C, // g
0x7F, 0x08, 0x04, 0x04, 0x78, // h
0x00, 0x44, 0x7D, 0x40, 0x00, // i
0x20, 0x40, 0x44, 0x3D, 0x00, // j
0x00, 0x7F, 0x10, 0x28, 0x44, // k
0x00, 0x41, 0x7F, 0x40, 0x00, // l
0x7C, 0x04, 0x18, 0x04, 0x78, // m
0x7C, 0x08, 0x04, 0x04, 0x78, // n
0x38, 0x44, 0x44, 0x44, 0x38, // o
0x7C, 0x14, 0x14, 0x14, 0x08, // p
0x08, 0x14, 0x14, 0x18, 0x7C, // q
0x7C, 0x08, 0x04, 0x04, 0x08, // r
0x48, 0x54, 0x54, 0x54, 0x20, // s
0x04, 0x3F, 0x44, 0x40, 0x20, // t
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
0x44, 0x28, 0x10, 0x28, 0x44, // x
0x0C, 0x50, 0x50, 0x50, 0x3C, // y
0x44, 0x64, 0x54, 0x4C, 0x44, // z
0x00, 0x08, 0x36, 0x41, 0x00, // {
0x00, 0x00, 0x7F, 0x00, 0x00, // |
0x00, 0x41, 0x36, 0x08, 0x00, // }
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol
};
#endif

207
SSD1306init.h Normal file
View File

@@ -0,0 +1,207 @@
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
* Modifications (C) 2021 Neil McKechnie
*
* This Library 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.
*
* This Library 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 the Arduino SSD1306Ascii Library. If not, see
* <http://www.gnu.org/licenses/>.
*/
/**
* @file SSD1306init.h
* @brief Display controller initialization commands.
*/
#ifndef SSD1306init_h
#define SSD1306init_h
//------------------------------------------------------------------------------
#ifndef __AVR__
/** Handle AVR flash addressing. */
#define MEM_TYPE
#else // __AVR__
#define MEM_TYPE __attribute__ ((progmem))
#endif // __AVR__
//------------------------------------------------------------------------------
/** Set Lower Column Start Address for Page Addressing Mode. */
#define SSD1306_SETLOWCOLUMN 0x00
/** Set Higher Column Start Address for Page Addressing Mode. */
#define SSD1306_SETHIGHCOLUMN 0x10
/** Set Memory Addressing Mode. */
#define SSD1306_MEMORYMODE 0x20
/** Set display RAM display start line register from 0 - 63. */
#define SSD1306_SETSTARTLINE 0x40
/** Set Display Contrast to one of 256 steps. */
#define SSD1306_SETCONTRAST 0x81
/** Enable or disable charge pump. Follow with 0X14 enable, 0X10 disable. */
#define SSD1306_CHARGEPUMP 0x8D
/** Set Segment Re-map between data column and the segment driver. */
#define SSD1306_SEGREMAP 0xA0
/** Resume display from GRAM content. */
#define SSD1306_DISPLAYALLON_RESUME 0xA4
/** Force display on regardless of GRAM content. */
#define SSD1306_DISPLAYALLON 0xA5
/** Set Normal Display. */
#define SSD1306_NORMALDISPLAY 0xA6
/** Set Inverse Display. */
#define SSD1306_INVERTDISPLAY 0xA7
/** Set Multiplex Ratio from 16 to 63. */
#define SSD1306_SETMULTIPLEX 0xA8
/** Set Display off. */
#define SSD1306_DISPLAYOFF 0xAE
/** Set Display on. */
#define SSD1306_DISPLAYON 0xAF
/**Set GDDRAM Page Start Address. */
#define SSD1306_SETSTARTPAGE 0XB0
/** Set COM output scan direction normal. */
#define SSD1306_COMSCANINC 0xC0
/** Set COM output scan direction reversed. */
#define SSD1306_COMSCANDEC 0xC8
/** Set Display Offset. */
#define SSD1306_SETDISPLAYOFFSET 0xD3
/** Sets COM signals pin configuration to match the OLED panel layout. */
#define SSD1306_SETCOMPINS 0xDA
/** This command adjusts the VCOMH regulator output. */
#define SSD1306_SETVCOMDETECT 0xDB
/** Set Display Clock Divide Ratio/ Oscillator Frequency. */
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
/** Set Pre-charge Period */
#define SSD1306_SETPRECHARGE 0xD9
/** Deactivate scroll */
#define SSD1306_DEACTIVATE_SCROLL 0x2E
/** No Operation Command. */
#define SSD1306_NOP 0XE3
//------------------------------------------------------------------------------
/** Set Pump voltage value: (30H~33H) 6.4, 7.4, 8.0 (POR), 9.0. */
#define SH1106_SET_PUMP_VOLTAGE 0X30
/** First byte of set charge pump mode */
#define SH1106_SET_PUMP_MODE 0XAD
/** Second byte charge pump on. */
#define SH1106_PUMP_ON 0X8B
/** Second byte charge pump off. */
#define SH1106_PUMP_OFF 0X8A
//------------------------------------------------------------------------------
/**
* @struct DevType
* @brief Device initialization structure.
*/
struct DevType {
/**
* Pointer to initialization command bytes.
*/
const uint8_t* initcmds;
/**
* Number of initialization bytes.
*/
const uint8_t initSize;
/**
* Width of the diaplay in pixels.
*/
const uint8_t lcdWidth;
/**
* Height of the display in pixels.
*/
const uint8_t lcdHeight;
/**
* Column offset RAM to display. Used to pick start column of SH1106.
*/
const uint8_t colOffset;
};
//------------------------------------------------------------------------------
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x32 SSD1306 oled display. */
static const uint8_t MEM_TYPE Adafruit128x32init[] = {
// Init sequence for Adafruit 128x32 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0x02, // sequential COM pins, disable remap
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
/** Initialize a 128x32 SSD1306 oled display. */
static const DevType MEM_TYPE Adafruit128x32 = {
Adafruit128x32init,
sizeof(Adafruit128x32init),
128,
32,
0
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x64 SSD1306 oled display. */
static const uint8_t MEM_TYPE Adafruit128x64init[] = {
// Init sequence for Adafruit 128x64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0x12, // alt COM pins, disable remap
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
/** Initialize a 128x64 oled display. */
static const DevType MEM_TYPE Adafruit128x64 = {
Adafruit128x64init,
sizeof(Adafruit128x64init),
128,
64,
0
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */
static const uint8_t MEM_TYPE SH1106_128x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETCONTRAST, 0x80, // 128
SSD1306_SEGREMAP | 0X1, // set segment remap
SSD1306_NORMALDISPLAY, // normal / reverse
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
SSD1306_COMSCANDEC, // Com scan direction
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
SSD1306_DISPLAYON
};
/** Initialize a 128x64 oled SH1106 display. */
static const DevType MEM_TYPE SH1106_128x64 = {
SH1106_128x64init,
sizeof(SH1106_128x64init),
128,
64,
2 // SH1106 is a 132x64 controller. Use middle 128 columns.
};
#endif // SSD1306init_h

View File

@@ -65,27 +65,62 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
**********************************************************************/
#include "StringFormatter.h"
#include "Sensors.h"
#include "EEStore.h"
///////////////////////////////////////////////////////////////////////////////
//
// checks one defined sensors and prints _changed_ sensor state
// to stream unless stream is NULL in which case only internal
// state is updated. Then advances to next sensor which will
// be checked att next invocation.
//
///////////////////////////////////////////////////////////////////////////////
void Sensor::checkAll(){
for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY;
void Sensor::checkAll(Print *stream){
if(!tt->active && tt->signal<0.5){
tt->active=true;
} else if(tt->active && tt->signal>0.9){
tt->active=false;
if (firstSensor == NULL) return;
if (readingSensor == NULL) readingSensor=firstSensor;
bool sensorstate = digitalRead(readingSensor->data.pin);
if (!sensorstate == readingSensor->active) { // active==true means sensorstate=0/false so sensor unchanged
// no change
if (readingSensor->latchdelay != 0) {
// enable if you want to debug contact jitter
//if (stream != NULL) StringFormatter::send(stream, F("JITTER %d %d\n"),
// readingSensor->latchdelay, readingSensor->data.snum);
readingSensor->latchdelay=0; // reset
}
} // loop over all sensors
} else if (readingSensor->latchdelay < 127) { // byte, max 255, good value unknown yet
// change but first increase anti-jitter counter
readingSensor->latchdelay++;
} else {
// make the change
readingSensor->active = !sensorstate;
readingSensor->latchdelay=0; // reset
if (stream != NULL) StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
}
readingSensor=readingSensor->nextSensor;
} // Sensor::checkAll
///////////////////////////////////////////////////////////////////////////////
//
// prints all sensor states to stream
//
///////////////////////////////////////////////////////////////////////////////
void Sensor::printAll(Print *stream){
for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
if (stream != NULL)
StringFormatter::send(stream, F("<%c %d>\n"), tt->active ? 'Q' : 'q', tt->data.snum);
} // loop over all sensors
} // Sensor::printAll
///////////////////////////////////////////////////////////////////////////////
Sensor *Sensor::create(int snum, int pin, int pullUp){
@@ -108,7 +143,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp){
tt->data.pin=pin;
tt->data.pullUp=(pullUp==0?LOW:HIGH);
tt->active=false;
tt->signal=1;
tt->latchdelay=0;
pinMode(pin,INPUT); // set mode to input
digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor
@@ -137,6 +172,7 @@ bool Sensor::remove(int n){
else
pp->nextSensor=tt->nextSensor;
if (readingSensor==tt) readingSensor=tt->nextSensor;
free(tt);
return true;
@@ -174,3 +210,4 @@ void Sensor::store(){
///////////////////////////////////////////////////////////////////////////////
Sensor *Sensor::firstSensor=NULL;
Sensor *Sensor::readingSensor=NULL;

View File

@@ -31,16 +31,18 @@ struct SensorData {
struct Sensor{
static Sensor *firstSensor;
static Sensor *readingSensor;
SensorData data;
boolean active;
float signal;
byte latchdelay;
Sensor *nextSensor;
static void load();
static void store();
static Sensor *create(int, int, int);
static Sensor* get(int);
static bool remove(int);
static void checkAll();
static void checkAll(Print *);
static void printAll(Print *);
}; // Sensor
#endif

View File

@@ -22,65 +22,108 @@
#if defined(ARDUINO_ARCH_SAMD)
// Some processors use a gcc compiler that renames va_list!!!
#include <cstdarg>
Print * StringFormatter::diagSerial= &SerialUSB;
#elif defined(ARDUINO_ARCH_AVR)
Print * StringFormatter::diagSerial= &Serial;
#elif defined(ARDUINO_ARCH_MEGAAVR)
Print * StringFormatter::diagSerial= &SerialUSB;
#else
Print * StringFormatter::diagSerial=&Serial;
#define __FlashStringHelper char
#endif
#include "LCDDisplay.h"
bool Diag::ACK=false;
bool Diag::CMD=false;
bool Diag::WIFI=false;
bool Diag::WITHROTTLE=false;
bool Diag::ETHERNET=false;
bool Diag::LCN=false;
void StringFormatter::diag( const __FlashStringHelper* input...) {
if (!diagSerial) return;
void StringFormatter::diag( const FSH* input...) {
if (!diagSerial) return;
diagSerial->print(F("<* "));
va_list args;
va_start(args, input);
send2(diagSerial,input,args);
diagSerial->print(F(" *>\n"));
}
void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) {
void StringFormatter::lcd(byte row, const FSH* input...) {
va_list args;
// Issue the LCD as a diag first
send(diagSerial,F("<* LCD%d:"),row);
va_start(args, input);
send2(diagSerial,input,args);
send(diagSerial,F(" *>\n"));
if (!LCDDisplay::lcdDisplay) return;
LCDDisplay::lcdDisplay->setRow(row);
va_start(args, input);
send2(LCDDisplay::lcdDisplay,input,args);
}
void StringFormatter::send(Print * stream, const FSH* input...) {
va_list args;
va_start(args, input);
send2(stream,input,args);
}
void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) {
void StringFormatter::send(Print & stream, const FSH* input...) {
va_list args;
va_start(args, input);
send2(&stream,input,args);
}
void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) {
void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
// thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output
char* flash=(char*)format;
for(int i=0; ; ++i) {
char c=pgm_read_byte_near(flash+i);
char c=GETFLASH(flash+i);
if (c=='\0') return;
if(c!='%') { stream->print(c); continue; }
bool formatContinues=false;
byte formatWidth=0;
bool formatLeft=false;
do {
formatContinues=false;
i++;
c=pgm_read_byte_near(flash+i);
c=GETFLASH(flash+i);
switch(c) {
case '%': stream->print('%'); break;
case 'c': stream->print((char) va_arg(args, int)); break;
case 's': stream->print(va_arg(args, char*)); break;
case 'e': printEscapes(stream,va_arg(args, char*)); break;
case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break;
case 'd': stream->print(va_arg(args, int), DEC); break;
case 'l': stream->print(va_arg(args, long), DEC); break;
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
case 'S': stream->print((const FSH*)va_arg(args, char*)); break;
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
case 'b': stream->print(va_arg(args, int), BIN); break;
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print(va_arg(args, int), HEX); break;
case 'f': stream->print(va_arg(args, double), 2); break;
//format width prefix
case '-':
formatLeft=true;
formatContinues=true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
formatWidth=formatWidth * 10 + (c-'0');
formatContinues=true;
break;
}
} while(formatContinues);
}
va_end(args);
}
@@ -94,6 +137,17 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
}
}
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
if (!stream) return;
char* flash=(char*)input;
for(int i=0; ; ++i) {
char c=GETFLASH(flash+i);
printEscape(stream,c);
if (c=='\0') return;
}
}
void StringFormatter::printEscape( char c) {
printEscape(diagSerial,c);
}
@@ -109,4 +163,27 @@ void StringFormatter::printEscape(Print * stream, char c) {
default: stream->print(c);
}
}
void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) {
if (width==0) {
stream->print(value, DEC);
return;
}
int digits=(value <= 0)? 1: 0; // zero and negative need extra digot
long v=value;
while (v) {
v /= 10;
digits++;
}
if (formatLeft) stream->print(value, DEC);
while(digits<width) {
stream->print(' ');
digits++;
}
if (!formatLeft) stream->print(value, DEC);
}

View File

@@ -19,39 +19,44 @@
#ifndef StringFormatter_h
#define StringFormatter_h
#include <Arduino.h>
#include "FSH.h"
#if defined(ARDUINO_ARCH_SAMD)
// Some processors use a gcc compiler that renames va_list!!!
#include <cstdarg>
#elif defined(ARDUINO_ARCH_MEGAAVR)
#define __FlashStringHelper char
#endif
#include "LCDDisplay.h"
class Diag {
public:
static bool ACK;
static bool CMD;
static bool WIFI;
static bool WITHROTTLE;
static bool ETHERNET;
static bool LCN;
};
class StringFormatter
{
public:
static void send(Print * serial, const __FlashStringHelper* input...);
static void send(Print & serial, const __FlashStringHelper* input...);
static void send(Print * serial, const FSH* input...);
static void send(Print & serial, const FSH* input...);
static void printEscapes(Print * serial,char * input);
static void printEscapes(Print * serial,const FSH* input);
static void printEscape(Print * serial, char c);
// DIAG support
static Print * diagSerial;
static void diag( const __FlashStringHelper* input...);
static void diag( const FSH* input...);
static void lcd(byte row, const FSH* input...);
static void printEscapes(char * input);
static void printEscape( char c);
private:
static void send2(Print * serial, const __FlashStringHelper* input,va_list args);
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,94 +0,0 @@
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
// All Credit to David Cutting
#include <Arduino.h>
#if defined(ARDUINO_SAMD_ZERO)
#if defined(SAMC21)
#include "ATSAMC21G/Timer.h"
#else
#include "ATSAMD21G/Timer.h"
#endif
Timer TimerA(TCC0);
Timer TimerB(TCC1);
Timer TimerC(TCC2);
void TCC0_Handler() {
if(TCC0->INTFLAG.bit.OVF) {
TCC0->INTFLAG.bit.OVF = 1;
TimerA.isrCallback();
}
}
void TCC1_Handler() {
if(TCC1->INTFLAG.bit.OVF) {
TCC1->INTFLAG.bit.OVF = 1;
TimerB.isrCallback();
}
}
void TCC2_Handler() {
if(TCC2->INTFLAG.bit.OVF) {
TCC2->INTFLAG.bit.OVF = 1;
TimerC.isrCallback();
}
}
#elif defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#include "ATMEGA2560/Timer.h"
Timer TimerA(1);
Timer TimerB(3);
Timer TimerC(4);
Timer TimerD(5);
ISR(TIMER1_OVF_vect)
{
TimerA.isrCallback();
}
ISR(TIMER3_OVF_vect)
{
TimerB.isrCallback();
}
ISR(TIMER4_OVF_vect)
{
TimerC.isrCallback();
}
ISR(TIMER5_OVF_vect)
{
TimerD.isrCallback();
}
#elif defined(ARDUINO_AVR_UNO) // Todo: add other 328 boards for compatibility
#include "ATMEGA328/Timer.h"
Timer TimerA(1);
Timer TimerB(2);
ISR(TIMER1_OVF_vect)
{
TimerA.isrCallback();
}
ISR(TIMER2_OVF_vect)
{
TimerB.isrCallback();
}
#elif defined(ARDUINO_ARCH_MEGAAVR)
#include "ATMEGA4809/Timer.h"
Timer TimerA(0);
ISR(TCA0_OVF_vect) {
TimerA.isrCallback();
}
#endif

View File

@@ -1,5 +1,7 @@
/*
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of Asbelos DCC API
*
@@ -19,29 +21,55 @@
#include "Turnouts.h"
#include "EEStore.h"
#include "PWMServoDriver.h"
#include "StringFormatter.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
bool Turnout::activate(int n,bool state){
//DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
// print all turnout states to stream
void Turnout::printAll(Print *stream){
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
} // Turnout::printAll
bool Turnout::activate(int n,bool state){
#ifdef EESTOREDEBUG
DIAG(F("Turnout::activate(%d,%d)"),n,state);
#endif
Turnout * tt=get(n);
if (tt==NULL) return false;
tt->activate(state);
if(n>0) EEPROM.put(n,tt->data.tStatus);
turnoutlistHash++;
return true;
}
bool Turnout::isActive(int n){
bool Turnout::isActive(int n){
Turnout * tt=get(n);
if (tt==NULL) return false;
return tt->data.tStatus & STATUS_ACTIVE;
}
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
void Turnout::activate(bool state) {
if (state) data.tStatus|=STATUS_ACTIVE;
else data.tStatus &= ~STATUS_ACTIVE;
if (data.tStatus & STATUS_PWM) PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0)));
else DCC::setAccessory(data.address,data.subAddress, state);
void Turnout::activate(bool state) {
#ifdef EESTOREDEBUG
DIAG(F("Turnout::activate(%d)"),state);
#endif
if (data.address==LCN_TURNOUT_ADDRESS) {
// A LCN turnout is transmitted to the LCN master.
LCN::send('T',data.id,state);
return; // The tStatus will be updated by a message from the LCN master, later.
}
if (state)
data.tStatus|=STATUS_ACTIVE;
else
data.tStatus &= ~STATUS_ACTIVE;
if (data.tStatus & STATUS_PWM)
PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0)));
else
DCC::setAccessory(data.address,data.subAddress, state);
// Save state if stored in EEPROM
if (EEStore::eeStore->data.nTurnouts > 0 && num > 0)
EEPROM.put(num, data.tStatus);
}
///////////////////////////////////////////////////////////////////////////////
@@ -80,7 +108,11 @@ void Turnout::load(){
if (data.tStatus & STATUS_PWM) tt=create(data.id,data.tStatus & STATUS_PWMPIN, data.inactiveAngle,data.moveAngle);
else tt=create(data.id,data.address,data.subAddress);
tt->data.tStatus=data.tStatus;
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to status byte within EEPROM
EEStore::advance(sizeof(tt->data));
#ifdef EESTOREDEBUG
tt->print(tt);
#endif
}
}
@@ -93,6 +125,10 @@ void Turnout::store(){
EEStore::eeStore->data.nTurnouts=0;
while(tt!=NULL){
#ifdef EESTOREDEBUG
tt->print(tt);
#endif
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to tstatus byte within EEPROM
EEPROM.put(EEStore::pointer(),tt->data);
EEStore::advance(sizeof(tt->data));
tt=tt->nextTurnout;
@@ -129,7 +165,19 @@ Turnout *Turnout::create(int id){
turnoutlistHash++;
return tt;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// print debug info about the state of a turnout
//
#ifdef EESTOREDEBUG
void Turnout::print(Turnout *tt) {
if (tt->data.tStatus & STATUS_PWM )
DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE);
else
DIAG(F("Turnout %d Addr %d Subaddr %d Status %d"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE);
}
#endif
Turnout *Turnout::firstTurnout=NULL;
int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists

View File

@@ -21,11 +21,12 @@
#include <Arduino.h>
#include "DCC.h"
#include "LCN.h"
const byte STATUS_ACTIVE=0x80; // Flag as activated
const byte STATUS_PWM=0x40; // Flag as a PWM turnout
const byte STATUS_PWMPIN=0x3F; // PWM pin 0-63
const int LCN_TURNOUT_ADDRESS=-1; // spoof dcc address -1 indicates a LCN turnout
struct TurnoutData {
int id;
uint8_t tStatus; // has STATUS_ACTIVE, STATUS_PWM, STATUS_PWMPIN
@@ -49,6 +50,12 @@ class Turnout {
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
static Turnout *create(int id);
void activate(bool state);
static void printAll(Print *);
#ifdef EESTOREDEBUG
void print(Turnout *tt);
#endif
private:
int num; // EEPROM address of tStatus in TurnoutData struct, or zero if not stored.
}; // Turnout
#endif

View File

@@ -1,21 +0,0 @@
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
// All Credit to David Cutting
#ifndef VirtualTimer_h
#define VirtualTimer_h
class VirtualTimer
{
public:
virtual void initialize() = 0;
virtual void setPeriod(unsigned long microseconds) = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void attachInterrupt(void (*isr)()) = 0;
virtual void detachInterrupt() = 0;
private:
};
#endif

View File

@@ -46,6 +46,8 @@
#include "StringFormatter.h"
#include "Turnouts.h"
#include "DIAG.h"
#include "GITHUB_SHA.h"
#include "version.h"
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
@@ -74,7 +76,7 @@ bool WiThrottle::areYouUsingThrottle(int cab) {
// One instance of WiThrottle per connected client, so we know what the locos are
WiThrottle::WiThrottle( int wificlientid) {
if (Diag::WITHROTTLE) DIAG(F("\nCreating new WiThrottle for client %d\n"),wificlientid);
if (Diag::WITHROTTLE) DIAG(F("%l Creating new WiThrottle for client %d"),millis(),wificlientid);
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
@@ -97,20 +99,12 @@ WiThrottle::~WiThrottle() {
}
}
void WiThrottle::parse(Print & stream, byte * cmdx) {
void WiThrottle::parse(RingStream * stream, byte * cmdx) {
// we have to take a copy of the cmd buffer as the reply will get built into the cmdx
byte local[150];
for (byte i=0;i<sizeof(local);i++) {
local[i]=cmdx[i];
if (!cmdx[i]) break;
}
local[149]='\0'; // prevent runaway parser
byte * cmd=local;
byte * cmd=cmdx;
heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("\nWiThrottle(%d)<-[%e]\n"),clientid, cmd);
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
if (initSent) {
// Send power state if different than last sent
@@ -139,6 +133,8 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
case 'P':
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
}
@@ -164,7 +160,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
if (initSent) {
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_TIMEOUT); // return timeout value
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
}
break;
case 'M': // multithrottle
@@ -172,12 +168,13 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
StringFormatter::send(stream,F("VN2.0\nHTDCC++EX\nRL0\n"));
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
if (annotateLeftRight) StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Left}|{2]\\[Right}|{4\n"));
else StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Closed}|{2]\\[Thrown}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_TIMEOUT);
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
initSent = true;
}
break;
@@ -187,7 +184,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit\n"), clientid);
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
delete this;
break;
}
@@ -210,16 +207,28 @@ int WiThrottle::getLocoId(byte * cmd) {
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
return getInt(cmd+1);
}
void WiThrottle::multithrottle(Print & stream, byte * cmd){
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for *
byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;>
// DIAG(F("\nMultithrottle aval=%c cab=%d"), aval[0],locoid);
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
switch(cmd[2]) {
case '+': // add loco request
if (cmd[3]=='*') {
// M+* means get loco from prog track, then join tracks ready to drive away
// Stash the things the callback will need later
stashStream= stream;
stashClient=stream->peekTargetMark();
stashThrottleChar=throttleChar;
stashInstance=this;
// ask DCC to call us back when the loco id has been read
DCC::getLocoId(getLocoCallback); // will remove any previous join
return; // return nothing in stream as response is sent later in the callback
}
//return error if address zero requested
if (locoid==0) {
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
@@ -236,8 +245,11 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
// TODO... get known Fn states from DCC (need memoryStream improvements to handle data length)
// for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],fkey);
//Get known Fn states from DCC
for(int fKey=0; fKey<=28; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
@@ -258,16 +270,16 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
}
}
void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int cab){
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
// Note cab=-1 for all cabs in the consist called throttleChar.
// DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
switch (aval[0]) {
case 'V': // Vspeed
{
byte locospeed=WiTToDCCSpeed(getInt(aval+1));
int witSpeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, locospeed, DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, locospeed);
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, witSpeed);
}
}
break;
@@ -336,28 +348,55 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
return WiTSpeed + 1; //offset others by 1
}
void WiThrottle::loop() {
void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat();
// TODO... any broadcasts to be done
(void)stream;
/* MUST follow this model in this loop.
* stream->mark();
* send 1 digit client id, and any data
* stream->commit()
*/
}
void WiThrottle::checkHeartbeat() {
// if 2 heartbeats missed... drop connection and eStop any locos still assigned to this client
if(heartBeatEnable && (millis()-heartBeat > HEARTBEAT_TIMEOUT*2000)) {
if (Diag::WITHROTTLE) DIAG(F("\n\nWiThrottle(%d) hearbeat missed, dropping connection\n\n"),clientid);
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0') {
if (Diag::WITHROTTLE) DIAG(F(" eStopping cab %d\n"), myLocos[loco].cab);
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
}
}
delete this;
} else {
// TODO Check if anything has changed on my locos since last notified!
}
}
}
char WiThrottle::LorS(int cab) {
return (cab<127)?'S':'L';
}
}
// Drive Away feature. Callback handling
RingStream * WiThrottle::stashStream;
WiThrottle * WiThrottle::stashInstance;
byte WiThrottle::stashClient;
char WiThrottle::stashThrottleChar;
void WiThrottle::getLocoCallback(int16_t locoid) {
stashStream->mark(stashClient);
if (locoid<0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
else {
char addcmd[20]={'M',stashThrottleChar,'+',LorS(locoid) };
itoa(locoid,addcmd+4,10);
stashInstance->multithrottle(stashStream, (byte *)addcmd);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
}
stashStream->commit();
}

View File

@@ -19,6 +19,7 @@
#ifndef WiThrottle_h
#define WiThrottle_h
#include "RingStream.h"
struct MYLOCO {
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
@@ -27,16 +28,17 @@ struct MYLOCO {
class WiThrottle {
public:
static void loop();
void parse(Print & stream, byte * cmd);
static void loop(RingStream * stream);
void parse(RingStream * stream, byte * cmd);
static WiThrottle* getThrottle( int wifiClient);
static bool annotateLeftRight;
private:
WiThrottle( int wifiClientId);
~WiThrottle();
static const int MAX_MY_LOCO=10; //maximum number of locos assigned to a single client
static const int HEARTBEAT_TIMEOUT=2;// heartbeat at 2secs to provide messaging transport
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=4; // heartbeat at 4secs to provide messaging transport
static const int ESTOP_SECONDS=8; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle;
static int getInt(byte * cmd);
static int getLocoId(byte * cmd);
@@ -55,9 +57,17 @@ class WiThrottle {
bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed);
int WiTToDCCSpeed(int WiTSpeed);
void multithrottle(Print & stream, byte * cmd);
void locoAction(Print & stream, byte* aval, char throttleChar, int cab);
void accessory(Print & stream, byte* cmd);
void checkHeartbeat();
void multithrottle(RingStream * stream, byte * cmd);
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
void accessory(RingStream *, byte* cmd);
void checkHeartbeat();
// callback stuff to support prog track acquire
static RingStream * stashStream;
static WiThrottle * stashInstance;
static byte stashClient;
static char stashThrottleChar;
static void getLocoCallback(int16_t locoid);
};
#endif

252
WifiInboundHandler.cpp Normal file
View File

@@ -0,0 +1,252 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of Asbelos DCC API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
#include <Arduino.h>
#include "WifiInboundHandler.h"
#include "RingStream.h"
#include "CommandDistributor.h"
#include "DIAG.h"
WifiInboundHandler * WifiInboundHandler::singleton;
void WifiInboundHandler::setup(Stream * ESStream) {
singleton=new WifiInboundHandler(ESStream);
}
void WifiInboundHandler::loop() {
singleton->loop1();
}
WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
wifiStream=ESStream;
clientPendingCIPSEND=-1;
inboundRing=new RingStream(INBOUND_RING);
outboundRing=new RingStream(OUTBOUND_RING);
pendingCipsend=false;
}
// Handle any inbound transmission
// +IPD,x,lll:data is stored in streamer[x]
// Other input returns
void WifiInboundHandler::loop1() {
// First handle all inbound traffic events because they will block the sending
if (loop2()!=INBOUND_IDLE) return;
WiThrottle::loop(outboundRing);
// if nothing is already CIPSEND pending, we can CIPSEND one reply
if (clientPendingCIPSEND<0) {
clientPendingCIPSEND=outboundRing->read();
if (clientPendingCIPSEND>=0) {
currentReplySize=outboundRing->count();
pendingCipsend=true;
}
}
if (pendingCipsend) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false;
return;
}
// if something waiting to execute, we can call it
int clientId=inboundRing->read();
if (clientId>=0) {
int count=inboundRing->count();
if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"),clientId,count);
byte cmd[count+1];
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
cmd[count]=0;
if (Diag::WIFI) DIAG(F("%e"),cmd);
outboundRing->mark(clientId); // remember start of outbound data
CommandDistributor::parse(clientId,cmd,outboundRing);
// The commit call will either write the lenbgth bytes
// OR rollback to the mark because the reply is empty or commend generated more than fits the buffer
outboundRing->commit();
return;
}
}
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
while (wifiStream->available()) {
int ch = wifiStream->read();
// echo the char to the diagnostic stream in escaped format
if (Diag::WIFI) {
// DIAG(F(" %d/"), loopState);
StringFormatter::printEscape(ch); // DIAG in disguise
}
switch (loopState) {
case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR, SEND OK
if (ch == '+') {
loopState = IPD;
break;
}
if (ch=='>') {
if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize);
for (int i=0;i<currentReplySize;i++) {
int cout=outboundRing->read();
wifiStream->write(cout);
if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise
}
clientPendingCIPSEND=-1;
pendingCipsend=false;
loopState=SKIPTOEND;
break;
}
if (ch=='R') { // Received ... bytes
loopState=SKIPTOEND;
break;
}
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0);
loopState=SKIPTOEND;
break;
}
if (ch>='0' && ch<='9') {
runningClientId=ch-'0';
loopState=GOT_CLIENT_ID;
break;
}
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away
purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
}
break;
case IPD: // Looking for I in +IPD
loopState = (ch == 'I') ? IPD1 : SKIPTOEND;
break;
case IPD1: // Looking for P in +IPD
loopState = (ch == 'P') ? IPD2 : SKIPTOEND;
break;
case IPD2: // Looking for D in +IPD
loopState = (ch == 'D') ? IPD3 : SKIPTOEND;
break;
case IPD3: // Looking for , After +IPD
loopState = (ch == ',') ? IPD4_CLIENT : SKIPTOEND;
break;
case IPD4_CLIENT: // reading connection id
if (ch >= '0' || ch <='9'){
runningClientId=ch-'0';
loopState=IPD5;
}
else loopState=SKIPTOEND;
break;
case IPD5: // Looking for , After +IPD,client
loopState = (ch == ',') ? IPD6_LENGTH : SKIPTOEND;
dataLength=0; // ready to start collecting the length
break;
case IPD6_LENGTH: // reading for length
if (ch == ':') {
if (dataLength==0) {
loopState=ANYTHING;
break;
}
if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength);
if (inboundRing->freeSpace()<=(dataLength+1)) {
// This input would overflow the inbound ring, ignore it
loopState=IPD_IGNORE_DATA;
if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:"));
break;
}
inboundRing->mark(runningClientId);
loopState=IPD_DATA;
break;
}
dataLength = dataLength * 10 + (ch - '0');
break;
case IPD_DATA: // reading data
inboundRing->write(ch);
dataLength--;
if (dataLength == 0) {
inboundRing->commit();
loopState = ANYTHING;
}
break;
case IPD_IGNORE_DATA: // ignoring data that would not fit in inbound ring
dataLength--;
if (dataLength == 0) loopState = ANYTHING;
break;
case GOT_CLIENT_ID: // got x before CLOSE or CONNECTED
loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND;
break;
case GOT_CLIENT_ID2: // got "x,"
if (ch=='C') {
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
case SKIPTOEND: // skipping for /n
if (ch=='\n') loopState=ANYTHING;
break;
} // switch
} // available
return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY;
}
void WifiInboundHandler::purgeCurrentCIPSEND() {
// A CIPSEND was sent but errored... or the client closed just toss it away
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
pendingCipsend=false;
clientPendingCIPSEND=-1;
}
#endif

62
WifiInboundHandler.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef WifiInboundHandler_h
#define WifiInboundHandler_h
#include "RingStream.h"
#include "WiThrottle.h"
#include "DIAG.h"
class WifiInboundHandler {
public:
static void setup(Stream * ESStream);
static void loop();
private:
static WifiInboundHandler * singleton;
enum INBOUND_STATE : byte {
INBOUND_BUSY, // keep calling in loop()
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
};
enum LOOP_STATE : byte {
ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc...
SKIPTOEND, // skip to newline
// +IPD,client,length:data
IPD, // got +
IPD1, // got +I
IPD2, // got +IP
IPD3, // got +IPD
IPD4_CLIENT, // got +IPD, reading cient id
IPD5, // got +IPD,c
IPD6_LENGTH, // got +IPD,c, reading length
IPD_DATA, // got +IPD,c,ll,: collecting data
IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring
GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED
};
WifiInboundHandler(Stream * ESStream);
void loop1();
INBOUND_STATE loop2();
void purgeCurrentCIPSEND();
Stream * wifiStream;
static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048;
RingStream * inboundRing;
RingStream * outboundRing;
LOOP_STATE loopState=ANYTHING;
int runningClientId; // latest client inbound processing data or CLOSE
int dataLength; // dataLength of +IPD
int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
};
#endif

View File

@@ -1,7 +1,8 @@
/*
© 2020, Chris Harlow. All rights reserved.
© 2020, Harald Barth.
This file is part of Asbelos DCC API
This file is part of CommandStation-EX
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,142 +17,302 @@
You should have received a copy of the GNU General Public License
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "WifiInterface.h"
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
#include "WifiInterface.h" /* config.h included there */
#include <avr/pgmspace.h>
#include "DIAG.h"
#include "StringFormatter.h"
#include "WiThrottle.h"
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n";
const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000";
const char PROGMEM PROMPT_SEARCH[] = ">";
const char PROGMEM SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
const char PROGMEM IPD_SEARCH[] = "+IPD";
#include "WifiInboundHandler.h"
const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false;
bool WifiInterface::closeAfter = false;
DCCEXParser WifiInterface::parser;
byte WifiInterface::loopstate = 0;
unsigned long WifiInterface::loopTimeoutStart = 0;
int WifiInterface::datalength = 0;
int WifiInterface::connectionId;
byte WifiInterface::buffer[MAX_WIFI_BUFFER+1];
MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER);
Stream * WifiInterface::wifiStream = NULL;
HTTP_CALLBACK WifiInterface::httpCallback = 0;
Stream * WifiInterface::wifiStream;
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
// or ot will cause issues with the following commands.
#define WIFI_CONNECT_TIMEOUT 16000
#endif
void WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port) {
////////////////////////////////////////////////////////////////////////////////
//
// Figure out number of serial ports depending on hardware
//
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define NUM_SERIAL 0
#endif
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
#define NUM_SERIAL 3
#endif
#ifndef NUM_SERIAL
#define NUM_SERIAL 1
#endif
bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiESSID,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel) {
wifiSerialState wifiUp = WIFI_NOAT;
#if NUM_SERIAL == 0
// no warning about unused parameters.
(void) serial_link_speed;
(void) wifiESSID;
(void) wifiPassword;
(void) hostname;
(void) port;
(void) channel;
#endif
#if NUM_SERIAL > 0
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
#if NUM_SERIAL > 1
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
#if NUM_SERIAL > 2
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
if (wifiUp == WIFI_NOAT) // here and still not AT commands found
return false;
DCCEXParser::setAtCommandCallback(ATCommand);
// CAUTION... ONLY CALL THIS ONCE
WifiInboundHandler::setup(wifiStream);
if (wifiUp == WIFI_CONNECTED)
connected = true;
else
connected = false;
return connected;
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
wifiSerialState wifiState;
static uint8_t ntry = 0;
ntry++;
wifiStream = &setupStream;
DIAG(F("\n++++++ Wifi Setup In Progress ++++++++\n"));
connected = setup2( SSid, password, hostname, port);
if (connected) {
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, OK_SEARCH, true);
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel);
if (wifiState == WIFI_NOAT) {
DIAG(F("++ Wifi Setup NO AT ++"));
return wifiState;
}
DIAG(F("\n++++++ Wifi Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED"));
if (wifiState == WIFI_CONNECTED) {
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, true);
}
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
return wifiState;
}
bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port) {
int ipOK = 0;
#ifdef DONT_TOUCH_WIFI_CONF
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
bool ipOK = false;
bool oldCmd = false;
char macAddress[17]; // mac address extraction
// First check... Restarting the Arduino does not restart the ES.
// There may alrerady be a connection with data in the pipeline.
// If there is, just shortcut the setup and continue to read the data as normal.
if (checkForOK(200,IPD_SEARCH, true)) {
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
loopstate=4; // carry on from correct place
return true;
if (checkForOK(200,F("+IPD"), true)) {
DIAG(F("Preconfigured Wifi already running with data waiting"));
return WIFI_CONNECTED;
}
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
if(!checkForOK(200, true))
return WIFI_NOAT; // No AT compatible WiFi module here
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console
checkForOK(2000, true); // Makes this visible on the console
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
checkForOK(2000, true, false); // Makes this visible on the console
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
// Copy 17 byte mac address
for (int i=0; i<17;i++) {
while(!wifiStream->available());
macAddress[i]=wifiStream->read();
StringFormatter::printEscape(macAddress[i]);
}
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
#else
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR?\r\n"));
if (!(checkForOK(2000, true))) {
oldCmd=true;
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
}
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
StringFormatter::send(wifiStream, F("AT+CWMODE%s=1\r\n"), oldCmd ? "" : "_CUR"); // configure as "station" = WiFi client
checkForOK(1000, true); // Not always OK, sometimes "no change"
const char *yourNetwork = "Your network ";
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
// If the source code looks unconfigured, check if the
// ESP8266 is preconfigured in station mode.
// We check the first 13 chars of the SSid and the password
// give a preconfigured ES8266 a chance to connect to a router
// typical connect time approx 7 seconds
delay(8000);
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
} else {
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
} else {
// later version supports CWJAP_CUR
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
checkForOK(2000, true); // dont care if not supported
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
ipOK = 1;
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
}
if (ipOK) {
// But we really only have the ESSID and password correct
// Let's check for IP (via DHCP)
ipOK = false;
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
}
if (!ipOK) {
StringFormatter::send(wifiStream, F("AT+CWMODE=3\r\n")); // configure as server or access point
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
// If we have not managed to get this going in station mode, go for AP mode
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
if (checkForOK(2000, OK_SEARCH, true)) {
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
// AT command early version supports CWJAP/CWSAP
if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
// checkForOK(1000, true); // Not always OK, sometimes "no change"
int i=0;
do {
// configure as AccessPoint. Try really hard as this is the
// last way out to get any Wifi connectivity.
StringFormatter::send(wifiStream, F("AT+CWMODE%s=2\r\n"), oldCmd ? "" : "_CUR");
} while (!checkForOK(1000+i*500, true) && i++<10);
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
// Figure out MAC addr
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) {
// Copy 17 byte mac address
for (int i=0; i<17;i++) {
while(!wifiStream->available());
macAddress[i]=wifiStream->read();
StringFormatter::printEscape(macAddress[i]);
}
DIAG(F("\n**\n"));
// establish the APname
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
} else {
memset(macAddress,'f',sizeof(macAddress));
}
else {
// later version supports CWJAP_CUR
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
checkForOK(20000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok
checkForOK(1000, true, false); // suck up remainder of AT+CIFSR
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);
} 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_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
if (i >= 2)
DIAG(F("Warning: Setting AP SSID and password failed")); // but issue warning
if (!oldCmd) {
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
checkForOK(2000, OK_SEARCH, true);
checkForOK(2000, true);
}
}
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
checkForOK(1000, true); // ignore result in case it already was off
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
if (!checkForOK(10000, OK_SEARCH, true)) return false;
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
if (!checkForOK(10000, OK_SEARCH, true)) return false;
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
#endif //DONT_TOUCH_WIFI_CONF
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
if (!checkForOK(10000, OK_SEARCH, true, false)) return false;
DIAG(F("\nPORT=%d\n"),port);
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
// Copy the IP address
{
const byte MAX_IP_LENGTH=15;
char ipString[MAX_IP_LENGTH+1];
ipString[MAX_IP_LENGTH]='\0'; // protection against missing " character on end.
for(byte ipLen=0;ipLen<MAX_IP_LENGTH;ipLen++) {
while(!wifiStream->available());
int ipChar=wifiStream->read();
StringFormatter::printEscape(ipChar);
if (ipChar=='"') {
ipString[ipLen]='\0';
break;
}
ipString[ipLen]=ipChar;
}
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
}
// suck up anything after the IP.
if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED;
LCD(5,F("PORT=%d"),port);
return true;
return WIFI_CONNECTED;
}
#ifdef DONT_TOUCH_WIFI_CONF
#pragma GCC diagnostic pop
#endif
// This function is used to allow users to enter <+ commands> through the DCCEXParser
@@ -163,199 +324,50 @@ void WifiInterface::ATCommand(const byte * command) {
command++;
if (*command=='X') {
connected = true;
DIAG(F("\n++++++ Wifi Connction forced on ++++++++\n"));
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
}
else {
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, OK_SEARCH, true);
checkForOK(10000, true);
}
}
void WifiInterface::setHTTPCallback(HTTP_CALLBACK callback) {
httpCallback = callback;
bool WifiInterface::checkForOK( const unsigned int timeout, bool echo, bool escapeEcho) {
return checkForOK(timeout,F("\r\nOK\r\n"),echo,escapeEcho);
}
bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, bool echo, bool escapeEcho) {
unsigned long startTime = millis();
char const *locator = waitfor;
DIAG(F("\nWifi Check: [%E]"), waitfor);
char *locator = (char *)waitfor;
DIAG(F("Wifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
int ch = wifiStream->read();
if (echo) {
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else DIAG(F("%c"), ch);
else StringFormatter::diagSerial->print((char)ch);
}
if (ch != pgm_read_byte_near(locator)) locator = waitfor;
if (ch == pgm_read_byte_near(locator)) {
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
if (ch == GETFLASH(locator)) {
locator++;
if (!pgm_read_byte_near(locator)) {
DIAG(F("\nFound in %dms"), millis() - startTime);
if (!GETFLASH(locator)) {
DIAG(F("Found in %dms"), millis() - startTime);
return true;
}
}
}
}
DIAG(F("\nTIMEOUT after %dms\n"), timeout);
DIAG(F("TIMEOUT after %dms"), timeout);
return false;
}
bool WifiInterface::isHTTP() {
// POST GET PUT PATCH DELETE
// You may think a simple strstr() is better... but not when ram & time is in short supply
switch (buffer[0]) {
case 'P':
if (buffer[1] == 'U' && buffer[2] == 'T' && buffer[3] == ' ' ) return true;
if (buffer[1] == 'O' && buffer[2] == 'S' && buffer[3] == 'T' && buffer[4] == ' ') return true;
if (buffer[1] == 'A' && buffer[2] == 'T' && buffer[3] == 'C' && buffer[4] == 'H' && buffer[5] == ' ') return true;
return false;
case 'G':
if (buffer[1] == 'E' && buffer[2] == 'T' && buffer[3] == ' ' ) return true;
return false;
case 'D':
if (buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'E' && buffer[4] == 'T' && buffer[5] == 'E' && buffer[6] == ' ') return true;
return false;
default:
return false;
}
}
void WifiInterface::loop() {
if (!connected) return;
WiThrottle::loop(); // check heartbeats
// read anything into a buffer, collecting info on the way
while (loopstate != 99 && wifiStream->available()) {
int ch = wifiStream->read();
// echo the char to the diagnostic stream in escaped format
if (Diag::WIFI) StringFormatter::printEscape(ch); // DIAG in disguise
switch (loopstate) {
case 0: // looking for +IPD
connectionId = 0;
if (ch == '+') loopstate = 1;
break;
case 1: // Looking for I in +IPD
loopstate = (ch == 'I') ? 2 : 0;
break;
case 2: // Looking for P in +IPD
loopstate = (ch == 'P') ? 3 : 0;
break;
case 3: // Looking for D in +IPD
loopstate = (ch == 'D') ? 4 : 0;
break;
case 4: // Looking for , After +IPD
loopstate = (ch == ',') ? 5 : 0;
break;
case 5: // reading connection id
if (ch == ',') loopstate = 6;
else connectionId = 10 * connectionId + (ch - '0');
break;
case 6: // reading for length
if (ch == ':') loopstate = (datalength == 0) ? 99 : 7; // 99 is getout without reading next char
else datalength = datalength * 10 + (ch - '0');
streamer.flush(); // basically sets write point at start of buffer
break;
case 7: // reading data
streamer.write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer.
// This protects against buffer overflows even with things as innocent
// as a browser which send massive, irrlevent HTTP headers.
datalength--;
if (datalength == 0) {
buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later
loopstate = 99;
}
break;
case 10: // Waiting for > so we can send reply
if (millis() - loopTimeoutStart > LOOP_TIMEOUT) {
if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for > prompt or ERROR\n"));
loopstate = 0; // go back to +IPD
break;
}
if (ch == '>') {
// DIAG(F("\n> [%e]\n"),buffer);
wifiStream->print((char *) buffer);
loopTimeoutStart = millis();
loopstate = closeAfter ? 11 : 0;
break;
}
if (ch == '.') { // busy during send, delay and retry
loopstate = 12; // look for SEND OK finished
break;
}
break;
case 11: // Waiting for SEND OK or ERROR to complete so we can closeAfter
if (millis() - loopTimeoutStart > LOOP_TIMEOUT) {
if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for SEND OK or ERROR\n"));
loopstate = 0; // go back to +IPD
break;
}
if (ch == 'K') { // assume its in SEND OK
if (Diag::WIFI) DIAG(F("\n Wifi AT+CIPCLOSE=%d\r\n"), connectionId);
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId);
loopstate = 0; // wait for +IPD
}
break;
case 12: // Waiting for OK after send busy
if (ch == '+') { // Uh-oh IPD problem
if (Diag::WIFI) DIAG(F("\n\n Wifi ASYNC CLASH - LOST REPLY\n"));
connectionId = 0;
loopstate = 1;
}
if (ch == 'K') { // assume its in SEND OK
if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
loopTimeoutStart = millis();
loopstate = 10; // non-blocking loop waits for > before sending
break;
}
break;
} // switch
} // while
if (loopstate != 99) return;
// AT this point we have read an incoming message into the buffer
if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer);
streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer
// SIDE EFFECT WARNING:::
// We know that parser will read the entire buffer before starting to write to it.
// Otherwise we would have to copy the buffer elsewhere and RAM is in short supply.
closeAfter = false;
// Intercept HTTP requests
if (isHTTP()) {
if (httpCallback) httpCallback(&streamer, buffer);
else {
StringFormatter::send(streamer, F("HTTP/1.1 404 Not Found\nContent-Type: text/html\nConnnection: close\n\n"));
StringFormatter::send(streamer, F("<html><body>This is <b>not</b> a web server.<br/></body></html>"));
}
closeAfter = true;
if (connected) {
WifiInboundHandler::loop();
}
else if (buffer[0] == '<') parser.parse(&streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
else WiThrottle::getThrottle(connectionId)->parse(streamer, buffer);
if (streamer.available() == 0) {
// No reply
if (closeAfter) {
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId);
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId);
}
loopstate = 0; // go back to waiting for +IPD
return;
}
// prepare to send reply
buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later
if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available());
if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
loopTimeoutStart = millis();
loopstate = 10; // non-blocking loop waits for > before sending
}
#endif

View File

@@ -1,7 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of Asbelos DCC API
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,42 +17,37 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WifiInterface_h
#define WifiInterface_h
#include "FSH.h"
#include "DCCEXParser.h"
#include "MemStream.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd);
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
class WifiInterface {
class WifiInterface
{
public:
static void setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port);
static void loop();
static void ATCommand(const byte * command);
static void setHTTPCallback(HTTP_CALLBACK callback);
private:
static Stream * wifiStream;
static DCCEXParser parser;
static bool setup2( const __FlashStringHelper* SSSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port);
static bool checkForOK(const unsigned int timeout, const char* waitfor, bool echo, bool escapeEcho=true);
static bool isHTTP();
static HTTP_CALLBACK httpCallback;
static bool connected;
static bool closeAfter;
static byte loopstate;
static int datalength;
static int connectionId;
static unsigned long loopTimeoutStart;
static const byte MAX_WIFI_BUFFER=250;
static byte buffer[MAX_WIFI_BUFFER+1];
static MemStream streamer;
public:
static bool setup(long serial_link_speed,
const FSH *wifiESSID,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel);
static void loop();
static void ATCommand(const byte *command);
private:
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel);
static Stream *wifiStream;
static DCCEXParser parser;
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel);
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;
};
#endif

114
config.example.h Normal file
View File

@@ -0,0 +1,114 @@
/**********************************************************************
config.h
COPYRIGHT (c) 2020 Fred Decker
The configuration file for DCC-EX Command Station
**********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
// generating resitor on the current sense pin of the device. Failure to select
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
//
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
// |
// +-----------------------v
//
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
//
#define IP_PORT 2560
/////////////////////////////////////////////////////////////////////////////////////
//
// NOTE: Only supported on Arduino Mega
// Set to false if you not even want it on the Arduino Mega
//
#define ENABLE_WIFI true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE WiFi Parameters (only in effect if WIFI is on)
//
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
// the <+> commands and this sketch will not change anything over
// AT commands and the other WIFI_* defines below do not have any effect.
//#define DONT_TOUCH_WIFI_CONF
//
// WIFI_SSID is the network name IF you want to use your existing home network.
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
//
// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD,
// then the WiFi chip will first try to connect to the previously
// configured network and if that fails fall back to Access Point mode.
// The SSID of the AP will be automatically set to DCCEX_*.
// If you DO set the WIFI_SSID then the WiFi chip will try to connect
// to that (home) network in station (client) mode. If a WIFI_PASSWORD
// is set (recommended), that password will be used for AP mode.
// The AP mode password must be at least 8 characters long.
//
// Your SSID may not contain ``"'' (double quote, ASCII 0x22).
#define WIFI_SSID "Your network name"
//
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "Your network passwd"
//
// WIFI_HOSTNAME: You probably don't need to change this
#define WIFI_HOSTNAME "dccex"
//
// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented,
// WiFi will be enabled (Mega only). The default channel is set to "1" whether
// 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
/////////////////////////////////////////////////////////////////////////////////////
//
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
// is not for Wifi. You will then need the Arduino Ethernet library as well
//
//#define ENABLE_ETHERNET true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
//
//#define IP_ADDRESS { 192, 168, 1, 200 }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
//
// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset.
// or one using a Hitachi HD44780.
// OR an I2C Oled screen.
// To enable, uncomment one of the lines below
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
// #define LCD_DRIVER 0x3F,16,2
//OR define OLED_DRIVER width,height in pixels (address auto detected)
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
// Also 132x64 I2C SH1106 devices.
// #define OLED_DRIVER 128,32
/////////////////////////////////////////////////////////////////////////////////////

50
defines.h Normal file
View File

@@ -0,0 +1,50 @@
/*
© 2020, Harald Barth.
This file is part of CommandStation-EX
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
//
// WIFI_ON: All prereqs for running with WIFI are met
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#else
#define WIFI_ON false
#endif
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
#define ETHERNET_ON true
#else
#define ETHERNET_ON false
#endif
#if WIFI_ON && ETHERNET_ON
#error Command Station does not support WIFI and ETHERNET at the same time.
#endif
////////////////////////////////////////////////////////////////////////////////
//
// This defines the speed at which the Arduino will communicate with the ESP8266 module.
// Currently only devices which can communicate at 115200 are supported.
//
#define WIFI_SERIAL_LINK_SPEED 115200

View File

@@ -1,152 +0,0 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is a demonstattion of calling the DCC-EX API
*/
#include "DCCEX.h"
#ifdef ARDUINO_AVR_UNO
#include <SoftwareSerial.h>
SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO
#define WIFI_BAUD 9600
#else
#define WIFI_BAUD 115200
#endif
// this code is here to demonstrate use of the DCC API and other techniques
// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from
// the usb or wifi streamm. It demonstrates how a command may be intercepted
// or even a new command created without having to break open the API library code.
// The filter is permitted to use or modify the parameter list before passing it on to
// the standard parser. By setting the opcode to 0, the standard parser will
// just ignore the command on the assumption that you have already handled it.
//
// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup().
void myComandFilter(Print * stream, byte & opcode, byte & paramCount, int p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
switch (opcode) {
case '!': // Create a bespoke new command to clear all loco reminders <!> or specific locos e.g <! 3 4 99>
if (paramCount==0) DCC::forgetAllLocos();
else for (int i=0;i<paramCount;i++) DCC::forgetLoco(p[i]);
opcode=0; // tell parser to ignore this command as we have done it already
break;
default: // drop through and parser will use the command unaltered.
break;
}
}
// This is an OPTIONAL example of a HTTP filter...
// If you have configured wifi and an HTTP request is received on the Wifi connection
// it will normally be rejected 404 Not Found.
// If you wish to handle HTTP requests, you can create a filter and ask the WifiInterface to
// call your code for each detected http request.
void myHttpFilter(Print * stream, byte * cmd) {
(void)cmd; // Avoid compiler warning because this example doesnt use this parameter
// BEWARE - As soon as you start responding, the cmd buffer is trashed!
// You must get everything you need from it before using StringFormatter::send!
StringFormatter::send(stream,F("HTTP/1.1 200 OK\nContent-Type: text/html\nConnnection: close\n\n"));
StringFormatter::send(stream,F("<html><body>This is my HTTP filter responding.<br/></body></html>"));
}
// Callback functions are necessary if you call any API that must wait for a response from the
// programming track. The API must return immediately otherwise other loop() functions would be blocked.
// Your callback function will be invoked when the data arrives from the prog track.
// See the DCC:getLocoId example in the setup function.
void myCallback(int result) {
DIAG(F("\n getting Loco Id callback result=%d"),result);
}
// Create a serial command parser... This is OPTIONAL if you don't need to handle JMRI type commands
// from the Serial port.
DCCEXParser serialParser;
// Try monitoring the memory
#include "freeMemory.h"
int ramLowWatermark=32767; // This figure gets overwritten dynamically in loop()
void setup() {
// The main sketch has responsibilities during setup()
// Responsibility 1: Start the usb connection for diagnostics and possible JMRI input
// DIAGSERAL is normally Serial but uses SerialUSB on a SAMD processor
DIAGSERIAL.begin(115200);
while(!DIAGSERIAL);
// Responsibility 2: 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
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
DCC::begin(STANDARD_MOTOR_SHIELD);
// Responsibility 3: **Optionally** Start the WiFi interface if required.
// NOTE: On a Uno you will have to provide a SoftwareSerial
// configured for the pins connected to the Wifi card
// and a 9600 baud rate.
// setup(serial, F(router name) or NULL, F(router password), F(hostname), F(AcessPoint name) or NULL , port)
// (port 3532 is 0xDCC decimal.)
Serial1.begin(WIFI_BAUD);
WifiInterface::setup(Serial1, F("BTHub5-M6PT"), F("49de8d4862"),F("DCCEX"),3532);
// Optionally tell the Wifi parser to use my http filter.
// This will intercept http commands from Wifi.
WifiInterface::setHTTPCallback(myHttpFilter);
// This is just for demonstration purposes
DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n"));
DCC::getLocoId(myCallback); // myCallback will be called with the result
DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n"));
// Optionally tell the command parser to use my example filter.
// This will intercept JMRI commands from both USB and Wifi
DCCEXParser::setFilter(myComandFilter);
DIAG(F("\nReady for JMRI commands\n"));
}
void loop() {
// The main sketch has responsibilities during loop()
// Responsibility 1: Handle DCC background processes
// (loco reminders and power checks)
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(DIAGSERIAL);
// Responsibility 3: Optionally handle any incoming WiFi traffic
WifiInterface::loop();
// Your additional loop code
// Optionally report any decrease in memory (will automatically trigger on first call)
int freeNow=freeMemory();
if (freeNow<ramLowWatermark) {
ramLowWatermark=freeNow;
DIAG(F("\nFree RAM=%d\n"),ramLowWatermark);
}
}

View File

@@ -1,29 +0,0 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This is a basic, no frills DCC-EX example of a DCC++ compatible setup.
* There are more advanced examples in the examples folder i
*/
#include "DCCEX.h"
// Create parser for <> commands coming from keyboard or JMRI on thr USB connection.
DCCEXParser serialParser;
void setup() {
// Responsibility 1: Start the usb connection for diagnostics and possible JMRI input
Serial.begin(115200);
// Responsibility 2: Start the DCC engine with information about your Motor Shield.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorDriverss.h
DCC::begin(STANDARD_MOTOR_SHIELD);
}
void loop() {
// Responsibility 1: Handle DCC background processes (loco reminders and power checks)
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
}

View File

@@ -1,73 +0,0 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is a demonstattion of setting up a DCC-EX
* Command station to support direct connection of WiThrottle devices
* such as "Engine Driver". If you contriol your layout through JMRI
* then DON'T connect throttles to this wifi, connect them to JMRI.
*
* This is just 3 statements longer than the basic setup.
*
* THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT.
* REFER TO SEPARATE EXAMPLE.
*/
#include "DCCEX.h"
// Create a serial command parser... Enables certain diagnostics and commands
// to be issued from the USB serial console
// This is NOT intended for JMRI....
DCCEXParser serialParser;
void setup() {
// The main sketch has responsibilities during setup()
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
Serial.begin(115200);
// 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
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
DCC::begin(STANDARD_MOTOR_SHIELD);
// Start the WiFi interface.
// NOTE: References to Serial1 are for the serial port used to connect
// your wifi chip/shield.
Serial1.begin(115200); // BAUD rate of your Wifi chip/shield
WifiInterface::setup(Serial1,
F("BTHub5-M6PT"), // Router name
F("49de8d4862"), // Router password
F("DCCEX"), // Hostname (ignored by some wifi chip firmware)
3532); // port (3532 is 0xDCC)
}
void loop() {
// The main sketch has responsibilities during loop()
// Responsibility 1: Handle DCC background processes
// (loco reminders and power checks)
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic
WifiInterface::loop();
}

111
freeMemory.cpp Normal file
View File

@@ -0,0 +1,111 @@
/*
* © 2020, Harald Barth
* © 2021, Neil McKechnie
*
* This file is part of Asbelos DCC-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "freeMemory.h"
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
#if defined(__arm__)
extern "C" char* sbrk(int);
#elif defined(__AVR__)
extern char *__brkval;
extern char *__malloc_heap_start;
#else
#error Unsupported board type
#endif
static volatile int minimum_free_memory = __INT_MAX__;
#if !defined(__IMXRT1062__)
static inline int freeMemory() {
char top;
#if defined(__arm__)
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(__AVR__)
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#else
#error bailed out already above
#endif
}
// Return low memory value.
int minimumFreeMemory() {
byte sreg_save = SREG;
noInterrupts(); // Disable interrupts
int retval = minimum_free_memory;
SREG = sreg_save; // Restore interrupt state
return retval;
}
#else
#if defined(ARDUINO_TEENSY40)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
static const unsigned OCRAM_SIZE = 512;
static const unsigned FLASH_SIZE = 1984;
#elif defined(ARDUINO_TEENSY41)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
static const unsigned OCRAM_SIZE = 512;
static const unsigned FLASH_SIZE = 7936;
#if TEENSYDUINO>151
extern "C" uint8_t external_psram_size;
#endif
#endif
static inline int freeMemory() {
extern unsigned long _ebss;
extern unsigned long _sdata;
extern unsigned long _estack;
const unsigned DTCM_START = 0x20000000UL;
unsigned dtcm = (unsigned)&_estack - DTCM_START;
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
unsigned freemem = dtcm - (stackinuse + varsinuse);
return freemem;
}
// Return low memory value.
int minimumFreeMemory() {
//byte sreg_save = SREG;
//noInterrupts(); // Disable interrupts
int retval = minimum_free_memory;
//SREG = sreg_save; // Restore interrupt state
return retval;
}
#endif
// Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other
// called subroutines. Must be called with interrupts disabled.
//
// Although __brkval may go up and down as heap memory is allocated
// and freed, this function records only the worst case encountered.
// So even if all of the heap is freed, the reported minimum free
// memory will not increase.
//
void updateMinimumFreeMemory(unsigned char extraBytes) {
int spare = freeMemory()-extraBytes;
if (spare < 0) spare = 0;
if (spare < minimum_free_memory) minimum_free_memory = spare;
}

View File

@@ -1,20 +1,25 @@
/*
* © 2020, Harald Barth
* © 2021, Neil McKechnie
*
* This file is part of DCC-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef freeMemory_h
#define freeMemory_h
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
void updateMinimumFreeMemory(unsigned char extraBytes=0);
int minimumFreeMemory();
#endif

View File

@@ -1,12 +1,16 @@
ECHO ON
FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i
echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt
avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt
SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf
avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
notepad %TMP%\OBJDUMP_%a%.txt
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
%TMP%\OBJDUMP_%a%.txt
EXIT

View File

@@ -4,9 +4,10 @@
ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}')
PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH
avr-objdump --private=mem-usage /tmp/arduino_build_233823/Blinkhabaplus.ino.elf
LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1)
avr-objdump --private=mem-usage "$LASTBUILD"
for segment in .text .data .bss ; do
echo '++++++++++++++++++++++++++++++++++'
avr-objdump -x -C /tmp/arduino_build_233823/Blinkhabaplus.ino.elf | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r
avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r
done

View File

@@ -24,6 +24,8 @@ upload_protocol = atmel-ice
lib_deps =
${env.lib_deps}
SparkFun External EEPROM Arduino Library
monitor_speed = 115200
monitor_flags = --echo
[env:mega2560]
platform = atmelavr
@@ -31,7 +33,10 @@ board = megaatmega2560
framework = arduino
lib_deps =
${env.lib_deps}
DIO2
arduino-libraries/Ethernet
SPI
monitor_speed = 115200
monitor_flags = --echo
[env:mega328]
platform = atmelavr
@@ -39,7 +44,10 @@ board = uno
framework = arduino
lib_deps =
${env.lib_deps}
DIO2
arduino-libraries/Ethernet
SPI
monitor_speed = 115200
monitor_flags = --echo
[env:unowifiR2]
platform = atmelmegaavr
@@ -47,7 +55,11 @@ board = uno_wifi_rev2
framework = arduino
lib_deps =
${env.lib_deps}
DIO2
arduino-libraries/Ethernet
SPI
monitor_speed = 115200
monitor_flags = --echo
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g
[env:uno]
platform = atmelavr
@@ -55,4 +67,7 @@ board = uno
framework = arduino
lib_deps =
${env.lib_deps}
DIO2
arduino-libraries/Ethernet
SPI
monitor_speed = 115200
monitor_flags = --echo

139
release_notes.md Normal file
View File

@@ -0,0 +1,139 @@
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
**Known Bugs:**
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
**Summary of the key new features added to CommandStation-EX V3.0.7**
- **Support for 28 Speed steps** - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- **Improved overload messages with raw values (relative to offset)**
**Summary of the key new features added to CommandStation-EX V3.0.6**
- **Prevent compiler warning about deprecated B constants**
- **Fix Bug that did not let us transmit 5 byte sized packets** - 5 Byte commands like PoM (programming on main) were not being sent correctly
- **Huge function numbers (DCC BinaryStateControl)** - Support Functions beyond F28
- **<!> ESTOP all** - New command to emergency stop all locos on the main track
- **<- [cab]> estop and forget cab/all cabs** - Stop and remove loco from the CS. Stops the repeating throttle messages
- **``<D RESET>`` command to reboot arduino**
- **Automatic sensor offset detect** -
- **Improved startup msgs from Motor Drivers (accuracy and auto sense factors)** -
- **Drop post-write verify** - No need to double check CV writes. Writes are now even faster.
- **Allow current sene pin set to UNUSED_PIN** - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- **Fix Fn Key startup with loco ID and fix state change for F16-28**
- removed ethernet mac config and made it automatic
- show wifi ip and port on lcd
- auto load config.example.h with warningh
- dropped example .ino files
- corrected .ino comments
- pololu fault pin handling
- waveform speed/simplicity improvements
- improved pin speed in waveform
- portability to nanoEvery and UnoWifiRev2 CPUs
- analog read speed improvements
- drop need for DIO2 library
- improved current check code
- linear <a> command
- removed need for ArduinoTimers files
- removed <D DCC SLOW>
- Removed option to choose different timer
- Added EX-RAIL hooks for later
- fixed Turnout list
- allow command keywords in mixed case
- dropped unused memstream
- PWM pin accuracy if requirements met.
**Summary of the key new features added to CommandStation-EX V3.0.4**
- **"Drive-Away" Feature added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track.
- **WiFi Startup Fixes**
**Summary of the key new features added to CommandStation-EX V3.0.3**
- **<W addr> command to write loco address and clear consist**
- **<R> command will allow for consist address**
- **Startup commands implemented**
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
- **Simultaneously update JMRI to handle new current meter**
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- **Add back fix for jitter**
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**
**Summary of the key new features added to CommandStation-EX V3.0.0:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - ```<#>``` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - - Engine Driver and JMRI Interface
- Scott Catalanno - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - (KCSmith)
**WebThrotle-EX**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Keith Ledbetter
- BradVan der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA

27
version.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef version_h
#define version_h
#include "StringFormatter.h"
#define VERSION "3.0.14"
// 3.0.14 gap in ack tolerant fix, prog track power management over join fix.
// 3.0.13 Functions>127 fix
// 3.0.12 Fix HOSTNAME function for STA mode for WiFi
// 3.0.11 ?
// 3.0.11 28 speedstep support
// 3.0.10 Teensy Support
// 3.0.9 rearranges serial newlines for the benefit of JMRI.
// 3.0.8 Includes <* *> wraps around DIAGs for the benefit of JMRI.
// 3.0.7 Includes merge from assortedBits (many changes) and ACK manager change for lazy decoders
// 3.0.6 Includes:
// Fix Bug that did not let us transmit 5 byte sized packets like PoM
// 3.0.5 Includes:
// Fix Fn Key startup with loco ID and fix state change for F16-28
// 3.0.4 Includes:
// Wifi startup bugfixes
// 3.0.3 Includes:
// <W addr> command to write loco address and clear consist
// <R> command will allow for consist address
// Startup commands implemented
#endif