1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-31 03:13:45 +02:00

Compare commits

..

142 Commits

Author SHA1 Message Date
Harald Barth
39cc9aecfc yaml syntax 2021-02-15 10:53:51 +01:00
Harald Barth
803c581825 Merge branch 'nanoEvery2-pololu' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2-pololu 2021-02-15 10:35:21 +01:00
Harald Barth
d377468006 only do the sha generation on master 2021-02-15 10:35:05 +01:00
Harald Barth
37fd5fe893 Committing a SHA 2021-02-15 00:01:49 +00:00
Harald Barth
edeaad4a9d Merge branch 'nanoEvery2-pololu' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2-pololu 2021-02-15 01:00:55 +01:00
Harald Barth
5edac75a9e remove warning 2021-02-15 00:59:33 +01:00
Harald Barth
947ef1d146 Committing a SHA 2021-02-14 23:32:44 +00:00
Harald Barth
9368a69e30 Handle shields with common fault pins (Pololu) 2021-02-15 00:31:36 +01:00
Asbelos
8240a24193 Committing a SHA 2021-02-14 20:21:18 +00:00
Asbelos
d4be9a8a6c Merge branch 'nanoEvery2' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2 2021-02-14 20:20:46 +00:00
Asbelos
5a9d5d1f96 Reduce duplicated F() macros
Compiler isn't as clever as one might expect
2021-02-14 20:20:36 +00:00
Asbelos
cae30f5faf Committing a SHA 2021-02-14 13:10:20 +00:00
Asbelos
919b975adc Merge branch 'nanoEvery2' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2 2021-02-14 13:09:48 +00:00
Asbelos
7e37580466 Wifi channel and code cleaning 2021-02-14 13:09:36 +00:00
Asbelos
79d991db00 Committing a SHA 2021-02-13 17:21:43 +00:00
Asbelos
7d72624232 Merge branch 'nanoEvery2' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2 2021-02-13 17:20:58 +00:00
Asbelos
a8bd530451 Reinstate IP_PORT
Arrrrgh!
2021-02-13 17:20:45 +00:00
Asbelos
5c5a2e924c Committing a SHA 2021-02-13 16:16:19 +00:00
Asbelos
46e0fcdc54 fix wifi setup issue 2021-02-13 16:15:46 +00:00
Asbelos
ba99de17bf Committing a SHA 2021-02-12 13:31:55 +00:00
Asbelos
75ab2ab533 PWM pin implementation 2021-02-12 13:31:23 +00:00
Asbelos
9d5f579847 Committing a SHA 2021-02-09 13:46:17 +00:00
Asbelos
f09eee25dd UnoRev2 protection 2021-02-09 13:43:40 +00:00
Asbelos
6f70bec67e Committing a SHA 2021-02-08 12:29:24 +00:00
Asbelos
6737785388 Comments and a reliability fix. 2021-02-08 12:28:16 +00:00
Asbelos
c1a8206667 Merge branch 'wifisetupfix' into nanoEvery2 2021-02-08 09:44:00 +00:00
Asbelos
61931bf40a Committing a SHA 2021-02-08 08:28:05 +00:00
Asbelos
c21bb6053a Merge branch 'nanoEvery2' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2 2021-02-08 08:26:44 +00:00
Asbelos
4c9182d95f Fix for nano compile 2021-02-08 08:26:32 +00:00
Asbelos
fd4d454463 Committing a SHA 2021-02-07 20:26:32 +00:00
Asbelos
3b74e16dd1 Github SHA 2021-02-07 20:25:54 +00:00
Asbelos
f120a1e43d Ethernet simulated mac
Plus fixed listening port
2021-02-07 15:46:36 +00:00
Asbelos
a9a6b56654 IP/PORT on LCD 2021-02-07 10:28:05 +00:00
Asbelos
f687625bfa Config/example loading 2021-02-07 10:27:46 +00:00
Asbelos
7bffe0bd1d Config comments and example use 2021-02-06 10:55:11 +00:00
Asbelos
446beff20a Merge branch 'nanoEvery2' of https://github.com/DCC-EX/CommandStation-EX into nanoEvery2 2021-02-04 10:45:52 +00:00
Asbelos
f796f23d7b minor performance tweaks 2021-02-04 10:45:45 +00:00
Harald Barth
514bb31cdd Distunguish between in/out of FASTPIN 2021-02-04 11:43:13 +01:00
Asbelos
653c421400 UNTESTED fast power,brake,fault pins 2021-02-02 11:30:35 +00:00
Asbelos
9dd210fa14 Drop analogReadFast (see DCCTimer)
AnalogRead speed set in DCCTimer for ease of porting.
Code tidy and diagnostics in MotorDriver
2021-02-01 10:06:54 +00:00
Asbelos
13757c8c57 DIO2 replacement
Currently for writing signal pins during waveform.
2021-01-31 13:43:35 +00:00
Asbelos
dc36cbee0c Merge branch 'master' into nanoEvery2 2021-01-31 10:38:19 +00: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
Harald Barth
4bf44f0051 Remove unued if 2021-01-30 18:19:25 +01:00
Harald Barth
b6847419fc Retry harder for AP mode 2021-01-30 18:15:05 +01:00
Harald Barth
0d8f45efad bugfixes wifi setup 2021-01-30 16:34:06 +01:00
Harald Barth
13dec796c1 version++ 2021-01-30 15:36:28 +01:00
Harald Barth
d577606ee9 redo flow through wifisetup again 2021-01-30 13:10:15 +01:00
Harald Barth
aba937f42f fix wrong format letter 2021-01-30 11:20:11 +01:00
Asbelos
6958f029b7 Fix no-loco id
Has to handle -1 correctly
2021-01-28 20:55:54 +00:00
Asbelos
1b19b61ebd Current check cleanup 2021-01-28 17:18:38 +00:00
Asbelos
7df07b03e4 Microtuning waveform
Significant reduction in code parths and call overheads
2021-01-27 16:58:42 +00:00
Asbelos
4e6f79589a Wave-state machine ( part 11) 2021-01-27 09:46:08 +00:00
Asbelos
b8d61fb839 Merge branch 'LinearA' into nanoEvery2 2021-01-26 12:00:22 +00:00
Asbelos
7092f7de33 Correcting non-portables merged from master 2021-01-26 11:54:51 +00:00
Asbelos
13593ecf4f Timer working
And slow wave crap removed
2021-01-26 10:55:46 +00:00
Asbelos
a4b63013ba Almost... 2021-01-26 09:04:09 +00:00
Asbelos
13e516f8b2 Merge branch 'portableTimer' into nanoEvery2 2021-01-25 21:12:06 +00:00
Asbelos
cbb039c02f Timer port 2021-01-25 20:20:41 +00:00
Asbelos
8a9feaef22 Clean simple Timer interface
Removes overkill files, puts all timer in a single small file. (DCCTimer)
2021-01-25 15:26:39 +00:00
Harald Barth
032b36ab45 Add the F define to be on safe side if it is not present in the library core code 2021-01-23 22:07:28 +01:00
Asbelos
9399aca63d Allow lower case keywords 2021-01-21 23:13:08 +00:00
Asbelos
3dede9eabe Linear address <a> cmd 2021-01-21 23:06:47 +00: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
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
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
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
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
Asbelos
ff81b4d1b4 cleaning up 2021-01-03 10:19:37 +00:00
Asbelos
cb0d2bcdc5 Cleanup 2021-01-03 09:11:11 +00:00
dexslab
740dcc7db4 Merge remote-tracking branch 'origin/master' into dex/unowifi 2020-12-28 18:11:43 -05:00
dexslab
1bc27a40e8 Add everytimerb.h 2020-12-28 18:11:40 -05:00
dexslab
ba873fb8bc Changed to EveryTimerB 2020-12-27 18:44:25 -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
dexslab
e7c76bf806 Merge remote-tracking branch 'origin/master' into dex/unowifi 2020-12-27 18:02:11 -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
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
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
ggee
040dc35b93 Add LCD and OLED libs (#102)
Add the example platformio configuration file
2020-12-17 17:33:29 -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
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
dexslab
bdab5d0ff7 Remove extra timer that was already added 2020-12-09 11:39:34 -05:00
Asbelos
7d888e9aa9 Merge branch 'dex/unowifi' of https://github.com/DCC-EX/CommandStation-EX into dex/unowifi 2020-12-09 12:01:36 +00:00
Asbelos
74c22c83fc FlasString and Timers for Uno Wifi
ALL these changes should be portable back to master
2020-12-09 11:57:38 +00:00
dexslab
b0388bfc67 Fixes for compile arduino unowifi r2 2020-12-08 14:42:21 -05:00
Asbelos
d96c919fee Uno Wifi compiling 2020-12-08 18:01:22 +00: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
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
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
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
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
dexslab
eececa322a Start adding back unowifi stuffz 2020-11-20 08:34:36 -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
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
59 changed files with 2460 additions and 1980 deletions

1
.gitattributes vendored
View File

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

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,4 +6,5 @@ Release/*
.gcc-flags.json
.pio/
.vscode/
config.h
config.h
.vscode/extensions.json

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

View File

@@ -1,15 +1,49 @@
////////////////////////////////////////////////////////////////////////////////////
// © 2020, Chris Harlow. All rights reserved.
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
//
// This file is a demonstattion of setting up a DCC-EX
// Command station with optional support for 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 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.
//
// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM.
// 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.
////////////////////////////////////////////////////////////////////////////////////
#include "config.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
/*
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
*
*
* 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"
// Create a serial command parser for the USB connection,
@@ -35,7 +69,7 @@ void setup()
// 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);
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
@@ -49,10 +83,19 @@ void setup()
// 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(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
LCD(1,F("Ready"));
}
@@ -75,6 +118,10 @@ void loop()
EthernetInterface::loop();
#endif
#if defined(RMFT_ACTIVE)
RMFT::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use
// Optionally report any decrease in memory (will automatically trigger on first call)

146
DCC.cpp
View File

@@ -17,12 +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.
@@ -43,17 +44,17 @@ const byte FN_GROUP_3=0x04;
const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10;
__FlashStringHelper* DCC::shieldName=NULL;
FSH* DCC::shieldName=NULL;
void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
shieldName=(__FlashStringHelper*)motorShieldName;
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
shieldName=(FSH *)motorShieldName;
DIAG(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, timerNumber);
DCCWaveform::begin(mainDriver,progDriver);
}
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
@@ -228,18 +229,18 @@ void DCC::setProgTrackBoost(bool on) {
DCCWaveform::progTrackBoosted=on;
}
__FlashStringHelper* DCC::getMotorShieldName() {
FSH* DCC::getMotorShieldName() {
return shieldName;
}
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
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
@@ -247,7 +248,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)
@@ -255,7 +256,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)
@@ -264,7 +265,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)
@@ -273,7 +274,7 @@ 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
@@ -281,7 +282,7 @@ const ackOp PROGMEM WRITE_BYTE_PROG[] = {
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
@@ -306,7 +307,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
@@ -329,12 +330,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,
@@ -366,7 +388,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,
@@ -378,6 +400,44 @@ const ackOp PROGMEM LOCO_ID_PROG[] = {
FAIL
};
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE,
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore router without cv19 support
// Turn off long address flag
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W0,WACK,NAKFAIL,
SETCV, (ackOp)1,
SETBYTEL, // low byte of word
WB,WACK,NAKFAIL,
VB,WACK,ITCB,
FAIL
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore router without cv19 support
// Turn on long address flag cv29 bit 5
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W1,WACK,NAKFAIL,
// Store high byte of address in cv 17
SETCV, (ackOp)17,
SETBYTEH, // high byte of word
WB,WACK,NAKFAIL,
VB,WACK,NAKFAIL,
// store
SETCV, (ackOp)18,
SETBYTEL, // low byte of word
WB,WACK,NAKFAIL,
VB,WACK,ITC1, // callback(1) means Ok
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.
@@ -420,6 +480,17 @@ void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
}
void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) {
if (id<1 || id>10239) { //0x27FF according to standard
callback(-1);
return;
}
if (id<=127)
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback, blocking);
else
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0;
@@ -552,7 +623,8 @@ 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;
@@ -567,6 +639,13 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
if (blocking) ackManagerLoop(blocking);
}
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
ackManagerWord=wordval;
ackManagerProg = program;
ackManagerCallback = callback;
if (blocking) ackManagerLoop(blocking);
}
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.
@@ -581,7 +660,7 @@ bool DCC::checkResets(bool blocking, uint8_t numResets) {
void DCC::ackManagerLoop(bool blocking) {
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
@@ -668,7 +747,15 @@ void DCC::ackManagerLoop(bool blocking) {
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 & 0xF)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(ackManagerByte & 0x7F);
return;
}
break;
@@ -700,12 +787,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:
@@ -723,7 +823,7 @@ 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:

21
DCC.h
View File

@@ -21,10 +21,10 @@
#include <Arduino.h>
#include "MotorDriver.h"
#include "MotorDrivers.h"
#include "FSH.h"
typedef void (*ACK_CALLBACK)(int result);
enum ackOp
enum ackOp : byte
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
@@ -37,12 +37,16 @@ enum ackOp
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
@@ -60,7 +64,7 @@ const byte MAX_LOCOS = 50;
class DCC
{
public:
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
static void loop();
// Public DCC API functions
@@ -88,13 +92,14 @@ public:
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 setLocoId(int id,ACK_CALLBACK callback, bool blocking = false);
// 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);
static __FlashStringHelper *getMotorShieldName();
static FSH *getMotorShieldName();
private:
struct LOCO
@@ -110,7 +115,7 @@ private:
static void setFunctionInternal(int cab, byte fByte, byte eByte);
static bool issueReminder(int reg);
static int nextLoco;
static __FlashStringHelper *shieldName;
static FSH *shieldName;
static LOCO speedTable[MAX_LOCOS];
static byte cv1(byte opcode, int cv);
@@ -124,10 +129,12 @@ private:
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 ackManagerSetup(int wordval, 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)
@@ -156,9 +163,7 @@ private:
#elif defined(ARDUINO_AVR_MEGA2560)
#define ARDUINO_TYPE "MEGA"
#elif defined(ARDUINO_ARCH_MEGAAVR)
#define ARDUINO_TYPE "UNOWIFIR2"
#elif defined(ARDUINO_SAMD_ZERO)
#define ARDUINO_TYPE "FireBoxMK1"
#define ARDUINO_TYPE "MEGAAVR"
#else
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
#endif

View File

@@ -10,9 +10,15 @@
#include "DCCEXParser.h"
#include "version.h"
#include "WifiInterface.h"
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#endif
#include "LCD_Implementation.h"
#include "freeMemory.h"
#include <Arduino.h>
#if __has_include ( "myAutomation.h")
#include "RMFT.h"
#define RMFT_ACTIVE
#endif
#endif

View File

@@ -49,8 +49,10 @@ const int HASH_KEYWORD_PROGBOOST = -6353;
const int HASH_KEYWORD_EEPROM = -7168;
const int HASH_KEYWORD_LIMIT = 27413;
const int HASH_KEYWORD_ETHERNET = -30767;
const int HASH_KEYWORD_MAX = 16244;
const int HASH_KEYWORD_MIN = 15978;
int DCCEXParser::stashP[MAX_PARAMS];
int DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
Print *DCCEXParser::stashStream = NULL;
@@ -100,7 +102,7 @@ void DCCEXParser::loop(Stream &stream)
Sensor::checkAll(&stream); // Update and print changes
}
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
{
byte state = 1;
byte parameterCount = 0;
@@ -109,10 +111,10 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
bool signNegative = false;
// clear all parameters in case not enough found
for (int i = 0; i < MAX_PARAMS; i++)
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
result[i] = 0;
while (parameterCount < MAX_PARAMS)
while (parameterCount < MAX_COMMAND_PARAMS)
{
byte hot = *remainingCmd;
@@ -141,6 +143,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
runningValue = 10 * runningValue + (hot - '0');
break;
}
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
if (hot >= 'A' && hot <= 'Z')
{
// Since JMRI got modified to send keywords in some rare cases, we need this
@@ -158,24 +161,97 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
return parameterCount;
}
int DCCEXParser::splitHexValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
{
byte state = 1;
byte parameterCount = 0;
int runningValue = 0;
const byte *remainingCmd = cmd + 1; // skips the opcode
// clear all parameters in case not enough found
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
result[i] = 0;
while (parameterCount < MAX_COMMAND_PARAMS)
{
byte hot = *remainingCmd;
switch (state)
{
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
state = 2;
continue;
case 2: // checking first hex digit
runningValue = 0;
state = 3;
continue;
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 16 * runningValue + (hot - '0');
break;
}
if (hot >= 'A' && hot <= 'F')
{
runningValue = 16 * runningValue + 10 + (hot - 'A');
break;
}
if (hot >= 'a' && hot <= 'f')
{
runningValue = 16 * runningValue + 10 + (hot - 'a');
break;
}
if (hot==' ' || hot=='>' || hot=='\0') {
result[parameterCount] = runningValue;
parameterCount++;
state = 1;
continue;
}
return -1; // invalid hex digit
}
remainingCmd++;
}
return parameterCount;
}
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
{
filterCallback = filter;
}
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
{
filterRMFTCallback = filter;
}
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
{
atCommandCallback = callback;
}
// Parse an F() string
void DCCEXParser::parse(const FSH * cmd) {
int size=strlen_P((char *)cmd)+1;
char buffer[size];
strcpy_P(buffer,(char *)cmd);
parse(&Serial,(byte *)buffer,true);
}
// See documentation on DCC class for info on this section
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{
(void)EEPROM; // tell compiler not to warn this is unused
if (Diag::CMD)
DIAG(F("\nPARSING:%s\n"), com);
int p[MAX_PARAMS];
int p[MAX_COMMAND_PARAMS];
while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces
byte params = splitValues(p, com);
@@ -183,6 +259,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
if (filterCallback)
filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0')
filterRMFTCallback(stream, opcode, params, p);
// Functions return from this switch if complete, break from switch implies error <X> to send
switch (opcode)
@@ -210,7 +288,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
else
break;
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds
// Convert DCC-EX protocol speed steps where
// -1=emergency stop, 0-126 as speeds
// to DCC 0=stop, 1= emergency stop, 2-127 speeds
if (tspeed > 126 || tspeed < -1)
break; // invalid JMRI speed code
@@ -236,12 +315,33 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
return;
break;
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
if (p[2] != (p[2] & 1))
return;
DCC::setAccessory(p[0], p[1], p[2] == 1);
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
{
int address;
byte subaddress;
byte activep;
if (params==2) { // <a LINEARADDRESS ACTIVATE>
address=(p[0] - 1) / 4 + 1;
subaddress=(p[0] - 1) % 4;
activep=1;
}
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
address=p[0];
subaddress=p[1];
activep=2;
}
else break; // invalid no of parameters
if (
((address & 0x01FF) != address) // invalid address (limit 9 bits )
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits )
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
) break;
DCC::setAccessory(address, subaddress,p[activep]==1);
}
return;
case 'T': // TURNOUT <T ...>
if (parseT(stream, params, p))
return;
@@ -265,10 +365,28 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
// Re-parse the command using a hex-only splitter
params=splitHexValues(p,com)-1; // drop REG
if (params<1) break;
{
byte packet[params];
for (int i=0;i<params;i++) {
packet[i]=(byte)p[i+1];
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)\n"), i, packet[i], packet[i]);
}
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
}
return;
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
break;
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
if (!stashCallback(stream, p))
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco, blocking);
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
return;
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
@@ -318,7 +436,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
if (params == 0)
if (params == 0 ||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
{
DCCWaveform::mainTrack.setPowerMode(mode);
DCCWaveform::progTrack.setPowerMode(mode);
@@ -356,8 +475,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
}
return;
case 'c': // READ CURRENT <c>
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.getLastCurrent());
case 'c': // SEND METER RESPONSES <c>
// <c MeterName value C/V unit min max res warn>
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>"), DCCWaveform::mainTrack.getCurrentmA(),
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
return;
case 'Q': // SENSORS <Q>
@@ -367,9 +489,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case 's': // <s>
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
Turnout::printAll(stream); //send all Turnout states
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
// TODO Send stats of speed reminders table
// TODO send status of turnouts etc etc
return;
return;
case 'E': // STORE EPROM <E>
EEStore::store();
@@ -402,6 +526,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
atCommandCallback(com);
return;
}
@@ -447,7 +573,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <Z>
case 0: // <Z> list Output definitions
{
bool gotone = false;
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
@@ -509,13 +635,14 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
{
switch (params)
{
case 0: // <T> show all turnouts
case 0: // <T> list turnout definitions
{
bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
{
gotOne = true;
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
StringFormatter::send(stream, F("<H %d %d %d %d>"), tt->data.id, tt->data.address,
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
}
return gotOne; // will <X> if none found
}
@@ -532,7 +659,7 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
if (!tt)
return false;
tt->activate(p[1]);
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
}
return true;
@@ -564,7 +691,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <S> lit sensor states
case 0: // <S> list sensor definitions
if (Sensor::firstSensor == NULL)
return false;
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
@@ -594,12 +721,22 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory());
break;
case HASH_KEYWORD_ACK: // <D ACK ON/OFF>
if (params >= 2 && p[1] == HASH_KEYWORD_LIMIT) {
DCCWaveform::progTrack.setAckLimit(p[2]);
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
} else
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
DCCWaveform::progTrack.setAckLimit(p[2]);
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
} else if (p[1] == HASH_KEYWORD_MIN) {
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck min=%dus\n"), p[2]);
} else if (p[1] == HASH_KEYWORD_MAX) {
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck max=%dus\n"), p[2]);
}
} else {
StringFormatter::send(stream, F("\nAck diag %S\n"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
@@ -618,16 +755,12 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
Diag::WITHROTTLE = onOff;
return true;
case HASH_KEYWORD_DCC:
DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW);
return true;
case HASH_KEYWORD_PROGBOOST:
DCC::setProgTrackBoost(true);
return true;
case HASH_KEYWORD_EEPROM:
if (params >= 1)
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
@@ -638,13 +771,13 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
}
// CALLBACKS must be static
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS])
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS])
{
if (stashBusy )
return false;
stashBusy = true;
stashStream = stream;
memcpy(stashP, p, MAX_PARAMS * sizeof(p[0]));
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
return true;
}
void DCCEXParser::callback_W(int result)
@@ -680,3 +813,10 @@ void DCCEXParser::callback_Rloco(int result)
StringFormatter::send(stashStream, F("<r %d>"), result);
stashBusy = false;
}
void DCCEXParser::callback_Wloco(int result)
{
if (result==1) result=stashP[0]; // pick up original requested id from command
StringFormatter::send(stashStream, F("<w %d>"), result);
stashBusy = false;
}

View File

@@ -19,6 +19,7 @@
#ifndef DCCEXParser_h
#define DCCEXParser_h
#include <Arduino.h>
#include "FSH.h"
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
@@ -28,10 +29,12 @@ struct DCCEXParser
DCCEXParser();
void loop(Stream & stream);
void parse(Print * stream, byte * command, bool blocking);
void parse(const FSH * cmd);
void flush();
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
static const int MAX_PARAMS=10; // Must not exceed this
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
private:
@@ -39,7 +42,8 @@ struct DCCEXParser
byte bufferLength=0;
bool inCommandPayload=false;
byte buffer[MAX_BUFFER+2];
int splitValues( int result[MAX_PARAMS], const byte * command);
int splitValues( int result[MAX_COMMAND_PARAMS], const byte * command);
int splitHexValues( int result[MAX_COMMAND_PARAMS], const byte * command);
bool parseT(Print * stream, int params, int p[]);
bool parseZ(Print * stream, int params, int p[]);
@@ -51,15 +55,17 @@ struct DCCEXParser
static bool stashBusy;
static Print * stashStream;
static int stashP[MAX_PARAMS];
bool stashCallback(Print * stream, int p[MAX_PARAMS]);
static int stashP[MAX_COMMAND_PARAMS];
bool stashCallback(Print * stream, int p[MAX_COMMAND_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_Wloco(int result);
static void callback_Vbit(int result);
static void callback_Vbyte(int result);
static FILTER_CALLBACK filterCallback;
static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int cab, byte value, byte fstart, byte fstop);

145
DCCTimer.cpp Normal file
View File

@@ -0,0 +1,145 @@
/*
* © 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) {
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO what are the relevant pins?
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
}
#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);
}
#endif

16
DCCTimer.h Normal file
View File

@@ -0,0 +1,16 @@
#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);
private:
};
#endif

View File

@@ -17,13 +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/>.
*/
#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;
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
@@ -31,33 +31,19 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
bool DCCWaveform::progTrackSyncMain=false;
bool DCCWaveform::progTrackBoosted=false;
VirtualTimer * DCCWaveform::interruptTimer=NULL;
int DCCWaveform::progTripValue=0;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
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"));
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
MotorDriver::commonFaultPin = (mainDriver->getFaultPin() == progDriver->getFaultPin());
if (MotorDriver::usePWM) DIAG(F("\nWaveform using PWM pins for accuracy."));
else DIAG(F("\nWaveform accuracy limited by signal pin configuration."));
DCCTimer::begin(DCCWaveform::interruptHandler);
}
void DCCWaveform::loop() {
@@ -65,19 +51,26 @@ void DCCWaveform::loop() {
progTrack.checkPowerOverload();
}
// 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();
}
@@ -92,13 +85,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;
@@ -112,10 +104,6 @@ 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);
@@ -123,9 +111,6 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
void DCCWaveform::checkPowerOverload() {
static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= motorDriver->getRawCurrentTripValue();
@@ -138,8 +123,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("\n*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***\n"), isMainTrack ? F("MAIN") : F("PROG"));
} else {
DIAG(F("\n*** %S FAULT PIN ACTIVE - OVERLOAD ***\n"), 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++;
@@ -149,9 +152,9 @@ void DCCWaveform::checkPowerOverload() {
setPowerMode(POWERMODE::OVERLOAD);
unsigned int mA=motorDriver->raw2mA(lastCurrent);
unsigned int maxmA=motorDriver->raw2mA(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);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
@@ -162,73 +165,45 @@ void DCCWaveform::checkPowerOverload() {
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("\n*** %S TRACK POWER RESET delay=%d ***\n"), 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--;
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
@@ -248,7 +223,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;
@@ -273,7 +251,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
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];
}
@@ -284,20 +262,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;
int baseline = motorDriver->getCurrentRaw();
int baseline=motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA threshold=%d/%dmA"),
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
baseline,motorDriver->raw2mA(baseline),
ackThreshold,motorDriver->raw2mA(ackThreshold));
ackThreshold,motorDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
}
void DCCWaveform::setAckPending() {
@@ -312,7 +287,7 @@ void DCCWaveform::setAckPending() {
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,
if (Diag::ACK) DIAG(F("\n%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.
@@ -320,18 +295,17 @@ byte DCCWaveform::getAck() {
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();
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 (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
@@ -342,7 +316,7 @@ void DCCWaveform::checkAck() {
// detected trailing edge of pulse
ackPulseDuration=micros()-ackPulseStart;
if (ackPulseDuration>=MIN_ACK_PULSE_DURATION && ackPulseDuration<=MAX_ACK_PULSE_DURATION) {
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;

View File

@@ -20,29 +20,27 @@
#ifndef DCCWaveform_h
#define DCCWaveform_h
#include "MotorDriver.h"
#include "ArduinoTimers.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;
// Ack time thresholds. Unit: microseconds
const int MIN_ACK_PULSE_DURATION = 2000;
const int MAX_ACK_PULSE_DURATION = 8500;
// Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22;
const byte MAX_PACKET_SIZE = 5; // NMRA standard exrtended packets
// 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};
@@ -50,8 +48,7 @@ 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 begin(MotorDriver * mainDriver, MotorDriver * progDriver);
static void loop();
static DCCWaveform mainTrack;
static DCCWaveform progTrack;
@@ -60,7 +57,28 @@ class DCCWaveform {
void setPowerMode(POWERMODE);
POWERMODE getPowerMode();
void checkPowerOverload();
int getLastCurrent();
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;
@@ -79,14 +97,24 @@ class DCCWaveform {
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;
@@ -96,16 +124,16 @@ class DCCWaveform {
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
WAVE_STATE state; // wave generator state machine
byte pendingPacket[MAX_PACKET_SIZE];
byte pendingLength;
byte pendingRepeats;
int lastCurrent;
int lastCurrent;
static int progTripValue;
int maxmA;
int tripmA;
// current sampling
POWERMODE powerMode;
@@ -128,6 +156,9 @@ class DCCWaveform {
unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros
unsigned int minAckPulseDuration = 2000; // micros
unsigned int maxAckPulseDuration = 8500; // micros
};
#endif

1
DIAG.h
View File

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

View File

@@ -17,10 +17,18 @@
* 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;
/**
@@ -42,16 +50,25 @@ void EthernetInterface::setup()
*/
EthernetInterface::EthernetInterface()
{
byte mac[]=MAC_ADDRESS;
byte mac[6];
DCCTimer::getSimulatedMacAddress(mac);
DIAG(F("\n+++++ Ethernet Setup. Simulatd mac="));
for (byte i=0;i<sizeof(mac); i++) {
DIAG(F("%x:"),mac[i]);
}
DIAG(F("\n"));
DIAG(F("\n+++++ Ethernet Setup "));
connected=false;
connected=false;
#ifdef IP_ADDRESS
Ethernet.begin(mac, IP_ADDRESS);
#else
if (Ethernet.begin(mac) == 0)
{
DIAG(F("begin FAILED\n"));
return;
}
#endif
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("shield not found\n"));
@@ -66,11 +83,11 @@ EthernetInterface::EthernetInterface()
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
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"), LISTEN_PORT);
LCD(5,F("Port:%d"), IP_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
@@ -166,8 +183,6 @@ void EthernetInterface::loop()
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d\n"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}
}
#endif

View File

@@ -22,33 +22,23 @@
#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 "MemStream.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
#include <Ethernet.h>
#include "RingStream.h"
/* some generated mac addresses as EthernetShields don't have one by default in HW.
* Sometimes they come on a sticker on the EthernetShield then use this address otherwise
* just choose one from below or generate one yourself. Only condition is that there is no
* other device on your network with the same Mac address.
*
* 52:b8:8a:8e:ce:21
* e3:e9:73:e1:db:0d
* 54:2b:13:52:ac:0c
* c2:d8:d4:7d:7c:cb
* 86:cf:fa:9f:07:79
*/
/**
* @brief Network Configuration
*
*/
#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
// this one is not used elsewhere and corresponds to your network layout
#define LISTEN_PORT 2560 // default listen port for the server
#define MAX_ETH_BUFFER 512
#define OUTBOUND_RING_SIZE 2048

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 "edeaad4"

View File

@@ -25,7 +25,8 @@
// which libraray is involved.
////////////////////////////////////////////////////////////////////////////////////
#include "config.h"
#ifndef LCD_Implementation_h
#define LCD_Implementation_h
#include <Wire.h>
#include "LCDDisplay.h"
@@ -50,6 +51,5 @@ LCDDisplay * LCDDisplay::lcdDisplay=0;
#include "LCD_NONE.h"
#define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */
#endif
#endif // LCD_Implementation_h

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,78 +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>
#if defined(ARDUINO_ARCH_MEGAAVR)
#include <Arduino.h>
#else
#include <Stream.h>
#endif
#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,50 +18,73 @@
*/
#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
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;
getFastPin(F("POWER"),powerPin,fastPowerPin);
pinMode(powerPin, OUTPUT);
signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
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;
senseFactor=sense_factor;
pinMode(currentPin, INPUT);
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);
pinMode(powerPin, OUTPUT);
pinMode(brakePin < 0 ? -brakePin : brakePin, OUTPUT);
setBrake(false);
pinMode(signalPin, OUTPUT);
if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT);
pinMode(currentPin, INPUT);
if (faultPin != UNUSED_PIN) pinMode(faultPin, INPUT);
}
bool MotorDriver::isPWMCapable() {
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
}
void MotorDriver::setPower(bool on) {
if (brakePin == -4 && on) {
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);
}
WritePin(powerPin, on ? HIGH : LOW);
else setLOW(fastPowerPin);
}
// setBrake applies brake if on == true. So to get
@@ -73,30 +96,40 @@ void MotorDriver::setPower(bool on) {
// compensate for that.
//
void MotorDriver::setBrake(bool on) {
bool state = on;
byte pin = brakePin;
if (brakePin < 0) {
pin=-pin;
state=!state;
}
WritePin(pin, state ? HIGH : LOW);
//DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin));
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);
}
}
}
/*
* 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, ceat a little and return -1 in that case.
*/
int MotorDriver::getCurrentRaw() {
if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH)
return (int)(32000/senseFactor);
int current = analogRead(currentPin);
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::raw2mA( int raw) {
@@ -105,3 +138,15 @@ unsigned int MotorDriver::raw2mA( int raw) {
int MotorDriver::mA2raw( unsigned int mA) {
return (int)(mA / senseFactor);
}
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin);
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\n"),port, result.inout,input,result.maskHIGH);
}

View File

@@ -18,12 +18,20 @@
*/
#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
struct FASTPIN {
volatile uint8_t *inout;
uint8_t maskHIGH;
uint8_t maskLOW;
};
class MotorDriver {
public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
@@ -34,12 +42,24 @@ class MotorDriver {
virtual unsigned int raw2mA( int raw);
virtual int mA2raw( unsigned int mA);
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
return rawCurrentTripValue;
}
bool isPWMCapable();
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:
byte powerPin, signalPin, signalPin2, currentPin, faultPin;
int8_t brakePin; // negative means pin is inverted
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;
unsigned int tripMilliamps;
int rawCurrentTripValue;

View File

@@ -1,8 +1,6 @@
#ifndef MotorDrivers_h
#define MotorDrivers_h
#if defined(ARDUINO_ARCH_MEGAAVR)
#include <Arduino.h>
#endif
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
// It will be overwritten if the library is updated.
@@ -31,8 +29,8 @@
// Pololu Motor Shield
#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, UNUSED_PIN)
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
@@ -40,8 +38,8 @@
// 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, UNUSED_PIN)
// 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

View File

@@ -83,7 +83,13 @@ 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>"), 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

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

@@ -1,52 +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 the following methods:
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.
* our automated installer
* download a zip file from this repository (green Code button above) and unzip it
* use git clone on this repository
To utilize this sketch, you can use the following:
With the manual methods you unzip or git clone to the Arduino IDE
projects folder and then open the file "CommandStation-EX.ino" in the
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
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. If you do not run the installer, you have to copy
config.example.h to config.h. If you do not have the standard config
you edit config.h according to the help texts in config.h.
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.
The latest production release of the Master branch is 3.0.1:
## What's new in CommandStation-EX?
* 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).
* 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)
For more information on the overall DCC++ EX system, please follow the links in the PDF file.
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).
Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository
# More information
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
The Master branch contains all of the Command Station functionality showed in the DCC-EX YouTube channel.
# How to find more information
--------------------------
[DCC++ EX WEB Page](https://dcc-ex.github.io "DCC++ EX WEB Page")
[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

@@ -22,13 +22,9 @@
#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"
@@ -40,14 +36,14 @@ bool Diag::WITHROTTLE=false;
bool Diag::ETHERNET=false;
void StringFormatter::diag( const __FlashStringHelper* input...) {
void StringFormatter::diag( const FSH* input...) {
if (!diagSerial) return;
va_list args;
va_start(args, input);
send2(diagSerial,input,args);
}
void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
void StringFormatter::lcd(byte row, const FSH* input...) {
va_list args;
// Issue the LCD as a diag first
@@ -62,25 +58,25 @@ void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
send2(LCDDisplay::lcdDisplay,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::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; }
@@ -91,14 +87,14 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va
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 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break;
case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); 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;
@@ -138,12 +134,12 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
}
}
void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) {
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
if (!stream) return;
char* flash=(char*)input;
for(int i=0; ; ++i) {
char c=pgm_read_byte_near(flash+i);
char c=GETFLASH(flash+i);
printEscape(stream,c);
if (c=='\0') return;
}

View File

@@ -19,12 +19,10 @@
#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"
@@ -41,22 +39,22 @@ class Diag {
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 __FlashStringHelper* 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 lcd(byte row, 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);
};

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

@@ -21,10 +21,17 @@
#include "Turnouts.h"
#include "EEStore.h"
#include "PWMServoDriver.h"
#include "StringFormatter.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
// 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>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
} // Turnout::printAll
bool Turnout::activate(int n,bool state){
#ifdef EESTOREDEBUG
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);

View File

@@ -49,6 +49,7 @@ 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

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

@@ -133,6 +133,8 @@ void WiThrottle::parse(RingStream * 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
}
@@ -262,10 +264,10 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
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;

View File

@@ -1,3 +1,23 @@
/*
* © 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"
@@ -228,3 +248,5 @@ void WifiInboundHandler::purgeCurrentCIPSEND() {
pendingCipsend=false;
clientPendingCIPSEND=-1;
}
#endif

View File

@@ -15,12 +15,12 @@ class WifiInboundHandler {
static WifiInboundHandler * singleton;
enum INBOUND_STATE {
enum INBOUND_STATE : byte {
INBOUND_BUSY, // keep calling in loop()
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
};
enum LOOP_STATE {
enum LOOP_STATE : byte {
ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc...
SKIPTOEND, // skip to newline

View File

@@ -17,29 +17,32 @@
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" /* config.h and defines.h included here */
#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 "WifiInboundHandler.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 SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
const char PROGMEM IPD_SEARCH[] = "+IPD";
const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false;
Stream * WifiInterface::wifiStream;
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
#define WIFI_CONNECT_TIMEOUT 14000
#endif
////////////////////////////////////////////////////////////////////////////////
//
// Figure out number of serial ports depending on hardware
//
#if defined(ARDUINO_AVR_UNO)
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define NUM_SERIAL 0
#endif
@@ -52,10 +55,11 @@ Stream * WifiInterface::wifiStream;
#endif
bool WifiInterface::setup(long serial_link_speed,
const __FlashStringHelper *wifiESSID,
const __FlashStringHelper *wifiPassword,
const __FlashStringHelper *hostname,
const int port) {
const FSH *wifiESSID,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel) {
wifiSerialState wifiUp = WIFI_NOAT;
@@ -70,7 +74,7 @@ bool WifiInterface::setup(long serial_link_speed,
#if NUM_SERIAL > 0
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
@@ -78,7 +82,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
@@ -86,7 +90,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
@@ -103,8 +107,8 @@ bool WifiInterface::setup(long serial_link_speed,
return connected;
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port) {
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++;
@@ -113,7 +117,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
wifiState = setup2( SSid, password, hostname, port);
wifiState = setup2( SSid, password, hostname, port, channel);
if (wifiState == WIFI_NOAT) {
DIAG(F("\n++ Wifi Setup NO AT ++\n"));
@@ -122,7 +126,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
if (wifiState == WIFI_CONNECTED) {
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, OK_SEARCH, true);
checkForOK(200, true);
}
@@ -130,139 +134,182 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
return wifiState;
}
wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port) {
#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)) {
if (checkForOK(200,F("+IPD"), true)) {
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
// loopstate=4; // carry on from correct place... or not as the case may be
return WIFI_CONNECTED;
}
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
if(!checkForOK(200, OK_SEARCH, true))
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
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("\nDONT_TOUCH_WIFI_CONF was set: Using existing config\n"));
#else
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
checkForOK(1000, true); // Not always OK, sometimes "no change"
// If the source code looks unconfigured, check if the
// ESP8266 is preconfigured. We check the first 13 chars
// of the password.
if (strncmp_P("Your network ",(const char*)password,13) == 0) {
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
// 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, true)) {
oldCmd=true;
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
}
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
ipOK = true;
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 {
if (!ipOK) {
// 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)) {
oldCmd=true;
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(16000, OK_SEARCH, true);
}
DIAG(F("\n**\n"));
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, OK_SEARCH, true); // dont care if not supported
checkForOK(2000, true); // dont care if not supported
if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(20000, OK_SEARCH, true);
}
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
}
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
if (ipOK) {
// But we really only have the ESSID and password correct
// Let's check for IP
// Let's check for IP (via DHCP)
ipOK = false;
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
}
}
if (!ipOK) {
// If we have not managed to get this going in station mode, go for AP mode
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint.
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
// 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=2\r\n"));
} 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"));
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, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
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]);
}
} else {
memset(macAddress,'f',sizeof(macAddress));
}
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
if (oldCmd) {
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
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);
}
} 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("\nWarning: Setting AP SSID and password failed\n")); // but issue warning
int i=0;
do StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
while (i++<2 && !checkForOK(16000, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok
} else {
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
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(10000, OK_SEARCH, true); // ignore result in case it already was off
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 WIFI_DISCONNECTED;
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 WIFI_DISCONNECTED;
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 WIFI_DISCONNECTED;
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\n"),port);
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
@@ -278,15 +325,19 @@ void WifiInterface::ATCommand(const byte * command) {
}
else {
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, OK_SEARCH, true);
checkForOK(10000, true);
}
}
bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
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 FSH * waitfor, bool echo, bool escapeEcho) {
unsigned long startTime = millis();
char const *locator = waitfor;
char *locator = (char *)waitfor;
DIAG(F("\nWifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
@@ -295,10 +346,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else DIAG(F("%c"), 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)) {
if (!GETFLASH(locator)) {
DIAG(F("\nFound in %dms"), millis() - startTime);
return true;
}
@@ -315,3 +366,5 @@ void WifiInterface::loop() {
WifiInboundHandler::loop();
}
}
#endif

View File

@@ -19,6 +19,7 @@
*/
#ifndef WifiInterface_h
#define WifiInterface_h
#include "FSH.h"
#include "DCCEXParser.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
@@ -30,26 +31,23 @@ class WifiInterface
public:
static bool setup(long serial_link_speed,
const __FlashStringHelper *wifiESSID,
const __FlashStringHelper *wifiPassword,
const __FlashStringHelper *hostname,
const int port = 2560);
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 __FlashStringHelper *SSSid, const __FlashStringHelper *password,
const __FlashStringHelper *hostname, int port);
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 __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 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;
static bool closeAfter;
static byte loopstate;
static int datalength;
static int connectionId;
static unsigned long loopTimeoutStart;
};
#endif

View File

@@ -1,10 +1,9 @@
/**********************************************************************
Config.h
COPYRIGHT (c) 2013-2016 Gregg E. Berman
config.h
COPYRIGHT (c) 2020 Fred Decker
The configuration file for DCC++ EX Command Station
The configuration file for DCC-EX Command Station
**********************************************************************/
@@ -44,9 +43,52 @@ The configuration file for DCC++ EX Command Station
//
// 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 conain ``"'' (double quote, ASCII 0x22).
#define WIFI_SSID "Your network name"
//
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "Your network passwd"
//
// WIFI_HOSTNAME: You probably don't need to change this
#define WIFI_HOSTNAME "dccex"
//
// WIFI_CHANNEL:
#define WIFI_CHANNEL 1
//
/////////////////////////////////////////////////////////////////////////////////////
//
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
// to set this if you have an extremely slow Wifi router.
//
//#define WIFI_CONNECT_TIMEOUT 14000
/////////////////////////////////////////////////////////////////////////////////////
//
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
// is not for Wifi. You will then need the Arduino Ethernet library as well
//
//#define ENABLE_ETHERNET true
/////////////////////////////////////////////////////////////////////////////////////
//
@@ -54,15 +96,6 @@ The configuration file for DCC++ EX Command Station
//
//#define IP_ADDRESS { 192, 168, 1, 200 }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
//
// Uncomment to use with Ethernet Shields
//
// NOTE: This is not used with ESP8266 WiFi modules.
//
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
/////////////////////////////////////////////////////////////////////////////////////
//

View File

@@ -21,10 +21,13 @@
////////////////////////////////////////////////////////////////////////////////
//
// 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))
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#else
#define WIFI_ON false
#endif

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();
}

View File

@@ -10,5 +10,7 @@ ECHO ++++++++++++++++++++++++++++++++++ >>%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 %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
notepad %TMP%\OBJDUMP_%a%.txt
EXIT

View File

@@ -36,7 +36,10 @@ lib_deps =
DIO2
arduino-libraries/Ethernet
SPI
mathertel/LiquidCrystal_PCF8574
marcoschwartz/LiquidCrystal_I2C
Adafruit/Adafruit_BusIO
Adafruit/Adafruit_SSD1306
Adafruit/Adafruit-GFX-Library
monitor_speed = 115200
monitor_flags = --echo
@@ -49,7 +52,7 @@ lib_deps =
DIO2
arduino-libraries/Ethernet
SPI
mathertel/LiquidCrystal_PCF8574
marcoschwartz/LiquidCrystal_I2C
monitor_speed = 115200
monitor_flags = --echo
@@ -62,9 +65,10 @@ lib_deps =
DIO2
arduino-libraries/Ethernet
SPI
mathertel/LiquidCrystal_PCF8574
marcoschwartz/LiquidCrystal_I2C
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
@@ -75,6 +79,6 @@ lib_deps =
DIO2
arduino-libraries/Ethernet
SPI
mathertel/LiquidCrystal_PCF8574
marcoschwartz/LiquidCrystal_I2C
monitor_speed = 115200
monitor_flags = --echo

View File

@@ -3,8 +3,12 @@
#include "StringFormatter.h"
// const char VERSION[] PROGMEM ="0.2.0";
#define VERSION "3.0.0"
#define VERSION "3.0.4"
// 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