mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 19:03:44 +02:00
Compare commits
423 Commits
v3.2.0rc13
...
v4.2.4-Dev
Author | SHA1 | Date | |
---|---|---|---|
|
3da44be86f | ||
|
0cf58a996d | ||
|
88f16140f8 | ||
|
431dc2bcc6 | ||
|
27a458a850 | ||
|
02ed7828c1 | ||
|
ad6c1384c9 | ||
|
7c4640ad91 | ||
|
c6866aba86 | ||
|
a91152be95 | ||
|
7d1d6bf1e1 | ||
|
cec6d6dbe7 | ||
|
08427abe70 | ||
|
9dabf14aa3 | ||
|
2f9c8faa77 | ||
|
86215b28ae | ||
|
827e4fef86 | ||
|
43b7b5d797 | ||
|
3c706926c5 | ||
|
a9ce9101e6 | ||
|
58bac3dc51 | ||
|
4fb53572f5 | ||
|
11c5dc5f06 | ||
|
5eb04f77a8 | ||
|
9d6931c438 | ||
|
c2c51e32c3 | ||
|
86538a4902 | ||
|
04188926b4 | ||
|
77cca8f6ee | ||
|
e36e867ec2 | ||
|
d3dbeaa666 | ||
|
cadb82ab6b | ||
|
1fc5f436fd | ||
|
9633e77c0a | ||
|
5d415366d8 | ||
|
b9c1e779ae | ||
|
162e1f9d3e | ||
|
ff28dbd561 | ||
|
60d91eef9d | ||
|
f0c2672835 | ||
|
8cff51b913 | ||
|
9f27759a9c | ||
|
0a6d023373 | ||
|
d333a265f4 | ||
|
c49d11573c | ||
|
82bcae627b | ||
|
905b2c0148 | ||
|
4a84ea1b43 | ||
|
059fd1b193 | ||
|
da8b189b43 | ||
|
915de96e80 | ||
|
a985356f0c | ||
|
c1993fba87 | ||
|
e1fd6e9414 | ||
|
ecda69ba32 | ||
|
76c5608181 | ||
|
84e44df47c | ||
|
db032d567d | ||
|
c9612984e8 | ||
|
f56e3bec9e | ||
|
966b9594ef | ||
|
186fd8adee | ||
|
5182bb171d | ||
|
caca265529 | ||
|
c115c441e4 | ||
|
6540ffee75 | ||
|
76137ff24c | ||
|
64a6412ce2 | ||
|
803db81c8f | ||
|
af75297a23 | ||
|
e7d8d320bd | ||
|
17bdd2d724 | ||
|
42c35a11e1 | ||
|
012d427c6e | ||
|
7551c2d2f6 | ||
|
5f1a263158 | ||
|
42ac954475 | ||
|
01f129e25f | ||
|
608c7547fb | ||
|
5430711672 | ||
|
704fabd1a4 | ||
|
6286f5fedf | ||
|
67b14ec57d | ||
|
f57fd245a1 | ||
|
03c6f3ab24 | ||
|
0301b78712 | ||
|
96f042897a | ||
|
7bad16dc59 | ||
|
87fd1b887e | ||
|
ad4a9d88b4 | ||
|
fad504bc7f | ||
|
172dbfd444 | ||
|
ee279c9a03 | ||
|
33327d14c9 | ||
|
6167a949b6 | ||
|
df767aaa36 | ||
|
37ea688eab | ||
|
89905f8ed7 | ||
|
c7cf8246a7 | ||
|
863f2f4a85 | ||
|
6b2cd226e2 | ||
|
07600274f1 | ||
|
167c5db1fe | ||
|
2c24bbee17 | ||
|
b09dba1213 | ||
|
33c9155f6e | ||
|
e5ce76e703 | ||
|
7a123e7e17 | ||
|
ed1b451b85 | ||
|
90d6ff43c5 | ||
|
9d4e7903d5 | ||
|
eeb70293e0 | ||
|
024c8fc199 | ||
|
ddc0c5ac3c | ||
|
8916d1415f | ||
|
2ad0d7ab76 | ||
|
3f0b3ccaf7 | ||
|
bdd87e7399 | ||
|
3aef54c0fe | ||
|
26fc11d1a6 | ||
|
d29219f858 | ||
|
06647ae7e4 | ||
|
ca84cd2ea6 | ||
|
fb513b64f9 | ||
|
4316413618 | ||
|
bfa81b801e | ||
|
ec12baa0ca | ||
|
7ce74cfdf8 | ||
|
67e8c04314 | ||
|
37f44709f9 | ||
|
83300387d2 | ||
|
237846f190 | ||
|
6c940615f6 | ||
|
342b9798f0 | ||
|
a5d47e0c2c | ||
|
feebe67ecb | ||
|
6e8929c89e | ||
|
0373f060fb | ||
|
ee639de5d6 | ||
|
b7cd4adb5f | ||
|
68f0c6681d | ||
|
3681f0e445 | ||
|
9768083bfe | ||
|
090acdae44 | ||
|
aa3c3c2ee4 | ||
|
385afdeb6c | ||
|
ff46e283ac | ||
|
108c5050ad | ||
|
12b5c2cdba | ||
|
76d6759d98 | ||
|
a7ea96b392 | ||
|
632e9335f3 | ||
|
ca3ed95624 | ||
|
06d1040da0 | ||
|
14dc569366 | ||
|
b7a82a0ad6 | ||
|
5cef1ac864 | ||
|
44b21fd987 | ||
|
6687c6f46d | ||
|
0406ca69cf | ||
|
695b776493 | ||
|
959225c252 | ||
|
a5cda1e350 | ||
|
7d6c2c8afb | ||
|
6104311ccb | ||
|
d0e71875e0 | ||
|
044b467085 | ||
|
3496b99197 | ||
|
6b7c2ccdf0 | ||
|
08eaa8ddb7 | ||
|
10a0cfcccb | ||
|
ef937dcacf | ||
|
e11d2d08d1 | ||
|
9a98d10a86 | ||
|
4833eaac65 | ||
|
744713769c | ||
|
7fba96417f | ||
|
5eb9678437 | ||
|
2385d0809c | ||
|
808aa9aba9 | ||
|
4297ed5572 | ||
|
40dfda47c7 | ||
|
16fafccf15 | ||
|
62e471606d | ||
|
0ab96d28c4 | ||
|
d37e303bdc | ||
|
b24f6b27c6 | ||
|
2b2012ef1d | ||
|
016bc37b53 | ||
|
712ed0674d | ||
|
1d36b03e7a | ||
|
c2d7e7169a | ||
|
661d042744 | ||
|
06e7ad5c53 | ||
|
1c78792dda | ||
|
cfcd61174d | ||
|
55561188e1 | ||
|
5f568c05b9 | ||
|
55196c2e7d | ||
|
c7b38170c1 | ||
|
d3b72dc4fc | ||
|
17fb921678 | ||
|
867e3b3930 | ||
|
79ef114c0d | ||
|
ebebd0dc11 | ||
|
506b65d0ea | ||
|
6d2a9e3b36 | ||
|
dd58e2c462 | ||
|
6135272c32 | ||
|
a1a2c9ce5b | ||
|
3c01bd9012 | ||
|
d2fa44eec7 | ||
|
632d777fe7 | ||
|
ff73a60874 | ||
|
f8a19de9fb | ||
|
f0e8419fea | ||
|
8f9da49cc8 | ||
|
d7a17b10b4 | ||
|
0268304d41 | ||
|
f66f5785f5 | ||
|
6d382fa0f4 | ||
|
af0d381e45 | ||
|
4a56998553 | ||
|
357560b226 | ||
|
589336eac3 | ||
|
cb365579d8 | ||
|
e13afd064d | ||
|
2d37947246 | ||
|
9367d708f7 | ||
|
a614a616fa | ||
|
2fd7a31ae4 | ||
|
977802f160 | ||
|
ac32cd5528 | ||
|
e721457844 | ||
|
7b40bd3290 | ||
|
3e8649f9a1 | ||
|
6994139e57 | ||
|
7a2fd90bfc | ||
|
b2df10a99a | ||
|
0dc91451d9 | ||
|
43bac3f78e | ||
|
cd0b8790b6 | ||
|
85c437b108 | ||
|
228553013b | ||
|
f7d64d5449 | ||
|
e7fb3648b0 | ||
|
acd6e7560f | ||
|
c58a126dfc | ||
|
5bdbe3895d | ||
|
bcd1335b08 | ||
|
afd94f0645 | ||
|
ad97592788 | ||
|
431208d191 | ||
|
724dea22d5 | ||
|
17eb7c560e | ||
|
21d1f482cf | ||
|
ff4dd2f1cd | ||
|
9cf70f5870 | ||
|
9273265036 | ||
|
14834d47a5 | ||
|
6515f1b512 | ||
|
64cae26333 | ||
|
920fcbc095 | ||
|
45f690eb4d | ||
|
766fdc43ac | ||
|
32fdb014ef | ||
|
8522e05b13 | ||
|
1b0d700009 | ||
|
28a4406044 | ||
|
20b12bcb7c | ||
|
e13175635c | ||
|
b41ca2f44a | ||
|
8fa1ba3039 | ||
|
bfb88bb30a | ||
|
5846e0fe23 | ||
|
a52551babe | ||
|
084ddf01e1 | ||
|
083c5b5cd3 | ||
|
63c9ca414d | ||
|
46e4dc2628 | ||
|
5ccef35074 | ||
|
7312951b2b | ||
|
5dfc014f49 | ||
|
4c8b7f8517 | ||
|
b847419a55 | ||
|
cd0dfc565c | ||
|
f878c1d01c | ||
|
aaa3e7a83c | ||
|
ece342f037 | ||
|
7c1c6dafa1 | ||
|
731d838e83 | ||
|
566f8ada23 | ||
|
a4c2ab7566 | ||
|
f9e36e6693 | ||
|
8085d03d65 | ||
|
7dd680ccd5 | ||
|
ef50665c16 | ||
|
0ab3fe07c5 | ||
|
a37ca6b6b6 | ||
|
5b12c2864d | ||
|
90ca262cd9 | ||
|
b29eedf772 | ||
|
3f283620d3 | ||
|
5adabcd1af | ||
|
2727332be3 | ||
|
49e0a0e5f5 | ||
|
71cd3fc292 | ||
|
331538549f | ||
|
78810d0e36 | ||
|
6826e01bd3 | ||
|
931e348c3d | ||
|
2cd0c169ce | ||
|
92c2768c0b | ||
|
0040f5caf6 | ||
|
349f5d5362 | ||
|
a10fca2b12 | ||
|
99c7ff6c3f | ||
|
2a87a6e997 | ||
|
dea55fec79 | ||
|
269e1b36ea | ||
|
865c8dd3bd | ||
|
be186b967b | ||
|
4f2dc0934f | ||
|
75b16c9047 | ||
|
cd952c6ede | ||
|
367e400d75 | ||
|
f5fb1540f0 | ||
|
7e3dcb8e8c | ||
|
4437f870b6 | ||
|
ac3ffd2a36 | ||
|
c15ea6e083 | ||
|
90092b4ad5 | ||
|
fa4f5f08ef | ||
|
f7e2c0ca99 | ||
|
2890a7928b | ||
|
03372f21e2 | ||
|
678cccbd95 | ||
|
524afc6caf | ||
|
6fc223d80b | ||
|
dd9152864b | ||
|
4f781074eb | ||
|
b29b8c999e | ||
|
99e636974a | ||
|
74bbe595fc | ||
|
1afb4753ec | ||
|
a7740d652d | ||
|
8db937e985 | ||
|
3038f29dac | ||
|
e57cd1e2aa | ||
|
c47dd101a5 | ||
|
cf905ce2f3 | ||
|
4bfcaa605a | ||
|
557b71c036 | ||
|
d0b01e3f03 | ||
|
bd9a04572d | ||
|
a26d988acc | ||
|
4300a3fdac | ||
|
22bf2b69f1 | ||
|
58ef7d25db | ||
|
1ec3b173fd | ||
|
07c0004996 | ||
|
99a62883c7 | ||
|
b7af6be1f2 | ||
|
eb6b14b848 | ||
|
fa261ec39f | ||
|
c711be7980 | ||
|
ed2aa4c1d8 | ||
|
82df3a21dc | ||
|
c00d3a825d | ||
|
35ee03537d | ||
|
f7e90e7b73 | ||
|
2632d44ec9 | ||
|
c8e5123c0a | ||
|
e7e26551ce | ||
|
50b854c526 | ||
|
55a789d65a | ||
|
a69b7ee113 | ||
|
114686d124 | ||
|
005ddef665 | ||
|
10209ed6f3 | ||
|
71117bc7a1 | ||
|
97065e892d | ||
|
4668e116f4 | ||
|
5cbf0c2cad | ||
|
c02e976c9f | ||
|
55c7a0a1e8 | ||
|
d7e46ac625 | ||
|
877db433a4 | ||
|
4901f12fcd | ||
|
836ccc143e | ||
|
77ee57eb83 | ||
|
837b0a9fb6 | ||
|
a109ba4e01 | ||
|
c87a80928b | ||
|
c5b283bd8c | ||
|
500fe2f717 | ||
|
278f7618f4 | ||
|
9d74b0f6a5 | ||
|
31059a615c | ||
|
7d7b337f82 | ||
|
05eb0d763a | ||
|
b6cfc39d23 | ||
|
8a0ddb0d74 | ||
|
faeb3194db | ||
|
26bd3ac342 | ||
|
d174c05127 | ||
|
75dffd9dfa | ||
|
0a10dbea0b | ||
|
43191e225e | ||
|
50bb1c950b | ||
|
0bb6b577fa | ||
|
cf0c818138 | ||
|
426b27f0dd | ||
|
19b4893b5f | ||
|
1c7a5320d8 | ||
|
afd4626988 | ||
|
a194b8965c | ||
|
696d12fc5e | ||
|
35cba02ee7 | ||
|
fa1d1619b6 | ||
|
b048879eaa | ||
|
34474cbf5c | ||
|
7397a4089b |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: DCC-EX
|
||||
patreon: dccex
|
14
.github/workflows/label-sponsors.yml
vendored
Normal file
14
.github/workflows/label-sponsors.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Label sponsors
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
build:
|
||||
name: is-sponsor-label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JasonEtco/is-sponsor-label-action@v1.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
3
.github/workflows/sha.yml
vendored
3
.github/workflows/sha.yml
vendored
@@ -24,10 +24,11 @@ jobs:
|
||||
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
|
||||
- uses: EndBug/add-and-commit@v8 # You can change this to use a specific version
|
||||
with:
|
||||
add: 'GITHUB_SHA.h'
|
||||
message: 'Committing a SHA'
|
||||
commit: --amend
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,7 +7,7 @@ Release/*
|
||||
.pio/
|
||||
.vscode/
|
||||
config.h
|
||||
.vscode/extensions.json
|
||||
.vscode/*
|
||||
mySetup.h
|
||||
mySetup.cpp
|
||||
myHal.cpp
|
||||
@@ -16,3 +16,5 @@ myFilter.cpp
|
||||
myAutomation.h
|
||||
myFilter.cpp
|
||||
myLayout.h
|
||||
.vscode/extensions.json
|
||||
.vscode/extensions.json
|
||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -3,5 +3,8 @@
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
|
@@ -28,100 +28,145 @@
|
||||
#include "defines.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// This section of CommandDistributor is simply not relevant on a uno or similar
|
||||
const byte NO_CLIENT=255;
|
||||
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
|
||||
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
|
||||
// use a buffer to allow broadcast
|
||||
StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer();
|
||||
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
|
||||
broadcastBufferWriter->flush();
|
||||
StringFormatter::send(broadcastBufferWriter, msg...);
|
||||
broadcastToClients(type);
|
||||
}
|
||||
#else
|
||||
// on a single USB connection config, write direct to Serial and ignore flush/shove
|
||||
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
|
||||
(void)type; //shut up compiler warning
|
||||
StringFormatter::send(&Serial, msg...);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CD_HANDLE_RING
|
||||
// wifi or ethernet ring streams with multiple client types
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
|
||||
// Parse is called by Withrottle or Ethernet interface to determine which
|
||||
// protocol the client is using and call the appropriate part of dcc++Ex
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
if (Diag::WIFI && Diag::CMD)
|
||||
DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer);
|
||||
ring=stream;
|
||||
ringClient=stream->peekTargetMark();
|
||||
if (buffer[0] == '<') {
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
|
||||
// First check if the client is not known
|
||||
// yet and in that case determinine type
|
||||
// NOTE: First character of transmission determines if this
|
||||
// client is using the DCC++ protocol where all commands start
|
||||
// with '<'
|
||||
if (clients[clientId] == NONE_TYPE) {
|
||||
if (buffer[0] == '<')
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
else
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
}
|
||||
|
||||
// mark buffer that is sent to parser
|
||||
ring->mark(clientId);
|
||||
|
||||
// When type is known, send the string
|
||||
// to the right parser
|
||||
if (clients[clientId] == COMMAND_TYPE) {
|
||||
DCCEXParser::parse(stream, buffer, ring);
|
||||
} else {
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
} else if (clients[clientId] == WITHROTTLE_TYPE) {
|
||||
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||
}
|
||||
ringClient=NO_CLIENT;
|
||||
|
||||
if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
|
||||
// The commit call will either write the length bytes
|
||||
// OR rollback to the mark because the reply is empty
|
||||
// or the command generated more output than fits in
|
||||
// the buffer
|
||||
if (!ring->commit()) {
|
||||
DIAG(F("OUTBOUND FULL processing cmd:%s"),buffer);
|
||||
}
|
||||
} else {
|
||||
DIAG(F("CD parse: was alredy committed")); //XXX Could have been committed by broadcastClient?!
|
||||
}
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
#endif
|
||||
|
||||
// This will not be called on a uno
|
||||
void CommandDistributor::broadcastToClients(clientType type) {
|
||||
|
||||
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||
broadcastBufferWriter->write((byte)'\0');
|
||||
byte rememberClient;
|
||||
(void)rememberClient; // shut up compiler warning
|
||||
|
||||
/* Boadcast to Serials */
|
||||
SerialManager::broadcast(broadcastBufferWriter);
|
||||
// Broadcast to Serials
|
||||
if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString());
|
||||
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
#ifdef CD_HANDLE_RING
|
||||
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||
// before merging broadcasts in the ring, then reinstate it in case
|
||||
// the process continues to output to its client.
|
||||
if (ringClient!=NO_CLIENT) ring->commit();
|
||||
|
||||
/* loop through ring clients */
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==NONE_TYPE) continue;
|
||||
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
|
||||
ring->mark(clientId);
|
||||
broadcastBufferWriter->printBuffer(ring);
|
||||
ring->commit();
|
||||
if (ring) {
|
||||
if ((rememberClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
|
||||
//DIAG(F("CD precommit client %d"), rememberClient);
|
||||
ring->commit();
|
||||
}
|
||||
// loop through ring clients
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==type) {
|
||||
//DIAG(F("CD mark client %d"), clientId);
|
||||
ring->mark(clientId);
|
||||
ring->print(broadcastBufferWriter->getString());
|
||||
//DIAG(F("CD commit client %d"), clientId);
|
||||
ring->commit();
|
||||
}
|
||||
}
|
||||
// at this point ring is committed (NO_CLIENT) either from
|
||||
// 4 or 13 lines above.
|
||||
if (rememberClient != RingStream::NO_CLIENT) {
|
||||
//DIAG(F("CD postmark client %d"), rememberClient);
|
||||
ring->mark(rememberClient);
|
||||
}
|
||||
}
|
||||
if (ringClient!=NO_CLIENT) ring->mark(ringClient);
|
||||
|
||||
#endif
|
||||
broadcastBufferWriter->flush();
|
||||
}
|
||||
#else
|
||||
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
|
||||
// Redirect ring output ditrect to Serial
|
||||
#define broadcastBufferWriter &Serial
|
||||
// and ignore the internal broadcast call.
|
||||
void CommandDistributor::broadcast(bool includeWithrottleClients) {
|
||||
(void)includeWithrottleClients;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Public broadcast functions below
|
||||
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
|
||||
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
|
||||
broadcast(false);
|
||||
broadcastReply(COMMAND_TYPE, F("<%c %d>\n"), on?'Q':'q', id);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// be safe for both types.
|
||||
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||
broadcastReply(COMMAND_TYPE, F("<H %d %d>\n"),id, !isClosed);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||
#endif
|
||||
broadcast(true);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
broadcast(false);
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||
#ifdef CD_HANDLE_RING
|
||||
WiThrottle::markForBroadcast(sp->loco);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool join=DCCWaveform::progTrackSyncMain;
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
bool join=TrackManager::isJoined();
|
||||
const FSH * reason=F("");
|
||||
char state='1';
|
||||
if (main && prog && join) reason=F(" JOIN");
|
||||
@@ -129,9 +174,16 @@ void CommandDistributor::broadcastPower() {
|
||||
else if (main) reason=F(" MAIN");
|
||||
else if (prog) reason=F(" PROG");
|
||||
else state='0';
|
||||
|
||||
StringFormatter::send(broadcastBufferWriter,
|
||||
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0');
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||
broadcast(true);
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0');
|
||||
#endif
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastText(const FSH * msg) {
|
||||
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||
#endif
|
||||
}
|
||||
|
@@ -23,25 +23,32 @@
|
||||
#define CommandDistributor_h
|
||||
#include "DCCEXParser.h"
|
||||
#include "RingStream.h"
|
||||
#include "StringBuffer.h"
|
||||
#include "defines.h"
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// Command Distributor must handle a RingStream of clients
|
||||
#define CD_HANDLE_RING
|
||||
#endif
|
||||
|
||||
class CommandDistributor {
|
||||
|
||||
private:
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
static void broadcastToClients(clientType type);
|
||||
static StringBuffer * broadcastBufferWriter;
|
||||
#ifdef CD_HANDLE_RING
|
||||
static RingStream * ring;
|
||||
static clientType clients[8];
|
||||
#endif
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastPower();
|
||||
static void broadcastText(const FSH * msg);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
private:
|
||||
static void broadcast(bool includeWithrottleClients);
|
||||
static RingStream * ring;
|
||||
static RingStream * broadcastBufferWriter;
|
||||
static byte ringClient;
|
||||
|
||||
// each bit in broadcastlist = 1<<clientid
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
static clientType clients[8];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -18,12 +18,14 @@
|
||||
|
||||
#if __has_include ( "config.h")
|
||||
#include "config.h"
|
||||
#ifndef MOTOR_SHIELD_TYPE
|
||||
#error Your config.h must include a MOTOR_SHIELD_TYPE definition. If you see this warning in spite not having a config.h, you have a buggy preprocessor and must copy config.example.h to config.h
|
||||
#endif
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
|
||||
@@ -47,6 +49,11 @@
|
||||
*/
|
||||
|
||||
#include "DCCEX.h"
|
||||
|
||||
#ifdef CPU_TYPE_ERROR
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
|
||||
#endif
|
||||
|
||||
#ifdef WIFI_WARNING
|
||||
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
|
||||
#endif
|
||||
@@ -76,20 +83,28 @@ void setup()
|
||||
// Responsibility 2: Start all the communications before the DCC engine
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
@@ -98,17 +113,16 @@ void setup()
|
||||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN_SERIAL.begin(115200);
|
||||
LCN::init(LCN_SERIAL);
|
||||
#endif
|
||||
|
||||
LCD(3,F("Ready"));
|
||||
LCD(3, F("Ready"));
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
|
||||
@@ -124,9 +138,15 @@ void loop()
|
||||
SerialManager::loop();
|
||||
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::loop();
|
||||
#endif //WIFI_ON
|
||||
#else //ARDUINO_ARCH_ESP32
|
||||
#ifndef WIFI_TASK_ON_CORE0
|
||||
WifiESP::loop();
|
||||
#endif
|
||||
#endif //ARDUINO_ARCH_ESP32
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::loop();
|
||||
#endif
|
||||
@@ -147,7 +167,7 @@ void loop()
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
int freeNow = minimumFreeMemory();
|
||||
int freeNow = DCCTimer::getMinimumFreeMemory();
|
||||
if (freeNow < ramLowWatermark) {
|
||||
ramLowWatermark = freeNow;
|
||||
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
|
||||
|
483
DCC.cpp
483
DCC.cpp
@@ -8,7 +8,7 @@
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "IODevice.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// messages to be sent to the waveform generator.
|
||||
@@ -56,36 +58,26 @@ const byte FN_GROUP_4=0x08;
|
||||
const byte FN_GROUP_5=0x10;
|
||||
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::joinRelay=UNUSED_PIN;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
|
||||
// Initialise HAL layer before reading EEprom.
|
||||
IODevice::begin();
|
||||
|
||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
EEStore::init();
|
||||
#endif
|
||||
|
||||
DCCWaveform::begin(mainDriver,progDriver);
|
||||
#ifndef ARDUINO_ARCH_ESP32 /* On ESP32 started in TrackManager::setTrackMode() */
|
||||
DCCWaveform::begin();
|
||||
#endif
|
||||
}
|
||||
|
||||
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||
setThrottle2(cab, speedCode);
|
||||
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
||||
// retain speed for loco reminders
|
||||
updateLocoReminder(cab, speedCode );
|
||||
}
|
||||
@@ -145,12 +137,25 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||
// or -1 on "loco not found"
|
||||
int8_t DCC::getThrottleSpeed(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
return speedTable[reg].speedCode & 0x7F;
|
||||
}
|
||||
|
||||
// returns speed code byte
|
||||
// or 128 (speed 0, dir forward) on "loco not found".
|
||||
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0)
|
||||
return 128;
|
||||
return speedTable[reg].speedCode;
|
||||
}
|
||||
|
||||
// returns direction on loco
|
||||
// or true/forward on "loco not found"
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return true;
|
||||
@@ -158,8 +163,9 @@ bool DCC::getThrottleDirection(int cab) {
|
||||
}
|
||||
|
||||
// Set function to value on or off
|
||||
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
if (cab<=0 ) return;
|
||||
bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
if (cab<=0 ) return false;
|
||||
if (functionNumber < 0) return false;
|
||||
|
||||
if (functionNumber>28) {
|
||||
//non reminding advanced binary bit set
|
||||
@@ -178,11 +184,11 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
b[nB++] = functionNumber >>7 ; // high order bits
|
||||
}
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return;
|
||||
if (reg<0) return false;
|
||||
|
||||
// Take care of functions:
|
||||
// Set state of function
|
||||
@@ -197,6 +203,7 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flip function state
|
||||
@@ -237,24 +244,39 @@ uint32_t DCC::getFunctionMap(int cab) {
|
||||
return (reg<0)?0:speedTable[reg].functions;
|
||||
}
|
||||
|
||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
// onoff is tristate:
|
||||
// 0 => send off packet
|
||||
// 1 => send on packet
|
||||
// >1 => send both on and off packets.
|
||||
|
||||
// An accessory has an address, 4 ports and 2 gates (coils) each. That's how
|
||||
// the initial decoders were orgnized and that influenced how the DCC
|
||||
// standard was made.
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate);
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate);
|
||||
#endif
|
||||
// use masks to detect wrong values and do nothing
|
||||
if(address != (address & 511))
|
||||
return;
|
||||
if(number != (number & 3))
|
||||
if(port != (port & 3))
|
||||
return;
|
||||
byte b[2];
|
||||
|
||||
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||
// first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
// second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil).
|
||||
b[0] = address % 64 + 128;
|
||||
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
|
||||
if (onoff != 0) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::activateEvent(address<<2|number,activate);
|
||||
RMFT2::activateEvent(address<<2|port,gate);
|
||||
#endif
|
||||
}
|
||||
if (onoff != 1) {
|
||||
b[1] &= ~0x08; // set C to 0
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -296,14 +318,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
}
|
||||
|
||||
void DCC::setProgTrackSyncMain(bool on) {
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
FSH* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
@@ -313,14 +327,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||
W0,WACK,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
W1,WACK,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
@@ -329,7 +343,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
ITC0, // if acked, callback(0)
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1,
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
@@ -337,7 +351,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK,
|
||||
ITC0,
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH READ_BIT_PROG[] = {
|
||||
@@ -346,7 +360,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK, // validate bit is zero
|
||||
ITC0, // if acked callback 0
|
||||
FAIL // bit not readable
|
||||
CALLFAIL // bit not readable
|
||||
};
|
||||
|
||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
@@ -354,7 +368,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||
// handle decoders that dont ack a write
|
||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
@@ -380,7 +394,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||
FAIL };
|
||||
CALLFAIL };
|
||||
|
||||
|
||||
const ackOp FLASH READ_CV_PROG[] = {
|
||||
@@ -403,7 +417,7 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||
FAIL }; // verification failed
|
||||
CALLFAIL }; // verification failed
|
||||
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
@@ -469,7 +483,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and callback
|
||||
FAIL
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
@@ -484,9 +498,9 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
V0,WACK,NAKFAIL,
|
||||
SETCV, (ackOp)1,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK, // some decoders don't ACK writes
|
||||
VB,WACK,ITCB,
|
||||
FAIL
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
@@ -502,47 +516,51 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
V1,WACK,NAKFAIL,
|
||||
// Store high byte of address in cv 17
|
||||
SETCV, (ackOp)17,
|
||||
SETBYTEH, // high byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,NAKFAIL,
|
||||
SETBYTEH, // high byte of word
|
||||
WB,WACK, // do write
|
||||
ITSKIP, // if ACK, jump to SKIPTARGET
|
||||
VB,WACK, // try verify instead
|
||||
ITSKIP, // if ACK, jump to SKIPTARGET
|
||||
CALLFAIL, // if still here, fail
|
||||
SKIPTARGET,
|
||||
// store
|
||||
SETCV, (ackOp)18,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,ITC1, // callback(1) means Ok
|
||||
FAIL
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
|
||||
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
|
||||
DCCACK::Setup(cv, 0,READ_CV_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(0,0, LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
@@ -551,9 +569,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
return;
|
||||
}
|
||||
if (id<=HIGHEST_SHORT_ADDR)
|
||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
else
|
||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
@@ -570,16 +588,16 @@ void DCC::forgetAllLocos() { // removes all speed reminders
|
||||
byte DCC::loopStatus=0;
|
||||
|
||||
void DCC::loop() {
|
||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||
ackManagerLoop(); // maintain prog track ack manager
|
||||
TrackManager::loop(); // power overload checks
|
||||
issueReminders();
|
||||
}
|
||||
|
||||
void DCC::issueReminders() {
|
||||
// if the main track transmitter still has a pending packet, skip this time around.
|
||||
if ( DCCWaveform::mainTrack.packetPending) return;
|
||||
if ( DCCWaveform::mainTrack.getPacketPending()) return;
|
||||
|
||||
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
|
||||
/*
|
||||
for (int reg=0;reg<MAX_LOCOS;reg++) {
|
||||
int slot=reg+nextLoco;
|
||||
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
|
||||
@@ -590,6 +608,17 @@ void DCC::issueReminders() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
for (int reg=nextLoco;reg<MAX_LOCOS+nextLoco;reg++) {
|
||||
int slot=reg%MAX_LOCOS;
|
||||
if (speedTable[slot].loco > 0) {
|
||||
// have found the next loco to remind
|
||||
// issueReminder will return true if this loco is completed (ie speed and functions)
|
||||
if (issueReminder(slot))
|
||||
nextLoco=(slot+1)%MAX_LOCOS;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DCC::issueReminder(int reg) {
|
||||
@@ -647,7 +676,7 @@ byte DCC::cv2(int cv) {
|
||||
return lowByte(cv);
|
||||
}
|
||||
|
||||
int DCC::lookupSpeedTable(int locoId) {
|
||||
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
// determine speed reg for this loco
|
||||
int firstEmpty = MAX_LOCOS;
|
||||
int reg;
|
||||
@@ -655,6 +684,9 @@ int DCC::lookupSpeedTable(int locoId) {
|
||||
if (speedTable[reg].loco == locoId) break;
|
||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||
}
|
||||
|
||||
// return -1 if not found and not auto creating
|
||||
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||
if (reg >= MAX_LOCOS) {
|
||||
DIAG(F("Too many locos"));
|
||||
@@ -695,319 +727,6 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||
int DCC::nextLoco = 0;
|
||||
|
||||
//ACK MANAGER
|
||||
ackOp const * DCC::ackManagerProg;
|
||||
ackOp const * DCC::ackManagerProgStart;
|
||||
byte DCC::ackManagerByte;
|
||||
byte DCC::ackManagerByteVerify;
|
||||
byte DCC::ackManagerStash;
|
||||
int DCC::ackManagerWord;
|
||||
byte DCC::ackManagerRetry;
|
||||
byte DCC::ackRetry = 2;
|
||||
int16_t DCC::ackRetrySum;
|
||||
int16_t DCC::ackRetryPSum;
|
||||
int DCC::ackManagerCv;
|
||||
byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
bool DCC::ackManagerRejoin;
|
||||
|
||||
CALLBACK_STATE DCC::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCC::ackManagerCallback;
|
||||
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
setProgTrackSyncMain(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
DCCWaveform::progTrack.autoPowerOff=false;
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
ackManagerSetup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCC::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||
}
|
||||
|
||||
void DCC::ackManagerLoop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
callbackState=READY;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=DCCWaveform::progTrack.getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
setProgTrackSyncMain(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
|
||||
|
128
DCC.h
128
DCC.h
@@ -36,83 +36,40 @@
|
||||
#error short addr greater than 127 does not make sense
|
||||
#endif
|
||||
#endif
|
||||
#include "DCCACK.h"
|
||||
const uint16_t LONG_ADDR_MARKER = 0x4000;
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
enum CALLBACK_STATE : byte {
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
// Allocations with memory implications..!
|
||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
const byte MAX_LOCOS = 20;
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
const byte MAX_LOCOS = 30;
|
||||
#else
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
const byte MAX_LOCOS = 50;
|
||||
#else
|
||||
const byte MAX_LOCOS = 30;
|
||||
#endif
|
||||
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static uint8_t getThrottleSpeed(int cab);
|
||||
static int8_t getThrottleSpeed(int cab);
|
||||
static uint8_t getThrottleSpeedByte(int cab);
|
||||
static bool getThrottleDirection(int cab);
|
||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||
static void setFunction(int cab, byte fByte, byte eByte);
|
||||
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||
static bool setFn(int cab, int16_t functionNumber, bool on);
|
||||
static void changeFn(int cab, int16_t functionNumber);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static uint32_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
||||
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
||||
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
static void readCV(int16_t cv, ACK_CALLBACK callback);
|
||||
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
||||
@@ -128,18 +85,11 @@ public:
|
||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print *stream);
|
||||
|
||||
static FSH *getMotorShieldName();
|
||||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
};
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
@@ -148,9 +98,11 @@ public:
|
||||
unsigned long functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
|
||||
private:
|
||||
static byte joinRelay;
|
||||
static byte loopStatus;
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
static void updateLocoReminder(int loco, byte speedCode);
|
||||
@@ -160,34 +112,9 @@ private:
|
||||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
static int lookupSpeedTable(int locoId);
|
||||
static void issueReminders();
|
||||
static void callback(int value);
|
||||
|
||||
// ACK MANAGER
|
||||
static ackOp const *ackManagerProg;
|
||||
static ackOp const *ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerLoop();
|
||||
static bool checkResets( uint8_t numResets);
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
@@ -202,31 +129,4 @@ private:
|
||||
static const byte BIT_OFF = 0x00;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
|
||||
#define ARDUINO_AVR_MEGA2560
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#elif defined(ARDUINO_TEENSY32)
|
||||
#define ARDUINO_TYPE "TEENSY32"
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
469
DCCACK.cpp
Normal file
469
DCCACK.cpp
Normal file
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
|
||||
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
|
||||
|
||||
MotorDriver * DCCACK::progDriver=NULL;
|
||||
ackOp const * DCCACK::ackManagerProg;
|
||||
ackOp const * DCCACK::ackManagerProgStart;
|
||||
byte DCCACK::ackManagerByte;
|
||||
byte DCCACK::ackManagerByteVerify;
|
||||
byte DCCACK::ackManagerStash;
|
||||
int DCCACK::ackManagerWord;
|
||||
byte DCCACK::ackManagerRetry;
|
||||
byte DCCACK::ackRetry = 2;
|
||||
int16_t DCCACK::ackRetrySum;
|
||||
int16_t DCCACK::ackRetryPSum;
|
||||
int DCCACK::ackManagerCv;
|
||||
byte DCCACK::ackManagerBitNum;
|
||||
bool DCCACK::ackReceived;
|
||||
bool DCCACK::ackManagerRejoin;
|
||||
volatile uint8_t DCCACK::numAckGaps=0;
|
||||
volatile uint8_t DCCACK::numAckSamples=0;
|
||||
uint8_t DCCACK::trailingEdgeCounter=0;
|
||||
|
||||
|
||||
unsigned int DCCACK::ackPulseDuration; // micros
|
||||
unsigned long DCCACK::ackPulseStart; // micros
|
||||
volatile bool DCCACK::ackDetected;
|
||||
unsigned long DCCACK::ackCheckStart; // millis
|
||||
volatile bool DCCACK::ackPending;
|
||||
bool DCCACK::autoPowerOff;
|
||||
int DCCACK::ackThreshold;
|
||||
int DCCACK::ackLimitmA = 50;
|
||||
int DCCACK::ackMaxCurrent;
|
||||
unsigned int DCCACK::ackCheckDuration; // millis
|
||||
|
||||
|
||||
CALLBACK_STATE DCCACK::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCCACK::ackManagerCallback;
|
||||
|
||||
void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerRejoin=TrackManager::isJoined();
|
||||
if (ackManagerRejoin) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
TrackManager::setJoin(false);
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
|
||||
progDriver=TrackManager::getProgDriver();
|
||||
if (progDriver==NULL) {
|
||||
TrackManager::setJoin(ackManagerRejoin);
|
||||
callback(-3); // we dont have a prog track!
|
||||
return;
|
||||
}
|
||||
if (!progDriver->canMeasureCurrent()) {
|
||||
TrackManager::setJoin(ackManagerRejoin);
|
||||
callback(-2); // our prog track cant measure current
|
||||
return;
|
||||
}
|
||||
|
||||
autoPowerOff=false;
|
||||
if (progDriver->getPower() == POWERMODE::OFF) {
|
||||
autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
|
||||
/* TODO !!! in MotorDriver surely!
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
**/
|
||||
}
|
||||
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
Setup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCCACK::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.getResets() < numResets;
|
||||
}
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
|
||||
void DCCACK::setAckBaseline() {
|
||||
int baseline=progDriver->getCurrentRaw();
|
||||
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,progDriver->raw2mA(baseline),
|
||||
ackThreshold,progDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
}
|
||||
|
||||
void DCCACK::setAckPending() {
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
|
||||
byte DCCACK::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::loop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (progDriver->getPower()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
setAckBaseline();
|
||||
callbackState=AFTER_READ;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CALLFAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCACK::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_READ:
|
||||
if (ackManagerRejoin && autoPowerOff) {
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
} else {
|
||||
callbackState=READY;
|
||||
}
|
||||
break;
|
||||
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
/* TODO
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
**/
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
TrackManager::setJoin(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
if (!ackPending) return;
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int current=progDriver->getCurrentRawInInterrupt();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
|
156
DCCACK.h
Normal file
156
DCCACK.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef DCCACK_h
|
||||
#define DCCACK_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
CALLFAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
enum CALLBACK_STATE : byte {
|
||||
|
||||
AFTER_READ, // Start callback sequence after something was read from the decoder
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
|
||||
class DCCACK {
|
||||
public:
|
||||
static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker
|
||||
static inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
static inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
static inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void loop();
|
||||
static bool isActive() { return ackManagerProg!=NULL;}
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
static const byte WRITE_BYTE = 0x7C;
|
||||
static const byte VERIFY_BYTE = 0x74;
|
||||
static const byte BIT_MANIPULATE = 0x78;
|
||||
static const byte WRITE_BIT = 0xF0;
|
||||
static const byte VERIFY_BIT = 0xE0;
|
||||
static const byte BIT_ON = 0x08;
|
||||
static const byte BIT_OFF = 0x00;
|
||||
|
||||
static void setAckBaseline();
|
||||
static void setAckPending();
|
||||
static void callback(int value);
|
||||
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// ACK management (Prog track only)
|
||||
static void checkAck();
|
||||
static bool checkResets(uint8_t numResets);
|
||||
|
||||
static volatile bool ackPending;
|
||||
static volatile bool ackDetected;
|
||||
static int ackThreshold;
|
||||
static int ackLimitmA;
|
||||
static int ackMaxCurrent;
|
||||
static unsigned long ackCheckStart; // millis
|
||||
static unsigned int ackCheckDuration; // millis
|
||||
|
||||
static unsigned int ackPulseDuration; // micros
|
||||
static unsigned long ackPulseStart; // micros
|
||||
|
||||
static unsigned int minAckPulseDuration ; // micros
|
||||
static unsigned int maxAckPulseDuration ; // micros
|
||||
static MotorDriver* progDriver;
|
||||
static volatile uint8_t numAckGaps;
|
||||
static volatile uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
static ackOp const * ackManagerProg;
|
||||
static ackOp const * ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerStash;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerBitNum;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static bool autoPowerOff;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
|
||||
|
||||
};
|
||||
#endif
|
9
DCCEX.h
9
DCCEX.h
@@ -32,18 +32,23 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "SerialManager.h"
|
||||
#include "version.h"
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#include "WifiInterface.h"
|
||||
#else
|
||||
#include "WifiESP32.h"
|
||||
#endif
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#endif
|
||||
#include "LCD_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "freeMemory.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
#include "Outputs.h"
|
||||
#include "EXRAIL.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL.h"
|
||||
|
||||
#endif
|
||||
|
313
DCCEXParser.cpp
313
DCCEXParser.cpp
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Herb Morton
|
||||
@@ -30,24 +31,17 @@
|
||||
#include "Turnouts.h"
|
||||
#include "Outputs.h"
|
||||
#include "Sensors.h"
|
||||
#include "freeMemory.h"
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "defines.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include <avr/wdt.h>
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Figure out if we have enough memory for advanced features
|
||||
//
|
||||
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
|
||||
// nope
|
||||
#else
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#endif
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
@@ -73,9 +67,12 @@ const int16_t HASH_KEYWORD_RETRY = 25704;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
||||
const int16_t HASH_KEYWORD_TT=2688;
|
||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_C=67;
|
||||
const int16_t HASH_KEYWORD_T=84;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
@@ -91,7 +88,7 @@ Print *DCCEXParser::stashStream = NULL;
|
||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||
byte DCCEXParser::stashTarget=0;
|
||||
|
||||
// This is a JMRI command parser, one instance per incoming stream
|
||||
// This is a JMRI command parser.
|
||||
// It doesnt know how the string got here, nor how it gets back.
|
||||
// It knows nothing about hardware or tracks... it just parses strings and
|
||||
// calls the corresponding DCC api.
|
||||
@@ -145,7 +142,7 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
runningValue = 16 * runningValue + (hot - 'A' + 10);
|
||||
break;
|
||||
}
|
||||
if (hot >= 'A' && hot <= 'Z')
|
||||
if (hot=='_' || (hot >= 'A' && hot <= 'Z'))
|
||||
{
|
||||
// Since JMRI got modified to send keywords in some rare cases, we need this
|
||||
// Super Kluge to turn keywords into a hash value that can be recognised later
|
||||
@@ -162,9 +159,12 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
return parameterCount;
|
||||
}
|
||||
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||
extern __attribute__((weak)) void myFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = myFilter;
|
||||
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||
|
||||
// deprecated
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterCallback = filter;
|
||||
@@ -184,12 +184,26 @@ 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,NULL);
|
||||
parse(&USB_SERIAL,(byte *)buffer,NULL);
|
||||
}
|
||||
|
||||
// See documentation on DCC class for info on this section
|
||||
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
|
||||
// found is true first after the leading "<" has been passed
|
||||
bool found = (com[0] != '<');
|
||||
for (byte *c=com; c[0] != '\0'; c++) {
|
||||
if (found) {
|
||||
parseOne(stream, c, ringStream);
|
||||
found=false;
|
||||
}
|
||||
if (c[0] == '<')
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
#ifndef DISABLE_EEPROM
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
@@ -214,10 +228,23 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return; // filterCallback asked us to ignore
|
||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||
{
|
||||
if (params==1) { // <t cab> display state
|
||||
|
||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||
if (slot>=0) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
}
|
||||
else // send dummy state speed 0 fwd no functions.
|
||||
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t cab;
|
||||
int16_t tspeed;
|
||||
int16_t direction;
|
||||
|
||||
|
||||
if (params == 4)
|
||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||
cab = p[1];
|
||||
@@ -259,33 +286,44 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
|
||||
{
|
||||
int address;
|
||||
byte subaddress;
|
||||
byte activep;
|
||||
byte onoff;
|
||||
if (params==2) { // <a LINEARADDRESS ACTIVATE>
|
||||
address=(p[0] - 1) / 4 + 1;
|
||||
subaddress=(p[0] - 1) % 4;
|
||||
activep=1;
|
||||
activep=1;
|
||||
onoff=2; // send both
|
||||
}
|
||||
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
activep=2;
|
||||
onoff=2; // send both
|
||||
}
|
||||
else if (params==4) { // <a ADDRESS SUBADDRESS ACTIVATE ONOFF>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
if ((p[3] < 0) || (p[3] > 1)) // invalid onoff 0|1
|
||||
break;
|
||||
onoff=p[3];
|
||||
}
|
||||
else break; // invalid no of parameters
|
||||
|
||||
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;
|
||||
((address & 0x01FF) != address) // invalid address (limit 9 bits)
|
||||
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits)
|
||||
|| (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1
|
||||
) break;
|
||||
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
|
||||
#else
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1,onoff);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -317,7 +355,8 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||
// NOTE: this command was parsed in HEX instead of decimal
|
||||
params--; // drop REG
|
||||
if (params<1) break;
|
||||
if (params<1) break;
|
||||
if (params > MAX_PACKET_SIZE) break;
|
||||
{
|
||||
byte packet[params];
|
||||
for (int i=0;i<params;i++) {
|
||||
@@ -333,7 +372,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
break;
|
||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||
DCC::setLocoId(p[0],callback_Wloco);
|
||||
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||
else // WRITE CV ON PROG <W CV VALUE>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||
return;
|
||||
|
||||
@@ -361,6 +402,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case 'R': // READ CV ON PROG
|
||||
if (params == 1)
|
||||
{ // <R CV> -- uses verify callback
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
DCC::verifyCVByte(p[0], 0, callback_Vbyte);
|
||||
return;
|
||||
}
|
||||
if (params == 3)
|
||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
@@ -401,9 +449,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -428,12 +476,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -444,18 +492,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
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>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||
return;
|
||||
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
|
||||
break;
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
@@ -483,6 +527,11 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
return;
|
||||
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
|
||||
return;
|
||||
@@ -497,17 +546,80 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
if(params!=3) break;
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
|
||||
DCC::setFn(p[0], p[1], p[2] == 1);
|
||||
return;
|
||||
if (DCC::setFn(p[0], p[1], p[2] == 1)) return;
|
||||
break;
|
||||
|
||||
#if WIFI_ON
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
if (atCommandCallback && !ringStream) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
atCommandCallback((HardwareSerial *)stream,com);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
sendFlashList(stream,RMFT2::routeIdList);
|
||||
sendFlashList(stream,RMFT2::automationIdList);
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
||||
id,
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getRouteType(id), // A/R
|
||||
RMFT2::getRouteDescription(id)
|
||||
#else
|
||||
'X',F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
|
||||
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_T: // <JT> returns turnout list
|
||||
StringFormatter::send(stream, F("<jT"));
|
||||
if (params==1) { // <JT>
|
||||
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
||||
if (t->isHidden()) continue;
|
||||
StringFormatter::send(stream, F(" %d"),t->getId());
|
||||
}
|
||||
}
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
default: break;
|
||||
} // switch(p[1])
|
||||
break; // case J
|
||||
}
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||
@@ -521,6 +633,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
StringFormatter::send(stream, F("<X>\n"));
|
||||
}
|
||||
|
||||
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t value=GETFLASHW(flashList+i);
|
||||
if (value==0) return;
|
||||
StringFormatter::send(stream,F(" %d"),value);
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
@@ -569,43 +689,39 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
//===================================
|
||||
bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
if (params == 2)
|
||||
{
|
||||
byte instructionField = p[1] & 0xE0; // 1110 0000
|
||||
if (instructionField == 0x80) // 1000 0000 Function group 1
|
||||
{
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||
funcmap(p[0], normalized, 0, 4);
|
||||
}
|
||||
else if (instructionField == 0xA0) // 1010 0000 Function group 2
|
||||
{
|
||||
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
|
||||
funcmap(p[0], p[1], 5, 8);
|
||||
else
|
||||
funcmap(p[0], p[1], 9, 12);
|
||||
}
|
||||
}
|
||||
if (params == 3)
|
||||
{
|
||||
if (p[1] == 222)
|
||||
funcmap(p[0], p[2], 13, 20);
|
||||
else if (p[1] == 223)
|
||||
funcmap(p[0], p[2], 21, 28);
|
||||
}
|
||||
(void)stream; // NO RESPONSE
|
||||
return true;
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
if (params == 2) {
|
||||
byte instructionField = p[1] & 0xE0; // 1110 0000
|
||||
if (instructionField == 0x80) { // 1000 0000 Function group 1
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
|
||||
return (funcmap(p[0], normalized, 0, 4));
|
||||
} else if (instructionField == 0xA0) { // 1010 0000 Function group 2
|
||||
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
|
||||
return (funcmap(p[0], p[1], 5, 8));
|
||||
else
|
||||
return (funcmap(p[0], p[1], 9, 12));
|
||||
}
|
||||
}
|
||||
if (params == 3) {
|
||||
if (p[1] == 222) {
|
||||
return (funcmap(p[0], p[2], 13, 20));
|
||||
} else if (p[1] == 223) {
|
||||
return (funcmap(p[0], p[2], 21, 28));
|
||||
}
|
||||
}
|
||||
(void)stream; // NO RESPONSE
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
bool DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
{
|
||||
for (int16_t i = fstart; i <= fstop; i++)
|
||||
{
|
||||
DCC::setFn(cab, i, value & 1);
|
||||
value >>= 1;
|
||||
}
|
||||
for (int16_t i = fstart; i <= fstop; i++) {
|
||||
if (! DCC::setFn(cab, i, value & 1)) return false;
|
||||
value >>= 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//===================================
|
||||
@@ -731,23 +847,23 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory());
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||
DCCACK::setAckLimit(p[2]);
|
||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
||||
DCCACK::setMinAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
||||
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||
if (p[2] >255) p[2]=3;
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
@@ -778,15 +894,13 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESET:
|
||||
{
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
break; // and <X> if we didnt restart
|
||||
}
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
@@ -821,6 +935,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
@@ -856,7 +974,14 @@ void DCCEXParser::commitAsyncReplyStream() {
|
||||
void DCCEXParser::callback_W(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
F("<r %d %d>\n"), stashP[0], result == 1 ? stashP[1] : -1);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_W4(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
|
@@ -33,6 +33,7 @@ struct DCCEXParser
|
||||
|
||||
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void parse(const FSH * cmd);
|
||||
static void parseOne(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
@@ -60,6 +61,7 @@ struct DCCEXParser
|
||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_W4(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_Rloco(int16_t result);
|
||||
@@ -69,7 +71,8 @@ struct DCCEXParser
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
|
||||
};
|
||||
|
||||
|
232
DCCRMT.cpp
Normal file
232
DCCRMT.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* © 2021-2022, Harald Barth.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "defines.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCRMT.h"
|
||||
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
||||
#include "soc/gpio_sig_map.h"
|
||||
|
||||
// Number of bits resulting out of X bytes of DCC payload data
|
||||
// Each byte has one bit extra and at the end we have one EOF marker
|
||||
#define DATA_LEN(X) ((X)*9+1)
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
|
||||
#error wrong IDF version
|
||||
#endif
|
||||
|
||||
void setDCCBit1(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_1_HALFPERIOD;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_1_HALFPERIOD;
|
||||
}
|
||||
|
||||
void setDCCBit0(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_0_HALFPERIOD;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_0_HALFPERIOD;
|
||||
}
|
||||
|
||||
// special long zero to trigger scope
|
||||
void setDCCBit0Long(rmt_item32_t* item) {
|
||||
item->level0 = 1;
|
||||
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||
item->level1 = 0;
|
||||
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||
}
|
||||
|
||||
void setEOT(rmt_item32_t* item) {
|
||||
item->val = 0;
|
||||
}
|
||||
|
||||
// This is an array that contains the this pointers
|
||||
// to all uses channel objects. This is used to determine
|
||||
// which of the channels was triggering the ISR as there
|
||||
// is only ONE common ISR routine for all channels.
|
||||
RMTChannel *channelHandle[8] = { 0 };
|
||||
|
||||
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
|
||||
RMTChannel *tt = channelHandle[channel];
|
||||
if (tt) tt->RMTinterrupt();
|
||||
}
|
||||
|
||||
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
|
||||
byte ch;
|
||||
byte plen;
|
||||
if (isMain) {
|
||||
ch = 0;
|
||||
plen = PREAMBLE_BITS_MAIN;
|
||||
} else {
|
||||
ch = 2;
|
||||
plen = PREAMBLE_BITS_PROG;
|
||||
}
|
||||
|
||||
// preamble
|
||||
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
|
||||
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
|
||||
for (byte n=0; n<plen; n++)
|
||||
setDCCBit1(preamble + n); // preamble bits
|
||||
#ifdef SCOPE
|
||||
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
|
||||
#else
|
||||
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
|
||||
#endif
|
||||
setEOT(preamble + plen + 1); // EOT marker
|
||||
|
||||
// idle
|
||||
idleLen = 28;
|
||||
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
|
||||
if (isMain) {
|
||||
for (byte n=0; n<8; n++) // 0 to 7
|
||||
setDCCBit1(idle + n);
|
||||
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
|
||||
setDCCBit0(idle + n);
|
||||
for (byte n=18; n<26; n++) // 18 to 25
|
||||
setDCCBit1(idle + n);
|
||||
} else {
|
||||
for (byte n=0; n<26; n++) // all zero
|
||||
setDCCBit0(idle + n);
|
||||
}
|
||||
setDCCBit1(idle + 26); // end bit
|
||||
setEOT(idle + 27); // EOT marker
|
||||
|
||||
// data: max packet size today is 5 + checksum
|
||||
maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum
|
||||
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
|
||||
|
||||
rmt_config_t config;
|
||||
// Configure the RMT channel for TX
|
||||
bzero(&config, sizeof(rmt_config_t));
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.channel = channel = (rmt_channel_t)ch;
|
||||
config.clk_div = RMT_CLOCK_DIVIDER;
|
||||
config.gpio_num = (gpio_num_t)pins.pin;
|
||||
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
|
||||
// number of bits needed is 22preamble + start +
|
||||
// 11*9 + extrazero + EOT = 124
|
||||
// 2 mem block of 64 RMT items should be enough
|
||||
|
||||
ESP_ERROR_CHECK(rmt_config(&config));
|
||||
addPin(pins.invpin, true);
|
||||
/*
|
||||
// test: config another gpio pin
|
||||
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
||||
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
|
||||
*/
|
||||
|
||||
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
||||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
||||
|
||||
// DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
|
||||
|
||||
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
|
||||
channelHandle[channel] = this; // used by interrupt
|
||||
rmt_register_tx_end_callback(interrupt, 0);
|
||||
rmt_set_tx_intr_en(channel, true);
|
||||
|
||||
DIAG(F("Channel %d DCC signal for %s start"), config.channel, isMain ? "MAIN" : "PROG");
|
||||
|
||||
// send one bit to kickstart the signal, remaining data will come from the
|
||||
// packet queue. We intentionally do not wait for the RMT TX complete here.
|
||||
//rmt_write_items(channel, preamble, preambleLen, false);
|
||||
RMTprefill();
|
||||
dataReady = false;
|
||||
}
|
||||
|
||||
void RMTChannel::RMTprefill() {
|
||||
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
|
||||
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
||||
}
|
||||
|
||||
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
|
||||
//int RMTChannel::RMTfillData(dccPacket packet) {
|
||||
// dataReady: Signals to then interrupt routine. It is set when
|
||||
// we have data in the channel buffer which can be copied out
|
||||
// to the HW. dataRepeat on the other hand signals back to
|
||||
// the caller of this function if the data has been sent enough
|
||||
// times (0 to 3 means 1 to 4 times in total).
|
||||
if (dataRepeat > 0) // we have still old work to do
|
||||
return dataRepeat;
|
||||
if (dataReady == true) // the packet is not copied out yet
|
||||
return 1000;
|
||||
if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data
|
||||
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen);
|
||||
return -1; // something very broken, can not convert packet
|
||||
}
|
||||
|
||||
// convert bytes to RMT stream of "bits"
|
||||
byte bitcounter = 0;
|
||||
for(byte n=0; n<byteCount; n++) {
|
||||
for(byte bit=0; bit<8; bit++) {
|
||||
if (buffer[n] & transmitMask[bit])
|
||||
setDCCBit1(data + bitcounter++);
|
||||
else
|
||||
setDCCBit0(data + bitcounter++);
|
||||
}
|
||||
setDCCBit0(data + bitcounter++); // zero at end of each byte
|
||||
}
|
||||
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
||||
setEOT(data + bitcounter++); // EOT marker
|
||||
dataLen = bitcounter;
|
||||
dataReady = true;
|
||||
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IRAM_ATTR RMTChannel::RMTinterrupt() {
|
||||
//no rmt_tx_start(channel,true) as we run in loop mode
|
||||
//preamble is always loaded at beginning of buffer
|
||||
packetCounter++;
|
||||
if (!dataReady && dataRepeat == 0) { // we did run empty
|
||||
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
||||
return; // nothing to do about that
|
||||
}
|
||||
|
||||
// take care of incoming data
|
||||
if (dataReady) { // if we have new data, fill while preamble is running
|
||||
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
|
||||
dataReady = false;
|
||||
}
|
||||
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
||||
dataRepeat--;
|
||||
}
|
||||
|
||||
bool RMTChannel::addPin(byte pin, bool inverted) {
|
||||
if (pin == UNUSED_PIN)
|
||||
return true;
|
||||
gpio_num_t gpioNum = (gpio_num_t)(pin);
|
||||
esp_err_t err;
|
||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
||||
err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
||||
if (err != ESP_OK) return false;
|
||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, inverted, 0);
|
||||
if (err != ESP_OK) return false;
|
||||
return true;
|
||||
}
|
||||
bool RMTChannel::addPin(pinpair pins) {
|
||||
return addPin(pins.pin) && addPin(pins.invpin, true);
|
||||
}
|
||||
#endif //ESP32
|
66
DCCRMT.h
Normal file
66
DCCRMT.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* © 2021-2022, Harald Barth.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "driver/rmt.h"
|
||||
#include "soc/rmt_reg.h"
|
||||
#include "soc/rmt_struct.h"
|
||||
#include "MotorDriver.h" // for class pinpair
|
||||
|
||||
// make calculations easy and set up for microseconds
|
||||
#define RMT_CLOCK_DIVIDER 80
|
||||
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
|
||||
#define DCC_0_HALFPERIOD 100 //8000
|
||||
|
||||
class RMTChannel {
|
||||
public:
|
||||
RMTChannel(pinpair pins, bool isMain);
|
||||
bool addPin(byte pin, bool inverted=0);
|
||||
bool addPin(pinpair pins);
|
||||
void IRAM_ATTR RMTinterrupt();
|
||||
void RMTprefill();
|
||||
//int RMTfillData(dccPacket packet);
|
||||
int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
|
||||
inline bool busy() {
|
||||
if (dataRepeat > 0) // we have still old work to do
|
||||
return true;
|
||||
return dataReady;
|
||||
};
|
||||
inline uint32_t packetCount() { return packetCounter; };
|
||||
|
||||
private:
|
||||
|
||||
rmt_channel_t channel;
|
||||
// 3 types of data to send, preamble and then idle or data
|
||||
// if this is prog track, idle will contain reset instead
|
||||
rmt_item32_t *idle;
|
||||
byte idleLen;
|
||||
rmt_item32_t *preamble;
|
||||
byte preambleLen;
|
||||
rmt_item32_t *data;
|
||||
byte dataLen;
|
||||
byte maxDataLen;
|
||||
uint32_t packetCounter = 0;
|
||||
// flags
|
||||
volatile bool dataReady = false; // do we have real data available or send idle
|
||||
volatile byte dataRepeat = 0;
|
||||
};
|
||||
#endif //ESP32
|
218
DCCTimer.cpp
218
DCCTimer.cpp
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DCCTimer.h"
|
||||
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||
TCB0.CNT = 0;
|
||||
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TCB0_INT_vect){
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm;
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
#elif defined(TEENSYDUINO)
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
#else
|
||||
read_mac(mac);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
void DCCTimer::read_mac(byte mac[6]) {
|
||||
read(0xe,mac,0);
|
||||
read(0xf,mac,3);
|
||||
}
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return pin==TIMER1_A_PIN
|
||||
|| pin==TIMER1_B_PIN
|
||||
#ifdef TIMER1_C_PIN
|
||||
|| pin==TIMER1_C_PIN
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#include <avr/boot.h>
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
62
DCCTimer.h
62
DCCTimer.h
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -20,6 +21,34 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* There are several different implementations of this class which the compiler will select
|
||||
according to the hardware.
|
||||
*/
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DCCTimer_h
|
||||
#define DCCTimer_h
|
||||
#include "Arduino.h"
|
||||
@@ -32,11 +61,36 @@ class DCCTimer {
|
||||
static void getSimulatedMacAddress(byte mac[6]);
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||
static void read_mac(byte mac[6]);
|
||||
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||
static void clearPWM();
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0)
|
||||
__attribute__((always_inline)) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
};
|
||||
|
||||
static int getMinimumFreeMemory();
|
||||
static void reset();
|
||||
|
||||
private:
|
||||
static int freeMemory();
|
||||
static volatile int minimum_free_memory;
|
||||
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency
|
||||
static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
#else
|
||||
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
#endif
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
123
DCCTimerAVR.cpp
Normal file
123
DCCTimerAVR.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UNO or MEGA
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
|
||||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
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
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
TCCR1A= 0;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
|
||||
}
|
||||
#endif
|
137
DCCTimerESP.cpp
Normal file
137
DCCTimerESP.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* © 2020-2022 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on an ESP8266 and ESP32
|
||||
// On ESP32 we do not even use the functions but they are here for completeness sake
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
timer1_disable();
|
||||
|
||||
// There seem to be differnt ways to attach interrupt handler
|
||||
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
|
||||
// Let us choose the one from the API
|
||||
timer1_attachInterrupt(interruptHandler);
|
||||
|
||||
// not exactly sure of order:
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
||||
timer1_write(CLOCK_CYCLES);
|
||||
}
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
|
||||
|
||||
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
// This should not be called on ESP32 so disable it
|
||||
return;
|
||||
interruptHandler = callback;
|
||||
hw_timer_t *timer = NULL;
|
||||
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
|
||||
timerAttachInterrupt(timer, interruptHandler, true);
|
||||
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
|
||||
timerAlarmEnable(timer);
|
||||
}
|
||||
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
#endif
|
||||
|
128
DCCTimerMEGAAVR.cpp
Normal file
128
DCCTimerMEGAAVR.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||
TCB0.CNT = 0;
|
||||
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TCB0_INT_vect){
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
CPU_CCP=0xD8;
|
||||
WDT.CTRLA=0x4;
|
||||
while(true){}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
176
DCCTimerSAMD.cpp
Normal file
176
DCCTimerSAMD.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a SAMD21 based board
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
|
||||
#include "FSH.h" //PMA temp debug
|
||||
#include "DIAG.h" //PMA temp debug
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
|
||||
// and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now
|
||||
// enables 10-bit mode, although 12-bit is possible
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64
|
||||
ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible
|
||||
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
|
||||
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization
|
||||
|
||||
// Timer setup - setup clock sources first
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
|
||||
GCLK_GENDIV_ID(4); // Apply to GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
|
||||
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
|
||||
GCLK_GENCTRL_ID(4); // Select GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
|
||||
4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
|
||||
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1
|
||||
while (GCLK->STATUS.bit.SYNCBUSY);
|
||||
|
||||
// Assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway
|
||||
// for "normal accuracy" DCC waveform generation. For high accuracy we're going to need
|
||||
// to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and output
|
||||
// pins for each TCC are only available on certain pins
|
||||
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
|
||||
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync
|
||||
|
||||
// Set the frequency
|
||||
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
|
||||
TCC0->PER.reg = CLOCK_CYCLES * 2;
|
||||
while (TCC0->SYNCBUSY.bit.PER);
|
||||
|
||||
// Start the timer
|
||||
TCC0->CTRLA.bit.ENABLE = 1;
|
||||
while (TCC0->SYNCBUSY.bit.ENABLE);
|
||||
|
||||
// Set the interrupt condition, priority and enable it in the NVIC
|
||||
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
|
||||
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
|
||||
NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||
// copied from rf24 branch
|
||||
void TCC0_Handler() {
|
||||
if(TCC0->INTFLAG.bit.OVF) {
|
||||
TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC1_Handler() {
|
||||
if(TCC1->INTFLAG.bit.OVF) {
|
||||
TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC2_Handler() {
|
||||
if(TCC2->INTFLAG.bit.OVF) {
|
||||
TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
|
||||
interruptHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
|
||||
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
__disable_irq();
|
||||
NVIC_SystemReset();
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#endif
|
130
DCCTimerSTM32.cpp
Normal file
130
DCCTimerSTM32.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a STM32 based boards
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include "FSH.h" //PMA temp debug
|
||||
#include "DIAG.h" //PMA temp debug
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow
|
||||
|
||||
#if defined(STM32F411RE)
|
||||
// STM32F411RE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
|
||||
#elif defined(STM32F446ZE)
|
||||
// STM32F446ZE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
|
||||
#else
|
||||
#warning Serial1 not defined
|
||||
#endif
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer11_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
timer.pause();
|
||||
timer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timer.attachInterrupt(Timer11_Handler);
|
||||
timer.refresh();
|
||||
timer.resume();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
|
||||
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
__disable_irq();
|
||||
NVIC_SystemReset();
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#endif
|
144
DCCTimerTEENSY.cpp
Normal file
144
DCCTimerTEENSY.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a TEENSY
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef TEENSYDUINO
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void teensyRead(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
teensyRead(0xe,mac,0);
|
||||
teensyRead(0xf,mac,3);
|
||||
}
|
||||
#endif
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = freeMemory();
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern "C" char* sbrk(int incr);
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
#endif
|
||||
void DCCTimer::reset() {
|
||||
// found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
#endif
|
335
DCCWaveform.cpp
335
DCCWaveform.cpp
@@ -2,7 +2,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -21,43 +21,52 @@
|
||||
* 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_ARCH_ESP32
|
||||
// This code is replaced entirely on an ESP32
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
#include "freeMemory.h"
|
||||
|
||||
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
volatile uint8_t DCCWaveform::numAckGaps=0;
|
||||
volatile uint8_t DCCWaveform::numAckSamples=0;
|
||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||
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);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
void DCCWaveform::loop(bool ackManagerActive) {
|
||||
mainTrack.checkPowerOverload(false);
|
||||
progTrack.checkPowerOverload(ackManagerActive);
|
||||
void DCCWaveform::loop() {
|
||||
// empty placemarker in case ESP32 needs something here
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
@@ -66,11 +75,11 @@ void DCCWaveform::interruptHandler() {
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
// member functions would be cleaner but have more overhead
|
||||
byte sigMain=signalTransform[mainTrack.state];
|
||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
|
||||
// Set the signal state for both tracks
|
||||
mainTrack.motorDriver->setSignal(sigMain);
|
||||
progTrack.motorDriver->setSignal(sigProg);
|
||||
TrackManager::setDCCSignal(sigMain);
|
||||
TrackManager::setPROGSignal(sigProg);
|
||||
|
||||
// Move on in the state engine
|
||||
mainTrack.state=stateTransform[mainTrack.state];
|
||||
@@ -80,10 +89,10 @@ void DCCWaveform::interruptHandler() {
|
||||
// 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();
|
||||
else DCCACK::checkAck(progTrack.getResets());
|
||||
|
||||
}
|
||||
#pragma GCC push_options
|
||||
#pragma GCC pop_options
|
||||
|
||||
// An instance of this class handles the DCC transmissions for one track. (main or prog)
|
||||
// Interrupts are marshalled via the statics.
|
||||
@@ -91,9 +100,6 @@ void DCCWaveform::interruptHandler() {
|
||||
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
|
||||
|
||||
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
|
||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
@@ -105,105 +111,10 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
requiredPreambles = preambleBits+1;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
ackPending=false;
|
||||
}
|
||||
|
||||
POWERMODE DCCWaveform::getPowerMode() {
|
||||
return powerMode;
|
||||
}
|
||||
|
||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
powerMode = mode;
|
||||
bool ison = (mode == POWERMODE::ON);
|
||||
motorDriver->setPower( ison);
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
|
||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
|
||||
tripValue=progTripValue;
|
||||
|
||||
// Trackname for diag messages later
|
||||
const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG");
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (MotorDriver::commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
setPowerMode(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname);
|
||||
} else {
|
||||
DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname);
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPowerMode(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPowerMode(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE DCCWaveform::stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool DCCWaveform::signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::interrupt2() {
|
||||
@@ -216,7 +127,7 @@ void DCCWaveform::interrupt2() {
|
||||
remainingPreambles--;
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
updateMinimumFreeMemory(22);
|
||||
DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,21 +160,20 @@ void DCCWaveform::interrupt2() {
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
sentResetsSincePacket=0;
|
||||
clearResets();
|
||||
}
|
||||
else {
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (sentResetsSincePacket<250) sentResetsSincePacket++;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
|
||||
// Wait until there is no packet pending, then make this pending
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
@@ -279,91 +189,90 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
packetPending = true;
|
||||
sentResetsSincePacket=0;
|
||||
clearResets();
|
||||
}
|
||||
|
||||
// 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();
|
||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,motorDriver->raw2mA(baseline),
|
||||
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
return packetPending;
|
||||
}
|
||||
#endif
|
||||
|
||||
void DCCWaveform::setAckPending() {
|
||||
if (isMainTrack) return;
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCACK.h"
|
||||
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
|
||||
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
|
||||
|
||||
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
requiredPreambles = preambleBits;
|
||||
}
|
||||
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::checkAck() {
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
void DCCWaveform::begin() {
|
||||
for(const auto& md: TrackManager::getMainDrivers()) {
|
||||
pinpair p = md->getSignalPin();
|
||||
if(rmtMainChannel) {
|
||||
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
|
||||
rmtMainChannel->addPin(p); // add pin to existing main channel
|
||||
} else {
|
||||
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
|
||||
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
|
||||
}
|
||||
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
MotorDriver *md = TrackManager::getProgDriver();
|
||||
if (md) {
|
||||
pinpair p = md->getSignalPin();
|
||||
if (rmtProgChannel) {
|
||||
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
|
||||
rmtProgChannel->addPin(p); // add pin to existing prog channel
|
||||
} else {
|
||||
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
|
||||
rmtProgChannel = new RMTChannel(p, false);
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
transmitRepeats=0; // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
|
||||
byte checksum = 0;
|
||||
for (byte b = 0; b < byteCount; b++) {
|
||||
checksum ^= buffer[b];
|
||||
pendingPacket[b] = buffer[b];
|
||||
}
|
||||
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||
pendingPacket[byteCount] = checksum;
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
// The resets will be zero not only now but as well repeats packets into the future
|
||||
clearResets(repeats+1);
|
||||
{
|
||||
int ret;
|
||||
do {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel != NULL)
|
||||
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
} else {
|
||||
if (rmtProgChannel != NULL)
|
||||
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
|
||||
}
|
||||
} while(ret > 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
if(isMainTrack) {
|
||||
if (rmtMainChannel == NULL)
|
||||
return true;
|
||||
return rmtMainChannel->busy();
|
||||
} else {
|
||||
if (rmtProgChannel == NULL)
|
||||
return true;
|
||||
return rmtProgChannel->busy();
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR DCCWaveform::loop() {
|
||||
DCCACK::checkAck(progTrack.getResets());
|
||||
}
|
||||
#endif
|
||||
|
150
DCCWaveform.h
150
DCCWaveform.h
@@ -25,107 +25,70 @@
|
||||
#define DCCWaveform_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCRMT.h"
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
// to the transform array.
|
||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||
|
||||
|
||||
// NOTE: static functions are used for the overall controller, then
|
||||
// one instance is created for each track.
|
||||
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
class DCCWaveform {
|
||||
public:
|
||||
DCCWaveform( byte preambleBits, bool isMain);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||
static void loop(bool ackManagerActive);
|
||||
static void begin();
|
||||
static void loop();
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
|
||||
void beginTrack();
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
void checkPowerOverload(bool ackManagerActive);
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||
return 0;
|
||||
}
|
||||
inline int getCurrentmA() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return motorDriver->raw2mA(lastCurrent);
|
||||
return 0;
|
||||
}
|
||||
inline int getMaxmA() {
|
||||
if (maxmA == 0) { //only calculate this for first request, it doesn't change
|
||||
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
|
||||
}
|
||||
return maxmA;
|
||||
}
|
||||
inline int getTripmA() {
|
||||
if (tripmA == 0) { //only calculate this for first request, it doesn't change
|
||||
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
|
||||
}
|
||||
return tripmA;
|
||||
}
|
||||
inline void clearRepeats() { transmitRepeats=0; }
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
inline void clearResets() { sentResetsSincePacket=0; }
|
||||
inline byte getResets() { return sentResetsSincePacket; }
|
||||
#else
|
||||
// extrafudge is added when we know that the resets will first come extrafudge packets in the future
|
||||
inline void clearResets(byte extrafudge=0) {
|
||||
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return;
|
||||
resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
|
||||
resetPacketBase += extrafudge;
|
||||
};
|
||||
inline byte getResets() {
|
||||
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0;
|
||||
uint32_t packetcount = isMainTrack ?
|
||||
rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
|
||||
uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic.
|
||||
if (count > UINT32_MAX/2) // we are in the extrafudge area
|
||||
return 0;
|
||||
if (count > 255) // cap to 255
|
||||
return 255;
|
||||
return count; // all special cases handled above
|
||||
};
|
||||
#endif
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
bool getPacketPending();
|
||||
|
||||
private:
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
volatile bool autoPowerOff=false;
|
||||
void setAckBaseline(); //prog track only
|
||||
void setAckPending(); //prog track only
|
||||
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
inline void doAutoPowerOff() {
|
||||
if (autoPowerOff) {
|
||||
setPowerMode(POWERMODE::OFF);
|
||||
autoPowerOff=false;
|
||||
}
|
||||
};
|
||||
inline bool canMeasureCurrent() {
|
||||
return motorDriver->canMeasureCurrent();
|
||||
};
|
||||
inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// 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];
|
||||
|
||||
#else
|
||||
volatile uint32_t resetPacketBase;
|
||||
#endif
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
void checkAck();
|
||||
|
||||
bool isMainTrack;
|
||||
MotorDriver* motorDriver;
|
||||
// Transmission controller
|
||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte transmitLength;
|
||||
@@ -138,38 +101,9 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
int lastCurrent;
|
||||
static int progTripValue;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
// ACK management (Prog track only)
|
||||
volatile bool ackPending;
|
||||
volatile bool ackDetected;
|
||||
int ackThreshold;
|
||||
int ackLimitmA = 60;
|
||||
int ackMaxCurrent;
|
||||
unsigned long ackCheckStart; // millis
|
||||
unsigned int ackCheckDuration; // millis
|
||||
|
||||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 4000; // micros
|
||||
unsigned int maxAckPulseDuration = 8500; // micros
|
||||
|
||||
volatile static uint8_t numAckGaps;
|
||||
volatile static uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static RMTChannel *rmtMainChannel;
|
||||
static RMTChannel *rmtProgChannel;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
10
EEStore.cpp
10
EEStore.cpp
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* All rights reserved.
|
||||
@@ -31,12 +31,12 @@
|
||||
#include "Sensors.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
ExternalEEPROM EEPROM;
|
||||
#endif
|
||||
|
||||
void EEStore::init() {
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three
|
||||
// A pins grounded (0b1010000 = 0x50)
|
||||
#endif
|
||||
@@ -49,7 +49,7 @@ void EEStore::init() {
|
||||
if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) {
|
||||
// if not, create blank eeStore structure (no
|
||||
// turnouts, no sensors) and save it back to EEPROM
|
||||
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID));
|
||||
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0);
|
||||
eeStore->data.nTurnouts = 0;
|
||||
eeStore->data.nSensors = 0;
|
||||
eeStore->data.nOutputs = 0;
|
||||
@@ -98,7 +98,7 @@ int EEStore::pointer() { return (eeAddress); }
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::dump(int num) {
|
||||
byte b;
|
||||
byte b = 0;
|
||||
DIAG(F("Addr 0x char"));
|
||||
for (int n = 0; n < num; n++) {
|
||||
EEPROM.get(n, b);
|
||||
|
@@ -26,7 +26,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#if defined(ARDUINO_ARCH_SAMC)
|
||||
#include <SparkFun_External_EEPROM.h>
|
||||
extern ExternalEEPROM EEPROM;
|
||||
#else
|
||||
|
61
ESP32-fixes.cpp
Normal file
61
ESP32-fixes.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include "ESP32-fixes.h"
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||
#endif
|
||||
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] != 0) {
|
||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] == 0) {
|
||||
if (!cnt_channel) {
|
||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
}
|
||||
ledcWrite(pin_to_channel[pin], value);
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020 Harald Barth
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,9 +17,10 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef freeMemory_h
|
||||
#define freeMemory_h
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes=0);
|
||||
int minimumFreeMemory();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
#endif
|
||||
|
541
EXRAIL2.cpp
541
EXRAIL2.cpp
@@ -40,7 +40,6 @@
|
||||
T2. Extend to >64k
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
@@ -50,7 +49,7 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "Turnouts.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
#include "TrackManager.h"
|
||||
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
@@ -63,15 +62,19 @@ const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||
const int16_t HASH_KEYWORD_KILL=5218;
|
||||
const int16_t HASH_KEYWORD_ALL=3457;
|
||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||
const int16_t HASH_KEYWORD_RED=26099;
|
||||
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||
|
||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
|
||||
// The thrrads exist in a ring, each time through loop() the next thread in the ring is serviced.
|
||||
// The threads exist in a ring, each time through loop() the next thread in the ring is serviced.
|
||||
|
||||
// Statics
|
||||
const int16_t LOCO_ID_WAITING=-99; // waiting for loco id from prog track
|
||||
int16_t RMFT2::progtrackLocoId; // used for callback when detecting a loco on prograck
|
||||
int16_t RMFT2::progtrackLocoId; // used for callback when detecting a loco on prog track
|
||||
bool RMFT2::diag=false; // <D EXRAIL ON>
|
||||
RMFT2 * RMFT2::loopTask=NULL; // loopTask contains the address of ONE of the tasks in a ring.
|
||||
RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
|
||||
@@ -84,6 +87,9 @@ LookList * RMFT2::onThrowLookup=NULL;
|
||||
LookList * RMFT2::onCloseLookup=NULL;
|
||||
LookList * RMFT2::onActivateLookup=NULL;
|
||||
LookList * RMFT2::onDeactivateLookup=NULL;
|
||||
LookList * RMFT2::onRedLookup=NULL;
|
||||
LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
|
||||
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
|
||||
@@ -113,69 +119,50 @@ int16_t LookList::find(int16_t value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::begin() {
|
||||
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
int progCounter;
|
||||
|
||||
// counters to create lookup arrays
|
||||
int sequenceCount=0; // to allow for seq 0 at start
|
||||
int onThrowCount=0;
|
||||
int onCloseCount=0;
|
||||
int onActivateCount=0;
|
||||
int onDeactivateCount=0;
|
||||
|
||||
// first pass count sizes for fast lookup arrays
|
||||
int16_t count=0;
|
||||
// find size for list
|
||||
for (progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
switch (opcode) {
|
||||
case OPCODE_ROUTE:
|
||||
case OPCODE_AUTOMATION:
|
||||
case OPCODE_SEQUENCE:
|
||||
sequenceCount++;
|
||||
break;
|
||||
|
||||
case OPCODE_ONTHROW:
|
||||
onThrowCount++;
|
||||
break;
|
||||
|
||||
case OPCODE_ONCLOSE:
|
||||
onCloseCount++;
|
||||
break;
|
||||
|
||||
case OPCODE_ONACTIVATE:
|
||||
onActivateCount++;
|
||||
break;
|
||||
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
onDeactivateCount++;
|
||||
break;
|
||||
|
||||
default: // Ignore
|
||||
break;
|
||||
}
|
||||
if (opcode==op1 || opcode==op2 || opcode==op3) count++;
|
||||
}
|
||||
// create list
|
||||
LookList* list=new LookList(count);
|
||||
if (count==0) return list;
|
||||
|
||||
for (progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::begin() {
|
||||
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
|
||||
// create lookups
|
||||
sequenceLookup=new LookList(sequenceCount);
|
||||
onThrowLookup=new LookList(onThrowCount);
|
||||
onCloseLookup=new LookList(onCloseCount);
|
||||
onActivateLookup=new LookList(onActivateCount);
|
||||
onDeactivateLookup=new LookList(onDeactivateCount);
|
||||
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
|
||||
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
|
||||
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
|
||||
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
|
||||
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
for (int sigpos=0;;sigpos+=3) {
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (redpin==0) break; // end of signal list
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
IODevice::write(redpin,true);
|
||||
if (amberpin) IODevice::write(amberpin,false);
|
||||
IODevice::write(greenpin,false);
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
|
||||
int progCounter;
|
||||
for (progCounter=0;; SKIPOP){
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
@@ -183,6 +170,7 @@ int16_t LookList::find(int16_t value) {
|
||||
|
||||
switch (opcode) {
|
||||
case OPCODE_AT:
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
case OPCODE_AFTER:
|
||||
case OPCODE_IF:
|
||||
case OPCODE_IFNOT: {
|
||||
@@ -196,7 +184,7 @@ int16_t LookList::find(int16_t value) {
|
||||
VPIN id=operand;
|
||||
int addr=GET_OPERAND(1);
|
||||
byte subAddr=GET_OPERAND(2);
|
||||
DCCTurnout::create(id,addr,subAddr);
|
||||
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -206,42 +194,21 @@ int16_t LookList::find(int16_t value) {
|
||||
int activeAngle=GET_OPERAND(2);
|
||||
int inactiveAngle=GET_OPERAND(3);
|
||||
int profile=GET_OPERAND(4);
|
||||
ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile);
|
||||
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PINTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
VpinTurnout::create(id,pin);
|
||||
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_ROUTE:
|
||||
case OPCODE_AUTOMATION:
|
||||
case OPCODE_SEQUENCE:
|
||||
sequenceLookup->add(operand,progCounter);
|
||||
break;
|
||||
|
||||
case OPCODE_ONTHROW:
|
||||
onThrowLookup->add(operand,progCounter);
|
||||
break;
|
||||
|
||||
case OPCODE_ONCLOSE:
|
||||
onCloseLookup->add(operand,progCounter);
|
||||
break;
|
||||
|
||||
case OPCODE_ONACTIVATE:
|
||||
onActivateLookup->add(operand,progCounter);
|
||||
break;
|
||||
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
onDeactivateLookup->add(operand,progCounter);
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_AUTOSTART:
|
||||
// automatically create a task from here at startup.
|
||||
new RMFT2(progCounter);
|
||||
// but we will do one at 0 anyway by default.
|
||||
if (progCounter>0) new RMFT2(progCounter);
|
||||
break;
|
||||
|
||||
default: // Ignore
|
||||
@@ -250,13 +217,28 @@ int16_t LookList::find(int16_t value) {
|
||||
}
|
||||
SKIPOP; // include ENDROUTES opcode
|
||||
|
||||
DIAG(F("EXRAIL %db, fl=%d seq=%d, onT=%d, onC=%d"),
|
||||
progCounter,MAX_FLAGS,
|
||||
sequenceCount, onThrowCount, onCloseCount);
|
||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||
|
||||
new RMFT2(0); // add the startup route
|
||||
}
|
||||
|
||||
void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||
}
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(routeIdList+i);
|
||||
if (rid==id) return 'R';
|
||||
if (rid==0) break;
|
||||
}
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(automationIdList+i);
|
||||
if (rid==id) return 'A';
|
||||
if (rid==0) break;
|
||||
}
|
||||
return 'X';
|
||||
}
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
@@ -304,12 +286,23 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d} "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
@@ -346,50 +339,57 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
}
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_ROUTES: // </ ROUTES > JMRI withrottle support
|
||||
if (paramCount>1) return false;
|
||||
StringFormatter::send(stream,F("</ROUTES "));
|
||||
emitWithrottleRouteList(stream);
|
||||
StringFormatter::send(stream,F(">"));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter 0 to MAX_FLAGS-1
|
||||
|
||||
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
delete task;
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
if (task->taskId==p[1]) {
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
setFlag(p[1],SECTION_FLAG);
|
||||
return true;
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
setFlag(p[1],0,SECTION_FLAG);
|
||||
return true;
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
setFlag(p[1], LATCH_FLAG);
|
||||
return true;
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
setFlag(p[1], 0, LATCH_FLAG);
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -402,11 +402,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
// Automations are given a state to set the button to "handoff" which implies
|
||||
// handing over the loco to the automation.
|
||||
// Routes are given "Set" buttons and do not cause the loco to be handed over.
|
||||
void RMFT2::emitWithrottleRouteList(Print* stream) {
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
emitWithrottleDescriptions(stream);
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
RMFT2::RMFT2(int progCtr) {
|
||||
@@ -428,7 +424,7 @@ RMFT2::RMFT2(int progCtr) {
|
||||
invert=false;
|
||||
timeoutFlag=false;
|
||||
stackDepth=0;
|
||||
onTurnoutId=0; // Not handling an ONTHROW/ONCLOSE
|
||||
onEventStartPosition=-1; // Not handling an ONxxx
|
||||
|
||||
// chain into ring of RMFTs
|
||||
if (loopTask==NULL) {
|
||||
@@ -465,10 +461,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||
void RMFT2::driveLoco(byte speed) {
|
||||
if (loco<=0) return; // Prevent broadcast!
|
||||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
/* TODO.....
|
||||
power on appropriate track if DC or main if dcc
|
||||
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
**********/
|
||||
|
||||
DCC::setThrottle(loco,speed, forward^invert);
|
||||
speedo=speed;
|
||||
}
|
||||
@@ -493,28 +493,21 @@ bool RMFT2::skipIfBlock() {
|
||||
while (nest > 0) {
|
||||
SKIPOP;
|
||||
byte opcode = GET_OPCODE;
|
||||
switch(opcode) {
|
||||
case OPCODE_ENDEXRAIL:
|
||||
kill(F("missing ENDIF"), nest);
|
||||
return false;
|
||||
case OPCODE_IF:
|
||||
case OPCODE_IFCLOSED:
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
case OPCODE_IFNOT:
|
||||
case OPCODE_IFRANDOM:
|
||||
case OPCODE_IFRESERVE:
|
||||
case OPCODE_IFTHROWN:
|
||||
case OPCODE_IFTIMEOUT:
|
||||
nest++;
|
||||
break;
|
||||
case OPCODE_ENDIF:
|
||||
nest--;
|
||||
break;
|
||||
case OPCODE_ELSE:
|
||||
// if nest==1 then this is the ELSE for the IF we are skipping
|
||||
if (nest==1) nest=0; // cause loop exit and return after ELSE
|
||||
break;
|
||||
// all other IF type commands increase the nesting level
|
||||
if (opcode>IF_TYPE_OPCODES) nest++;
|
||||
else switch(opcode) {
|
||||
case OPCODE_ENDEXRAIL:
|
||||
kill(F("missing ENDIF"), nest);
|
||||
return false;
|
||||
|
||||
case OPCODE_ENDIF:
|
||||
nest--;
|
||||
break;
|
||||
|
||||
case OPCODE_ELSE:
|
||||
// if nest==1 then this is the ELSE for the IF we are skipping
|
||||
if (nest==1) nest=0; // cause loop exit and return after ELSE
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -525,7 +518,15 @@ bool RMFT2::skipIfBlock() {
|
||||
|
||||
|
||||
/* static */ void RMFT2::readLocoCallback(int16_t cv) {
|
||||
progtrackLocoId=cv;
|
||||
if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr
|
||||
progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
||||
if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
||||
DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR);
|
||||
progtrackLocoId = -1;
|
||||
}
|
||||
} else {
|
||||
progtrackLocoId=cv;
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::loop() {
|
||||
@@ -542,6 +543,10 @@ void RMFT2::loop2() {
|
||||
|
||||
byte opcode = GET_OPCODE;
|
||||
int16_t operand = GET_OPERAND(0);
|
||||
|
||||
// skipIf will get set to indicate a failing IF condition
|
||||
bool skipIf=false;
|
||||
|
||||
// if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand);
|
||||
// Attention: Returning from this switch leaves the program counter unchanged.
|
||||
// This is used for unfinished waits for timers or sensors.
|
||||
@@ -570,6 +575,13 @@ void RMFT2::loop2() {
|
||||
driveLoco(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_FORGET:
|
||||
if (loco!=0) {
|
||||
DCC::forgetLoco(loco);
|
||||
loco=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_INVERT_DIRECTION:
|
||||
invert= !invert;
|
||||
driveLoco(speedo);
|
||||
@@ -594,6 +606,18 @@ void RMFT2::loop2() {
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATGTE: // wait for analog sensor>= value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATLT: // wait for analog sensor < value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
|
||||
timeoutStart=millis();
|
||||
timeoutFlag=false;
|
||||
@@ -609,7 +633,7 @@ void RMFT2::loop2() {
|
||||
return;
|
||||
|
||||
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
|
||||
if (!timeoutFlag) if (!skipIfBlock()) return;
|
||||
skipIf=!timeoutFlag;
|
||||
break;
|
||||
|
||||
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
|
||||
@@ -648,12 +672,21 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_SET_TRACK:
|
||||
// operand is trackmode<<8 | track id
|
||||
// If DC/DCX use my loco for DC address
|
||||
{
|
||||
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
|
||||
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
|
||||
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
pausingTask=NULL;
|
||||
driveLoco(speedo);
|
||||
@@ -661,40 +694,52 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_IF: // do next operand if sensor set
|
||||
if (!readSensor(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=!readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_ELSE: // skip to matching ENDIF
|
||||
if (!skipIfBlock()) return;
|
||||
skipIf=true;
|
||||
break;
|
||||
|
||||
case OPCODE_IFGTE: // do next operand if sensor>= value
|
||||
if (IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1))) if (!skipIfBlock()) return;
|
||||
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLT: // do next operand if sensor< value
|
||||
if (IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1))) if (!skipIfBlock()) return;
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||
if (readSensor(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
if ((int16_t)random(100)>=operand) if (!skipIfBlock()) return;
|
||||
skipIf=(int16_t)(micros()%100) >= operand;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||
if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG);
|
||||
else if (!skipIfBlock()) return;
|
||||
else skipIf=true;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRED: // do block if signal as expected
|
||||
skipIf=!isSignal(operand,SIGNAL_RED);
|
||||
break;
|
||||
|
||||
case OPCODE_IFAMBER: // do block if signal as expected
|
||||
skipIf=!isSignal(operand,SIGNAL_AMBER);
|
||||
break;
|
||||
|
||||
case OPCODE_IFGREEN: // do block if signal as expected
|
||||
skipIf=!isSignal(operand,SIGNAL_GREEN);
|
||||
break;
|
||||
|
||||
case OPCODE_IFTHROWN:
|
||||
if (Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=Turnout::isClosed(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFCLOSED:
|
||||
if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=Turnout::isThrown(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_ENDIF:
|
||||
@@ -713,19 +758,19 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_RANDWAIT:
|
||||
delayMe(random(operand)*100L);
|
||||
delayMe(operand==0 ? 0 : (micros()%operand) *100L);
|
||||
break;
|
||||
|
||||
case OPCODE_RED:
|
||||
doSignal(operand,true,false,false);
|
||||
doSignal(operand,SIGNAL_RED);
|
||||
break;
|
||||
|
||||
case OPCODE_AMBER:
|
||||
doSignal(operand,false,true,false);
|
||||
doSignal(operand,SIGNAL_AMBER);
|
||||
break;
|
||||
|
||||
case OPCODE_GREEN:
|
||||
doSignal(operand,false,false,true);
|
||||
doSignal(operand,SIGNAL_GREEN);
|
||||
break;
|
||||
|
||||
case OPCODE_FON:
|
||||
@@ -787,16 +832,25 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ENDEXRAIL:
|
||||
kill();
|
||||
return;
|
||||
|
||||
|
||||
case OPCODE_KILLALL:
|
||||
while(loopTask) loopTask->kill(F("KILLALL"));
|
||||
return;
|
||||
|
||||
case OPCODE_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_POWERON:
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
@@ -870,20 +924,26 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_AUTOSTART: // Handled only during begin process
|
||||
case OPCODE_PAD: // Just a padding for previous opcode needing >1 operad byte.
|
||||
case OPCODE_PAD: // Just a padding for previous opcode needing >1 operand byte.
|
||||
case OPCODE_TURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_ONCLOSE: // Turnout event catcers ignored here
|
||||
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
|
||||
case OPCODE_ONTHROW:
|
||||
case OPCODE_ONACTIVATE: // Activate event catcers ignored here
|
||||
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
kill(F("INVOP"),operand);
|
||||
}
|
||||
// Falling out of the switch means move on to the next opcode
|
||||
// but if we are skipping a false IF or else
|
||||
if (skipIf) if (!skipIfBlock()) return;
|
||||
SKIPOP;
|
||||
}
|
||||
|
||||
@@ -892,12 +952,13 @@ void RMFT2::delayMe(long delay) {
|
||||
delayStart=millis();
|
||||
}
|
||||
|
||||
void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
if (FLAGOVERFLOW(id)) return; // Outside range limit
|
||||
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
if (FLAGOVERFLOW(id)) return false; // Outside range limit
|
||||
byte f=flags[id];
|
||||
f &= ~offMask;
|
||||
f |= onMask;
|
||||
flags[id]=f;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RMFT2::getFlag(VPIN id,byte mask) {
|
||||
@@ -911,74 +972,114 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) {
|
||||
//if (diag) DIAG(F(" dosignal %d"),id);
|
||||
for (int sigpos=0;;sigpos+=3) {
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
//if (diag) DIAG(F("red=%d"),redpin);
|
||||
if (redpin==0) {
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return; // signal not found
|
||||
}
|
||||
if (redpin==id) {
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
//if (diag) DIAG(F("signal %d %d %d"),redpin,amberpin,greenpin);
|
||||
// If amberpin is zero, synthesise amber from red+green
|
||||
IODevice::write(redpin,red || (amber && (amberpin==0)));
|
||||
if (amberpin) IODevice::write(amberpin,amber);
|
||||
if (greenpin) IODevice::write(greenpin,green || (amber && (amberpin==0)));
|
||||
return;
|
||||
}
|
||||
int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) { // end of signal list
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return -1;
|
||||
}
|
||||
// sigid is the signal id used in RED/AMBER/GREEN macro
|
||||
// for a LED signal it will be same as redpin
|
||||
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||
|
||||
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
// Schedule any event handler for this signal change.
|
||||
// Thjis will work even without a signal definition.
|
||||
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
|
||||
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
|
||||
else handleEvent(F("AMBER"), onAmberLookup,id);
|
||||
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return;
|
||||
|
||||
// keep track of signal state
|
||||
setFlag(sigslot,rag,SIGNAL_MASK);
|
||||
|
||||
// Correct signal definition found, get the rag values
|
||||
int16_t sigpos=sigslot*4;
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
|
||||
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
|
||||
if (sigtype == SERVO_SIGNAL_FLAG) {
|
||||
// A servo signal, the pin numbers are actually servo positions
|
||||
// Note, setting a signal to a zero position has no effect.
|
||||
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
|
||||
if (diag) DIAG(F("sigA %d %d"),id,servopos);
|
||||
if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (sigtype== DCC_SIGNAL_FLAG) {
|
||||
// redpin,amberpin are the DCC addr,subaddr
|
||||
DCC::setAccessory(redpin,amberpin, rag!=SIGNAL_RED);
|
||||
return;
|
||||
}
|
||||
|
||||
// LED or similar 3 pin signal, (all pins zero would be a virtual signal)
|
||||
// If amberpin is zero, synthesise amber from red+green
|
||||
const byte SIMAMBER=0x00;
|
||||
if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only
|
||||
|
||||
// Manage invert (HIGH on) pins
|
||||
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
|
||||
|
||||
// set the three pins
|
||||
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
|
||||
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
|
||||
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return false;
|
||||
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||
}
|
||||
|
||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||
int pc= (closed?onCloseLookup:onThrowLookup)->find(turnoutId);
|
||||
if (pc<0) return;
|
||||
|
||||
// Check we dont already have a task running this turnout
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->onTurnoutId==turnoutId) {
|
||||
DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId);
|
||||
return;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
|
||||
task=new RMFT2(pc); // new task starts at this instruction
|
||||
task->onTurnoutId=turnoutId; // flag for recursion detector
|
||||
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
|
||||
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
|
||||
int pc= (activate?onActivateLookup:onDeactivateLookup)->find(addr);
|
||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
||||
}
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
|
||||
// Check we dont already have a task running this address
|
||||
// Check we dont already have a task running this handler
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->onActivateAddr==addr) {
|
||||
DIAG(F("Recursive ON(DE)ACTIVATE for %d"),addr);
|
||||
if (task->onEventStartPosition==pc) {
|
||||
DIAG(F("Recursive ON%S(%d)"),reason, id);
|
||||
return;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
|
||||
task->onActivateAddr=addr; // flag for recursion detector
|
||||
task=new RMFT2(pc); // new task starts at this instruction
|
||||
task->onEventStartPosition=pc; // flag for recursion detector
|
||||
}
|
||||
|
||||
void RMFT2::printMessage2(const FSH * msg) {
|
||||
DIAG(F("EXRAIL(%d) %S"),loco,msg);
|
||||
}
|
||||
|
||||
// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation.
|
||||
void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) {
|
||||
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
||||
type,id,description, type=='R'?'2':'4');
|
||||
}
|
||||
|
86
EXRAIL2.h
86
EXRAIL2.h
@@ -22,6 +22,7 @@
|
||||
#define EXRAIL2_H
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||
@@ -33,33 +34,50 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||
OPCODE_RESERVE,OPCODE_FREE,
|
||||
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT,
|
||||
OPCODE_ATGTE,OPCODE_ATLT,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
|
||||
OPCODE_ENDIF,OPCODE_ELSE,
|
||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
|
||||
OPCODE_ROSTER,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
// see skipIfBlock()
|
||||
IF_TYPE_OPCODES, // do not move this...
|
||||
OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN,
|
||||
OPCODE_IFGTE,OPCODE_IFLT,
|
||||
OPCODE_IFTIMEOUT,
|
||||
OPCODE_IF,OPCODE_IFNOT,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
static const byte SECTION_FLAG = 0x80;
|
||||
static const byte LATCH_FLAG = 0x40;
|
||||
static const byte TASK_FLAG = 0x20;
|
||||
static const byte SPARE_FLAG = 0x10;
|
||||
static const byte COUNTER_MASK= 0x0F;
|
||||
static const byte LATCH_FLAG = 0x40;
|
||||
static const byte TASK_FLAG = 0x20;
|
||||
static const byte SPARE_FLAG = 0x10;
|
||||
static const byte SIGNAL_MASK = 0x0C;
|
||||
static const byte SIGNAL_RED = 0x08;
|
||||
static const byte SIGNAL_AMBER = 0x0C;
|
||||
static const byte SIGNAL_GREEN = 0x04;
|
||||
|
||||
static const byte MAX_STACK_DEPTH=4;
|
||||
|
||||
@@ -86,25 +104,39 @@ class LookList {
|
||||
RMFT2(int route, uint16_t cab);
|
||||
~RMFT2();
|
||||
static void readLocoCallback(int16_t cv);
|
||||
static void emitWithrottleRouteList(Print* stream);
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static void emitTurnoutDescription(Print* stream,int16_t id);
|
||||
static const byte rosterNameCount;
|
||||
static void emitWithrottleRoster(Print * stream);
|
||||
static const FSH * getRosterFunctions(int16_t cabid);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
static const int16_t FLASH routeIdList[];
|
||||
static const int16_t FLASH automationIdList[];
|
||||
static const int16_t FLASH rosterIdList[];
|
||||
static const FSH * getRouteDescription(int16_t id);
|
||||
static char getRouteType(int16_t id);
|
||||
static const FSH * getTurnoutDescription(int16_t id);
|
||||
static const FSH * getRosterName(int16_t id);
|
||||
static const FSH * getRosterFunctions(int16_t id);
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
static void streamFlags(Print* stream);
|
||||
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||
static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||
static bool getFlag(VPIN id,byte mask);
|
||||
static int16_t progtrackLocoId;
|
||||
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
||||
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
||||
static void emitWithrottleDescriptions(Print * stream);
|
||||
|
||||
static void doSignal(int16_t id,char rag);
|
||||
static bool isSignal(int16_t id,char rag);
|
||||
static int16_t getSignalSlot(int16_t id);
|
||||
static void setTurnoutHiddenState(Turnout * t);
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
@@ -117,7 +149,6 @@ private:
|
||||
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
||||
void printMessage2(const FSH * msg);
|
||||
|
||||
|
||||
static bool diag;
|
||||
static const FLASH byte RouteCode[];
|
||||
static const FLASH int16_t SignalDefinitions[];
|
||||
@@ -127,7 +158,11 @@ private:
|
||||
static LookList * onCloseLookup;
|
||||
static LookList * onActivateLookup;
|
||||
static LookList * onDeactivateLookup;
|
||||
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
int progCounter; // Byte offset of next route opcode in ROUTES table
|
||||
@@ -144,8 +179,7 @@ private:
|
||||
bool forward;
|
||||
bool invert;
|
||||
byte speedo;
|
||||
int16_t onTurnoutId;
|
||||
int16_t onActivateAddr;
|
||||
int onEventStartPosition;
|
||||
byte stackDepth;
|
||||
int callStack[MAX_STACK_DEPTH];
|
||||
};
|
||||
|
@@ -29,11 +29,15 @@
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
#undef ATTIMEOUT
|
||||
#undef AUTOMATION
|
||||
#undef AUTOSTART
|
||||
#undef BROADCAST
|
||||
#undef CALL
|
||||
#undef CLOSE
|
||||
#undef DCC_SIGNAL
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
#undef DELAY
|
||||
@@ -51,34 +55,46 @@
|
||||
#undef FOFF
|
||||
#undef FOLLOW
|
||||
#undef FON
|
||||
#undef FORGET
|
||||
#undef FREE
|
||||
#undef FWD
|
||||
#undef GREEN
|
||||
#undef HAL
|
||||
#undef IF
|
||||
#undef IFAMBER
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
#undef IFRED
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef ONACTIVATE
|
||||
#undef ONACTIVATEL
|
||||
#undef ONAMBER
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
#undef PRINT
|
||||
#undef POM
|
||||
#undef POWEROFF
|
||||
#undef POWERON
|
||||
#undef READ_LOCO
|
||||
#undef RED
|
||||
#undef RESERVE
|
||||
@@ -97,9 +113,12 @@
|
||||
#undef SERVO
|
||||
#undef SERVO2
|
||||
#undef SERVO_TURNOUT
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STOP
|
||||
@@ -107,6 +126,8 @@
|
||||
#undef TURNOUT
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
@@ -115,14 +136,18 @@
|
||||
#define ACTIVATE(addr,subaddr)
|
||||
#define ACTIVATEL(addr)
|
||||
#define AFTER(sensor_id)
|
||||
#define ALIAS(name,value)
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id)
|
||||
#define AT(sensor_id)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||
#define AUTOMATION(id, description)
|
||||
#define AUTOMATION(id,description)
|
||||
#define AUTOSTART
|
||||
#define BROADCAST(msg)
|
||||
#define CALL(route)
|
||||
#define CLOSE(id)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
#define DELAY(mindelay)
|
||||
@@ -139,35 +164,47 @@
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
#define FON(func)
|
||||
#define FON(func)
|
||||
#define FORGET
|
||||
#define FREE(blockid)
|
||||
#define FWD(speed)
|
||||
#define GREEN(signal_id)
|
||||
#define HAL(haltype,params...)
|
||||
#define IF(sensor_id)
|
||||
#define IFAMBER(signal_id)
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
#define IFRED(signal_id)
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LCD(row,msg)
|
||||
#define LCN(msg)
|
||||
#define MOVETT(id,steps,activity)
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
#define PARSE(msg)
|
||||
#define POM(cv,value)
|
||||
#define POWEROFF
|
||||
#define POWERON
|
||||
#define READ_LOCO
|
||||
#define RED(signal_id)
|
||||
#define RESERVE(blockid)
|
||||
@@ -175,7 +212,7 @@
|
||||
#define RESUME
|
||||
#define RETURN
|
||||
#define REV(speed)
|
||||
#define ROUTE(id, description)
|
||||
#define ROUTE(id,description)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
@@ -185,10 +222,13 @@
|
||||
#define SERIAL3(msg)
|
||||
#define SERVO(id,position,profile)
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STOP
|
||||
@@ -196,6 +236,8 @@
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define UNJOIN
|
||||
#define UNLATCH(sensor_id)
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...)
|
||||
#define WAITFOR(pin)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
|
156
EXRAILMacros.h
156
EXRAILMacros.h
@@ -49,26 +49,61 @@
|
||||
|
||||
// CAUTION: The macros below are multiple passed over myAutomation.h
|
||||
|
||||
|
||||
// helper macro for turnout descriptions, creates NULL for missing description
|
||||
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
#define ALIAS(name,value) const int name=value;
|
||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 2 convert descriptions to withrottle format emitter function
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
||||
void RMFT2::emitWithrottleDescriptions(Print * stream) {
|
||||
(void)stream;
|
||||
#include "myAutomation.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
|
||||
// Pass 3... Create Text sending functions
|
||||
// Pass 2 create throttle route list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t FLASH RMFT2::routeIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
// Pass 2a create throttle automation list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
const int16_t FLASH RMFT2::automationIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
// Pass 3 Create route descriptions:
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) case id: return F(description);
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) case id: return F(description);
|
||||
const FSH * RMFT2::getRouteDescription(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass 4... Create Text sending functions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
const int StringMacroTracker1=__COUNTER__;
|
||||
#undef BROADCAST
|
||||
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
||||
#undef PARSE
|
||||
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
|
||||
#undef PRINT
|
||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||
#undef LCN
|
||||
@@ -92,64 +127,80 @@ void RMFT2::printMessage(uint16_t id) {
|
||||
}
|
||||
|
||||
|
||||
// Pass 4: Turnout descriptions (optional)
|
||||
// Pass 5: Turnout descriptions (optional)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TURNOUT
|
||||
#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break;
|
||||
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||
#undef PIN_TURNOUT
|
||||
#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break;
|
||||
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||
#undef SERVO_TURNOUT
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break;
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description)
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description)
|
||||
|
||||
void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) {
|
||||
const FSH * desc=F("");
|
||||
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
switch (turnoutid) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
default:break;
|
||||
}
|
||||
if (GETFLASH(desc)=='\0') desc=F("%d");
|
||||
StringFormatter::send(stream,desc,turnoutid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass 5: Roster names (count)
|
||||
// Pass 6: Roster IDs (count)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) +1
|
||||
|
||||
const byte RMFT2::rosterNameCount=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 6: Roster names emitter
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 6: Roster IDs
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
|
||||
void RMFT2::emitWithrottleRoster(Print * stream) {
|
||||
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
|
||||
(void)format;
|
||||
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
|
||||
#include "myAutomation.h"
|
||||
stream->write('\n');
|
||||
}
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t FLASH RMFT2::rosterIdList[]={
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
// Pass 7: functions getter
|
||||
// Pass 7: Roster names getter
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F(name);
|
||||
const FSH * RMFT2::getRosterName(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass to get roster functions
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
|
||||
switch(cabid) {
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass 8 Signal definitions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef SIGNAL
|
||||
#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin,
|
||||
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
|
||||
#undef SIGNALH
|
||||
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
|
||||
#undef SERVO_SIGNAL
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0 };
|
||||
0,0,0,0 };
|
||||
|
||||
// Last Pass : create main routes table
|
||||
// Only undef the macros, not dummy them.
|
||||
@@ -164,12 +215,15 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||
#define ALIAS(name,value)
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||
#define BROADCAST(msg) PRINT(msg)
|
||||
#define CALL(route) OPCODE_CALL,V(route),
|
||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
|
||||
@@ -177,6 +231,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DONE OPCODE_ENDTASK,0,0,
|
||||
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||
#define ELSE OPCODE_ELSE,0,0,
|
||||
@@ -189,34 +244,46 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||
#define FON(func) OPCODE_FON,V(func),
|
||||
#define FORGET OPCODE_FORGET,0,0,
|
||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||
#define HAL(haltype,params...)
|
||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
#define PARSE(msg) PRINT(msg)
|
||||
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||
@@ -234,10 +301,13 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERIAL3(msg) PRINT(msg)
|
||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
@@ -245,6 +315,8 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include "EthernetInterface.h"
|
||||
#include "DIAG.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
@@ -162,9 +163,7 @@ void EthernetInterface::loop()
|
||||
buffer[count] = '\0'; // terminate the string properly
|
||||
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
|
||||
// execute with data going directly back
|
||||
outboundRing->mark(socket);
|
||||
CommandDistributor::parse(socket,buffer,outboundRing);
|
||||
outboundRing->commit();
|
||||
return; // limit the amount of processing that takes place within 1 loop() cycle.
|
||||
}
|
||||
}
|
||||
@@ -178,6 +177,8 @@ void EthernetInterface::loop()
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
}
|
||||
|
||||
WiThrottle::loop(outboundRing);
|
||||
|
||||
// handle at most 1 outbound transmission
|
||||
int socketOut=outboundRing->read();
|
||||
|
@@ -31,7 +31,7 @@
|
||||
#include "defines.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
|
9
FSH.h
9
FSH.h
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -47,6 +48,14 @@ typedef char FSH;
|
||||
#define FLASH
|
||||
#define strlen_P strlen
|
||||
#define strcpy_P strcpy
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||
#ifdef FLASH
|
||||
#undef FLASH
|
||||
#endif
|
||||
#define FLASH PROGMEM
|
||||
#else
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "bd7c8bf"
|
||||
#define GITHUB_SHA "PORTX-HAL-20220918"
|
||||
|
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -30,6 +32,9 @@
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later
|
||||
#else
|
||||
#define I2C_USE_WIRE
|
||||
#include "I2CManager_Wire.h" // Other platforms
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -110,10 +111,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Uncomment following line to enable Wire library instead of native I2C drivers
|
||||
// Add following line to config.h to enable Wire library instead of native I2C drivers
|
||||
//#define I2C_USE_WIRE
|
||||
|
||||
// Uncomment following line to disable the use of interrupts by the native I2C drivers.
|
||||
// Add following line to config.h to disable the use of interrupts by the native I2C drivers.
|
||||
//#define I2C_NO_INTERRUPTS
|
||||
|
||||
// Default to use interrupts within the native I2C drivers.
|
||||
@@ -230,7 +231,11 @@ public:
|
||||
private:
|
||||
bool _beginCompleted = false;
|
||||
bool _clockSpeedFixed = false;
|
||||
#if defined(__arm__)
|
||||
uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32
|
||||
#else
|
||||
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
|
||||
#endif
|
||||
|
||||
// Finish off request block by waiting for completion and posting status.
|
||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||
|
@@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend))
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
else
|
||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -23,7 +25,46 @@
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// atomic.h isn't available on SAMD, and likely others too...
|
||||
#if defined(__AVR__)
|
||||
#include <util/atomic.h>
|
||||
#elif defined(__arm__)
|
||||
// Helper assembly language functions
|
||||
static __inline__ uint8_t my_iSeiRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsie i" ::);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __inline__ uint8_t my_iCliRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsid i" ::);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __inline__ void my_iRestore(const uint32_t *__s)
|
||||
{
|
||||
uint32_t res = *__s;
|
||||
__asm__ __volatile__ ("MSR primask, %0" : : "r" (res) );
|
||||
}
|
||||
|
||||
static __inline__ uint32_t my_iGetIReg( void )
|
||||
{
|
||||
uint32_t reg;
|
||||
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
|
||||
return reg;
|
||||
}
|
||||
// Macros for atomic isolation
|
||||
#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \
|
||||
__attribute__((__cleanup__(my_iRestore))) = my_iGetIReg()
|
||||
|
||||
#define ATOMIC() \
|
||||
for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \
|
||||
_done; _done = 0 )
|
||||
|
||||
#define ATOMIC_BLOCK(x) ATOMIC()
|
||||
#define ATOMIC_RESTORESTATE
|
||||
#endif
|
||||
#else
|
||||
#define ATOMIC_BLOCK(x)
|
||||
#define ATOMIC_RESTORESTATE
|
||||
|
243
I2CManager_SAMD.h
Normal file
243
I2CManager_SAMD.h
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef I2CMANAGER_SAMD_H
|
||||
#define I2CMANAGER_SAMD_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
|
||||
//#include <avr/io.h>
|
||||
//#include <avr/interrupt.h>
|
||||
#include <wiring_private.h>
|
||||
|
||||
/***************************************************************************
|
||||
* Interrupt handler.
|
||||
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
|
||||
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
|
||||
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
|
||||
* bus on the SAMD architecture
|
||||
***************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO)
|
||||
void SERCOM3_Handler() {
|
||||
I2CManagerClass::handleInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Assume SERCOM3 for now - default I2C bus on Arduino Zero and variants of same
|
||||
Sercom *s = SERCOM3;
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed register.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
int t_rise;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 1000;
|
||||
} else if (i2cClockSpeed < 800000L) {
|
||||
i2cClockSpeed = 400000L;
|
||||
t_rise = 300;
|
||||
} else if (i2cClockSpeed < 1200000L) {
|
||||
i2cClockSpeed = 1000000L;
|
||||
t_rise = 120;
|
||||
} else {
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 1000;
|
||||
}
|
||||
|
||||
// Disable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 0 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Calculate baudrate - using a rise time appropriate for the speed
|
||||
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000));
|
||||
|
||||
// Enable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Setting bus idle mode and wait for sync
|
||||
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initialise I2C registers.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_init()
|
||||
{
|
||||
//Setting clock
|
||||
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM3_CORE) | // Generic Clock 0 (SERCOM3)
|
||||
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
|
||||
GCLK_CLKCTRL_CLKEN ;
|
||||
|
||||
/* Wait for peripheral clock synchronization */
|
||||
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
|
||||
|
||||
// Software reset the SERCOM
|
||||
s->I2CM.CTRLA.bit.SWRST = 1;
|
||||
|
||||
//Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0
|
||||
while(s->I2CM.CTRLA.bit.SWRST || s->I2CM.SYNCBUSY.bit.SWRST);
|
||||
|
||||
// Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit)
|
||||
s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* |
|
||||
SERCOM_I2CM_CTRLA_SCLSM*/ ;
|
||||
|
||||
// Enable Smart mode and Quick Command
|
||||
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN;
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_EnableIRQ(SERCOM3_IRQn);
|
||||
NVIC_SetPriority (SERCOM3_IRQn, 0); /* set Priority */
|
||||
|
||||
// Enable all interrupts
|
||||
s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR;
|
||||
#endif
|
||||
|
||||
// Calculate baudrate and set default rate for now
|
||||
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * I2C_FREQ) - 7 / (2 * 1000);
|
||||
|
||||
// Enable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
|
||||
// Setting bus idle mode and wait for sync
|
||||
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
|
||||
|
||||
// Set SDA/SCL pins as outputs and enable pullups, at present we assume these are
|
||||
// the default ones for SERCOM3 (see assumption above)
|
||||
pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType);
|
||||
pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType);
|
||||
|
||||
// Enable the SCL and SDA pins on the sercom: includes increased driver strength,
|
||||
// pull-up resistors and pin multiplexer
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a start bit for transmission.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
|
||||
// We may have initiated a stop bit before this without waiting for it.
|
||||
// Wait for stop bit to be sent before sending start.
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE == 0x2);
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
{
|
||||
// Send start and address with read/write flag or'd in
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
}
|
||||
else {
|
||||
// Wait while the I2C bus is BUSY
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a stop bit for transmission (does not interrupt)
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStop() {
|
||||
s->I2CM.CTRLB.bit.CMD = 3; // Stop condition
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Close I2C down
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
I2C_sendStop();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Main state machine for I2C, called from interrupt handler or,
|
||||
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
|
||||
if (s->I2CM.STATUS.bit.ARBLOST) {
|
||||
// Arbitration lost, restart
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (s->I2CM.STATUS.bit.BUSERR) {
|
||||
// Bus error
|
||||
state = I2C_STATUS_BUS_ERROR;
|
||||
} else if (s->I2CM.INTFLAG.bit.MB) {
|
||||
// Master write completed
|
||||
if (s->I2CM.STATUS.bit.RXNACK) {
|
||||
// Nacked, send stop.
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
if (currentRequest->operation == OPERATION_SEND_P)
|
||||
s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++));
|
||||
else
|
||||
s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
} else {
|
||||
// No more data to send/receive. Initiate a STOP condition.
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK; // Done
|
||||
}
|
||||
} else if (s->I2CM.INTFLAG.bit.SB) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive) {
|
||||
currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
|
||||
bytesToReceive--;
|
||||
} else {
|
||||
// Buffer full, issue nack/stop
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
if (bytesToReceive) {
|
||||
// PMA - I think Smart Mode means we have nothing to do...
|
||||
// More bytes to receive, issue ack and start another read
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transaction finished, issue NACK and STOP.
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* I2CMANAGER_SAMD_H */
|
@@ -94,22 +94,24 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
|
||||
/***************************************************************************
|
||||
* Function to queue a request block and initiate operations.
|
||||
*
|
||||
* For the Wire version, this executes synchronously, but the status is
|
||||
* returned in the I2CRB as for the asynchronous version.
|
||||
* For the Wire version, this executes synchronously.
|
||||
* The read/write/write_P functions return I2C_STATUS_OK always, and the
|
||||
* completion status of the operation is in the request block, as for
|
||||
* the non-blocking version.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
switch (req->operation) {
|
||||
case OPERATION_READ:
|
||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||
break;
|
||||
case OPERATION_SEND:
|
||||
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
case OPERATION_SEND_P:
|
||||
req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
case OPERATION_REQUEST:
|
||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||
read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
57
IODevice.cpp
57
IODevice.cpp
@@ -32,7 +32,7 @@
|
||||
|
||||
// Link to halSetup function. If not defined, the function reference will be NULL.
|
||||
extern __attribute__((weak)) void halSetup();
|
||||
extern __attribute__((weak)) void mySetup(); // Deprecated function name, output warning if it's declared
|
||||
extern __attribute__((weak)) void exrailHalSetup();
|
||||
|
||||
//==================================================================================================================
|
||||
// Static methods
|
||||
@@ -47,12 +47,26 @@ extern __attribute__((weak)) void mySetup(); // Deprecated function name, outpu
|
||||
// Create any standard device instances that may be required, such as the Arduino pins
|
||||
// and PCA9685.
|
||||
void IODevice::begin() {
|
||||
// Initialise the IO subsystem
|
||||
// Call user's halSetup() function (if defined in the build in myHal.cpp).
|
||||
// The contents will depend on the user's system hardware configuration.
|
||||
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
|
||||
|
||||
// This is done first so that the following defaults will detect an overlap and not
|
||||
// create something that conflicts with the users vpin definitions.
|
||||
if (halSetup)
|
||||
halSetup();
|
||||
|
||||
// include any HAL devices defined in exrail.
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
// Initialise the IO subsystem defaults
|
||||
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
|
||||
// Predefine two PCA9685 modules 0x40-0x41
|
||||
// Allocates 32 pins 100-131
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
|
||||
// Predefine two MCP23017 module 0x20/0x21
|
||||
// Allocates 32 pins 164-195
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
@@ -63,16 +77,6 @@ void IODevice::begin() {
|
||||
dev->_begin();
|
||||
}
|
||||
_initPhase = false;
|
||||
|
||||
// Check for presence of deprecated mySetup() function, and output warning.
|
||||
if (mySetup)
|
||||
DIAG(F("WARNING: mySetup() function should be renamed to halSetup()"));
|
||||
|
||||
// Call user's halSetup() function (if defined in the build in myHal.cpp).
|
||||
// The contents will depend on the user's system hardware configuration.
|
||||
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
|
||||
if (halSetup)
|
||||
halSetup();
|
||||
}
|
||||
|
||||
// Overarching static loop() method for the IODevice subsystem. Works through the
|
||||
@@ -274,7 +278,36 @@ IODevice *IODevice::findDevice(VPIN vpin) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Private helper function to check for vpin overlap. Run during setup only.
|
||||
// returns true if pins DONT overlap with existing device
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress);
|
||||
#endif
|
||||
VPIN lastPin=firstPin+nPins-1;
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
|
||||
// check for pin range overlaps (verbose but compiler will fix that)
|
||||
VPIN firstDevPin=dev->_firstVpin;
|
||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||
if (!noOverlap) {
|
||||
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for overlapping I2C address
|
||||
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
||||
DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // no overlaps... OK to go on with constructor
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
// Static data
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
|
57
IODevice.h
57
IODevice.h
@@ -168,6 +168,7 @@ protected:
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
_I2CAddress=0;
|
||||
}
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
@@ -220,13 +221,16 @@ protected:
|
||||
// Common object fields.
|
||||
VPIN _firstVpin;
|
||||
int _nPins;
|
||||
|
||||
uint8_t _I2CAddress;
|
||||
// Flag whether the device supports callbacks.
|
||||
bool _hasCallback = false;
|
||||
|
||||
// Pin number of interrupt pin for GPIO extender devices. The extender module will pull this
|
||||
// pin low if an input changes state.
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
// Static support function for subclass creation
|
||||
static void addDevice(IODevice *newDevice);
|
||||
@@ -239,7 +243,6 @@ private:
|
||||
bool owns(VPIN vpin);
|
||||
// Method to find device handling Vpin
|
||||
static IODevice *findDevice(VPIN vpin);
|
||||
|
||||
IODevice *_nextDevice = 0;
|
||||
unsigned long _nextEntryTime;
|
||||
static IODevice *_firstDevice;
|
||||
@@ -257,8 +260,6 @@ private:
|
||||
class PCA9685 : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
// Constructor
|
||||
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
@@ -270,6 +271,8 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
// Device-specific initialisation
|
||||
void _begin() override;
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
@@ -281,8 +284,7 @@ private:
|
||||
void updatePosition(uint8_t pin);
|
||||
void writeDevice(uint8_t pin, int value);
|
||||
void _display() override;
|
||||
|
||||
uint8_t _I2CAddress; // 0x40-0x43 possible
|
||||
|
||||
|
||||
struct ServoData {
|
||||
uint16_t activePosition : 12; // Config parameter
|
||||
@@ -317,10 +319,10 @@ private:
|
||||
class DCCAccessoryDecoder: public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
||||
// Constructor
|
||||
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
|
||||
// Device-specific write function.
|
||||
void _begin() override;
|
||||
void _write(VPIN vpin, int value) override;
|
||||
@@ -340,13 +342,13 @@ public:
|
||||
addDevice(new ArduinoPins(firstVpin, nPins));
|
||||
}
|
||||
|
||||
// Constructor
|
||||
ArduinoPins(VPIN firstVpin, int nPins);
|
||||
|
||||
static void fastWriteDigital(uint8_t pin, uint8_t value);
|
||||
static bool fastReadDigital(uint8_t pin);
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
ArduinoPins(VPIN firstVpin, int nPins);
|
||||
|
||||
// Device-specific pin configuration
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
// Device-specific write function.
|
||||
@@ -362,6 +364,39 @@ private:
|
||||
uint8_t *_pinInUse;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-Turntable.
|
||||
*/
|
||||
|
||||
class EXTurntable : public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
// Constructor
|
||||
EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
enum ActivityNumber : uint8_t {
|
||||
Turn = 0, // Rotate turntable, maintain phase
|
||||
Turn_PInvert = 1, // Rotate turntable, invert phase
|
||||
Home = 2, // Initiate homing
|
||||
Calibrate = 3, // Initiate calibration sequence
|
||||
LED_On = 4, // Turn LED on
|
||||
LED_Slow = 5, // Set LED to a slow blink
|
||||
LED_Fast = 6, // Set LED to a fast blink
|
||||
LED_Off = 7, // Turn LED off
|
||||
Acc_On = 8, // Turn accessory pin on
|
||||
Acc_Off = 9, // Turn accessory pin off
|
||||
};
|
||||
|
||||
private:
|
||||
// Device-specific write function.
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
int _read(VPIN vpin) override;
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
||||
void _display() override;
|
||||
uint8_t _stepperStatus;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "IO_MCP23008.h"
|
||||
|
@@ -59,6 +59,10 @@
|
||||
**********************************************************************************************/
|
||||
class ADS111x: public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins,4);
|
||||
@@ -68,10 +72,6 @@ public:
|
||||
_value[i] = -1;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
void _begin() {
|
||||
// Initialise ADS device
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
|
@@ -26,8 +26,8 @@
|
||||
#define ADDRESS(packedaddr) ((packedaddr) >> 2)
|
||||
#define SUBADDRESS(packedaddr) ((packedaddr) % 4)
|
||||
|
||||
void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
new DCCAccessoryDecoder(vpin, nPins, DCCAddress, DCCSubaddress);
|
||||
void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress);
|
||||
}
|
||||
|
||||
// Constructors
|
||||
|
@@ -69,6 +69,12 @@ private:
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
|
||||
public:
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) :
|
||||
IODevice(firstVpin, nPins),
|
||||
@@ -77,12 +83,7 @@ public:
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
new DFPlayer(firstVpin, nPins, serial);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
void _begin() override {
|
||||
_serial->begin(9600);
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
|
||||
|
121
IO_EXTurntable.h
Normal file
121
IO_EXTurntable.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* © 2021, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
|
||||
*
|
||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
|
||||
*
|
||||
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
|
||||
* <D TT vpin steps activity> in the serial console
|
||||
* MOVETT(vpin, steps, activity) in EX-RAIL
|
||||
* Refer to the documentation for further information including the valid activities.
|
||||
*/
|
||||
|
||||
#ifndef IO_EXTurntable_h
|
||||
#define IO_EXTurntable_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
new EXTurntable(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initialisation of TurntableEX
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Processing loop to obtain status of stepper
|
||||
// 0 = finished moving and in correct position
|
||||
// 1 = still moving
|
||||
void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
uint8_t readBuffer[1];
|
||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||
_stepperStatus = readBuffer[0];
|
||||
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
|
||||
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
|
||||
}
|
||||
|
||||
// Read returns status as obtained in our loop.
|
||||
// Return false if our status value is invalid.
|
||||
int EXTurntable::_read(VPIN vpin) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
// DIAG(F("_read status: %d"), _stepperStatus);
|
||||
if (_stepperStatus > 1) {
|
||||
return false;
|
||||
} else {
|
||||
return _stepperStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// writeAnalogue to send the steps and activity to Turntable-EX.
|
||||
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
|
||||
// value contains the steps, bit shifted to MSB + LSB.
|
||||
// activity contains the activity flag as per this list:
|
||||
//
|
||||
// Turn = 0, // Rotate turntable, maintain phase
|
||||
// Turn_PInvert = 1, // Rotate turntable, invert phase
|
||||
// Home = 2, // Initiate homing
|
||||
// Calibrate = 3, // Initiate calibration sequence
|
||||
// LED_On = 4, // Turn LED on
|
||||
// LED_Slow = 5, // Set LED to a slow blink
|
||||
// LED_Fast = 6, // Set LED to a fast blink
|
||||
// LED_Off = 7, // Turn LED off
|
||||
// Acc_On = 8, // Turn accessory pin on
|
||||
// Acc_Off = 9 // Turn accessory pin off
|
||||
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
uint8_t stepsMSB = value >> 8;
|
||||
uint8_t stepsLSB = value & 0xFF;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress, stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||
}
|
||||
|
||||
// Display Turnetable-EX device driver info.
|
||||
void EXTurntable::_display() {
|
||||
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
#endif
|
@@ -36,7 +36,7 @@ IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *se
|
||||
|
||||
// Static create method for one module.
|
||||
void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
|
@@ -36,10 +36,10 @@
|
||||
|
||||
class IO_ExampleSerial : public IODevice {
|
||||
public:
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
|
||||
protected:
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
void _write(VPIN vpin, int value) override;
|
||||
|
@@ -47,7 +47,7 @@ protected:
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
|
||||
// Data fields
|
||||
uint8_t _I2CAddress;
|
||||
|
||||
// Allocate enough space for all input pins
|
||||
T _portInputState;
|
||||
T _portOutputState;
|
||||
@@ -69,6 +69,10 @@ protected:
|
||||
|
||||
I2CRB requestBlock;
|
||||
FSH *_deviceName;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// workaround: Has somehow no min function for all types
|
||||
static inline T min(T a, int b) { return a < b ? a : b; };
|
||||
#endif
|
||||
};
|
||||
|
||||
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
||||
@@ -246,4 +250,4 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
||||
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
17
IO_HCSR04.h
17
IO_HCSR04.h
@@ -73,6 +73,14 @@ private:
|
||||
const uint16_t factor = 58; // ms/cm
|
||||
|
||||
public:
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
if (checkNoOverlap(vpin))
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor perfroms static initialisation of the device object
|
||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
_firstVpin = vpin;
|
||||
@@ -83,14 +91,7 @@ public:
|
||||
_offThreshold = offThreshold;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
}
|
||||
|
||||
protected:
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
pinMode(_trigPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
@@ -25,19 +26,19 @@
|
||||
class MCP23008 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) {
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) {
|
||||
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_GPIO;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
|
||||
}
|
||||
|
@@ -31,9 +31,10 @@
|
||||
class MCP23017 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin)
|
||||
@@ -42,8 +43,6 @@ public:
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_GPIOA;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ static void writeRegister(byte address, byte reg, byte value);
|
||||
|
||||
// Create device driver instance.
|
||||
void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
new PCA9685(firstVpin, nPins, I2CAddress);
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
// Configure a port on the PCA9685.
|
||||
@@ -114,7 +114,6 @@ void PCA9685::_begin() {
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#endif
|
||||
@@ -125,7 +124,10 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (s != NULL) {
|
||||
// Use configured parameters
|
||||
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
|
||||
} // else { /* ignorethe request */ }
|
||||
} else {
|
||||
/* simulate digital pin on PWM */
|
||||
_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
||||
@@ -139,11 +141,11 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
|
||||
vpin, value, profile, duration);
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value > 4095) value = 4095;
|
||||
else if (value < 0) value = 0;
|
||||
@@ -153,10 +155,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
|
||||
// Servo pin not configured, so configure now using defaults
|
||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||
if (s == NULL) return; // Check for memory allocation failure
|
||||
s->activePosition = 0;
|
||||
s->activePosition = 4095;
|
||||
s->inactivePosition = 0;
|
||||
s->currentPosition = value;
|
||||
s->profile = Instant; // Use instant profile (but not this time)
|
||||
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
|
||||
}
|
||||
|
||||
// Animated profile. Initiate the appropriate action.
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
@@ -43,16 +44,16 @@
|
||||
class PCF8574 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode);
|
||||
|
13
IO_VL53L0X.h
13
IO_VL53L0X.h
@@ -127,7 +127,13 @@ private:
|
||||
};
|
||||
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 3);
|
||||
@@ -138,11 +144,6 @@ public:
|
||||
_value = 0;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
if (_xshutPin == VPIN_NONE) {
|
||||
// Check if device is already responding on the nominated address.
|
||||
|
6
LCN.cpp
6
LCN.cpp
@@ -50,7 +50,11 @@ void LCN::loop() {
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
if (!Turnout::exists(id)) LCNTurnout::create(id);
|
||||
Turnout::setClosedStateOnly(id,ch=='t');
|
||||
Turnout::turnoutlistHash++; // signals ED update of turnout data
|
||||
id = 0;
|
||||
}
|
||||
else if (ch == 'y' || ch == 'Y') { // Turnout opcodes
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
Turnout::setClosed(id,ch=='y');
|
||||
id = 0;
|
||||
}
|
||||
else if (ch == 'S' || ch == 's') {
|
||||
|
333
MotorDriver.cpp
333
MotorDriver.cpp
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
@@ -22,27 +23,72 @@
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC
|
||||
|
||||
#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_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
#include <soc/sens_struct.h>
|
||||
#undef ADC_INPUT_MAX_VALUE
|
||||
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||
|
||||
int IRAM_ATTR local_adc1_get_raw(int channel) {
|
||||
uint16_t adc_value;
|
||||
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas_start1.meas1_start_sar = 0;
|
||||
SENS.sar_meas_start1.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas_start1.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas_start1.meas1_data_sar;
|
||||
return adc_value;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||
powerPin=power_pin;
|
||||
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||
pinMode(powerPin, OUTPUT);
|
||||
invertPower=power_pin < 0;
|
||||
if (invertPower) {
|
||||
powerPin = 0-power_pin;
|
||||
IODevice::write(powerPin,HIGH);// set to OUTPUT and off
|
||||
} else {
|
||||
powerPin = power_pin;
|
||||
IODevice::write(powerPin,LOW);// set to OUTPUT and off
|
||||
}
|
||||
|
||||
signalPin=signal_pin;
|
||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||
pinMode(signalPin, OUTPUT);
|
||||
|
||||
|
||||
fastSignalPin.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
dualSignal=true;
|
||||
@@ -56,15 +102,23 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
invertBrake=brake_pin < 0;
|
||||
brakePin=invertBrake ? 0-brake_pin : brake_pin;
|
||||
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
||||
pinMode(brakePin, OUTPUT);
|
||||
setBrake(false);
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
pinMode(currentPin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
|
||||
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
|
||||
#else
|
||||
pinMode(currentPin, INPUT);
|
||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||
#endif
|
||||
}
|
||||
|
||||
faultPin=fault_pin;
|
||||
@@ -73,15 +127,40 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
pinMode(faultPin, INPUT);
|
||||
}
|
||||
|
||||
senseFactor=sense_factor;
|
||||
// This conversion performed at compile time so the remainder of the code never needs
|
||||
// float calculations or libraray code.
|
||||
senseFactorInternal=sense_factor * senseScale;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
||||
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADC_INPUT_MAX_VALUE) {
|
||||
// This would mean that the values obtained from the ADC never
|
||||
// can reach the trip value. So independent of the current, the
|
||||
// short circuit protection would never trip. So we adjust the
|
||||
// trip value so that it is tiggered when the ADC reports it's
|
||||
// maximum value instead.
|
||||
|
||||
// DIAG(F("Changing short detection value from %d to %d mA"),
|
||||
// raw2mA(rawCurrentTripValue), raw2mA(ADC_INPUT_MAX_VALUE-senseOffset));
|
||||
rawCurrentTripValue=ADC_INPUT_MAX_VALUE-senseOffset;
|
||||
}
|
||||
|
||||
if (currentPin==UNUSED_PIN)
|
||||
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
||||
else
|
||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
||||
DIAG(F("** WARNING ** No current or short detection"));
|
||||
else {
|
||||
DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"),
|
||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||
|
||||
// self testing diagnostic for the non-float converters... may be removed when happy
|
||||
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
||||
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
||||
}
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
@@ -89,15 +168,21 @@ bool MotorDriver::isPWMCapable() {
|
||||
}
|
||||
|
||||
|
||||
void MotorDriver::setPower(bool on) {
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::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);
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
else setLOW(fastPowerPin);
|
||||
else {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
}
|
||||
powerMode=mode;
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
@@ -108,32 +193,16 @@ void MotorDriver::setPower(bool on) {
|
||||
// (HIGH == release brake) and setBrake does
|
||||
// compensate for that.
|
||||
//
|
||||
void MotorDriver::setBrake(bool on) {
|
||||
void MotorDriver::setBrake(bool on, bool interruptContext) {
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (on ^ invertBrake) setHIGH(fastBrakePin);
|
||||
else setLOW(fastBrakePin);
|
||||
if (!interruptContext) {noInterrupts();}
|
||||
if (on ^ invertBrake)
|
||||
setHIGH(fastBrakePin);
|
||||
else
|
||||
setLOW(fastBrakePin);
|
||||
if (!interruptContext) {interrupts();}
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
volatile unsigned int overflow_count=0;
|
||||
#endif
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_PIN;
|
||||
}
|
||||
@@ -148,40 +217,106 @@ bool MotorDriver::canMeasureCurrent() {
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
bool irq = disableInterrupts();
|
||||
// This function should NOT be called in an interruot so we
|
||||
// dont need to fart about saving and restoring CPU specific
|
||||
// interrupt registers.
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
|
||||
#else
|
||||
noInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
enableInterrupts(irq);
|
||||
#else // Uno, Mega and all the TEENSY3* but not TEENSY4*
|
||||
unsigned char sreg_backup;
|
||||
sreg_backup = SREG; /* save interrupt enable/disable state */
|
||||
cli();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
overflow_count = 0;
|
||||
interrupts();
|
||||
#endif
|
||||
if (sreg_backup & 128) sei(); /* restore interrupt state */
|
||||
#endif // outer #
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
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 DCCTimer has set the sample time to be much faster.
|
||||
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
return (unsigned int)(raw * senseFactor);
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
|
||||
#endif
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
//DIAG(F("DCSignal %d"), speedcode);
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
interrupts();
|
||||
} else {
|
||||
noInterrupts();
|
||||
setSignal(tDir);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
int MotorDriver::mA2raw( unsigned int mA) {
|
||||
return (int)(mA / senseFactor);
|
||||
|
||||
int MotorDriver::getCurrentRawInInterrupt() {
|
||||
|
||||
// IMPORTANT: This function must be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
#ifdef ARDUINO_ARCH_ESP32 //On ESP we do all in loop() instead of in interrupt
|
||||
return getCurrentRaw();
|
||||
#else
|
||||
return analogRead(currentPin)-senseOffset;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
return (int32_t)raw * senseFactorInternal / senseScale;
|
||||
}
|
||||
unsigned int MotorDriver::mA2raw( unsigned int mA) {
|
||||
return (int32_t)mA * senseScale / senseFactorInternal;
|
||||
}
|
||||
|
||||
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
||||
(void) type; // avoid compiler warning if diag not used above.
|
||||
(void) type; // avoid compiler warning if diag not used above.
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
PortGroup *port = digitalPinToPort(pin);
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
GPIO_TypeDef *port = digitalPinToPort(pin);
|
||||
#else
|
||||
uint8_t port = digitalPinToPort(pin);
|
||||
#endif
|
||||
if (input)
|
||||
result.inout = portInputRegister(port);
|
||||
else
|
||||
@@ -190,3 +325,65 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
result.maskLOW = ~result.maskHIGH;
|
||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||
}
|
||||
|
||||
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
|
||||
// Trackname for diag messages later
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent < tripValue) {
|
||||
setPower(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPower(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
|
205
MotorDriver.h
205
MotorDriver.h
@@ -1,10 +1,12 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,6 +24,52 @@
|
||||
#ifndef MotorDriver_h
|
||||
#define MotorDriver_h
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "DCCTimer.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))
|
||||
|
||||
#define TOKENPASTE(x, y) x ## y
|
||||
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define HAVE_PORTA(X) X
|
||||
#define HAVE_PORTB(X) X
|
||||
#define HAVE_PORTC(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define HAVE_PORTB(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#define PORTA REG_PORT_OUT0
|
||||
#define HAVE_PORTA(X) X
|
||||
#define PORTB REG_PORT_OUT1
|
||||
#define HAVE_PORTB(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
#define PORTA GPIOA->ODR
|
||||
#define HAVE_PORTA(X) X
|
||||
#define PORTB GPIOB->ODR
|
||||
#define HAVE_PORTB(X) X
|
||||
#define PORTC GPIOC->ODR
|
||||
#define HAVE_PORTC(X) X
|
||||
#endif
|
||||
|
||||
// if macros not defined as pass-through we define
|
||||
// them here as someting that is valid as a
|
||||
// statement and evaluates to false.
|
||||
#ifndef HAVE_PORTA
|
||||
#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTB
|
||||
#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
@@ -29,63 +77,154 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
#if defined(__IMXRT1062__)
|
||||
struct FASTPIN {
|
||||
volatile uint32_t *inout;
|
||||
uint32_t maskHIGH;
|
||||
uint32_t maskLOW;
|
||||
class pinpair {
|
||||
public:
|
||||
pinpair(byte p1, byte p2) {
|
||||
pin = p1;
|
||||
invpin = p2;
|
||||
};
|
||||
byte pin = UNUSED_PIN;
|
||||
byte invpin = UNUSED_PIN;
|
||||
};
|
||||
|
||||
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
typedef uint32_t portreg_t;
|
||||
#else
|
||||
struct FASTPIN {
|
||||
volatile uint8_t *inout;
|
||||
uint8_t maskHIGH;
|
||||
uint8_t maskLOW;
|
||||
};
|
||||
typedef uint8_t portreg_t;
|
||||
#endif
|
||||
|
||||
struct FASTPIN {
|
||||
volatile portreg_t *inout;
|
||||
portreg_t maskHIGH;
|
||||
portreg_t maskLOW;
|
||||
volatile portreg_t *shadowinout;
|
||||
};
|
||||
// The port registers that are shadowing
|
||||
// the real port registers. These are
|
||||
// defined in Motordriver.cpp
|
||||
extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
virtual void setPower( bool on);
|
||||
virtual void setSignal( bool high);
|
||||
virtual void setBrake( bool on);
|
||||
virtual int getCurrentRaw();
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
void setPower( POWERMODE mode);
|
||||
POWERMODE getPower() { return powerMode;}
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
// we need to take care of that and we have to turn off interrupts if
|
||||
// we setSignal() or setBrake() or setPower() during that time as
|
||||
// otherwise the call from interrupt context can undo whatever we do
|
||||
// from outside interrupt
|
||||
void setBrake( bool on, bool interruptContext=false);
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
};
|
||||
inline void enableSignal(bool on) {
|
||||
if (on)
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
pinMode(brakePin, OUTPUT);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
ledcDetachPin(brakePin);
|
||||
#else
|
||||
setDCSignal(128);
|
||||
#endif
|
||||
};
|
||||
int getCurrentRaw();
|
||||
int getCurrentRawInInterrupt();
|
||||
unsigned int raw2mA( int raw);
|
||||
unsigned int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
|
||||
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
|
||||
return true;
|
||||
#else
|
||||
#ifdef digitalPinToTimer
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||
#else
|
||||
return (brakePin<14 && brakePin >1);
|
||||
#endif //digitalPinToTimer
|
||||
#endif //ESP32/ARM
|
||||
}
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
static bool usePWM;
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
inline void makeProgTrack(bool on) { // let this output know it's a prog track.
|
||||
isProgTrack = on;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
private:
|
||||
bool isProgTrack = false; // tells us if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
}
|
||||
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
VPIN powerPin;
|
||||
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
bool dualSignal; // true to use signalPin2
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
float senseFactor;
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
//
|
||||
// senseScale is chosen as 256 to give enough scale for 2 decimal place
|
||||
// raw->mA conversion with an ultra fast optimised integer multiplication
|
||||
int senseFactorInternal; // set to senseFactor * senseScale
|
||||
static const int senseScale=256;
|
||||
int senseOffset;
|
||||
unsigned int tripMilliamps;
|
||||
int rawCurrentTripValue;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
static bool disableInterrupts() {
|
||||
uint32_t primask;
|
||||
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
|
||||
__disable_irq();
|
||||
return (primask == 0) ? true : false;
|
||||
}
|
||||
static void enableInterrupts(bool doit) {
|
||||
if (doit) __enable_irq();
|
||||
}
|
||||
#endif
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
static const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
static const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* (c) 2020 Chris Harlow. All rights reserved.
|
||||
@@ -38,17 +39,55 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
// The MotorDriver definition is:
|
||||
//
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
|
||||
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
//
|
||||
// If the brakePin is negative that means the sense
|
||||
// power_pin: Turns the board on/off. Often called ENABLE or PWM on the shield
|
||||
// signal_pin: Where the DCC signal goes in. Often called DIR on the shield
|
||||
// signal_pin2: Inverse of signal_pin. A few shields need this as well, can be replace by hardware inverter
|
||||
// brake_pin: When tuned on, brake is set - output shortened (*)
|
||||
// current_pin: Current sense voltage pin from shield to ADC
|
||||
// senseFactor: Relation between volts on current_pin and actual output current
|
||||
// tripMilliamps: Short circuit trip limit in milliampere, max 32767 (32.767A)
|
||||
// faultPin: Some shields have a pin to to report a fault condition to the uCPU. High when fault occurs
|
||||
//
|
||||
// (*) If the brake_pin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake)
|
||||
//
|
||||
// Arduino standard Motor Shield
|
||||
|
||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
|
||||
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
|
||||
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
|
||||
// to tweak both these
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 1500, UNUSED_PIN)
|
||||
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
||||
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
|
||||
// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that
|
||||
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
|
||||
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
|
||||
|
||||
#else
|
||||
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN)
|
||||
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
|
||||
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
|
||||
#endif
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
@@ -65,6 +104,17 @@
|
||||
// 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.
|
||||
|
||||
// Pololu Dual TB9051FTG Motor Shield
|
||||
// This is the shield without modifications. Unfortunately the TB9051FTG driver chip on
|
||||
// the shield makes short delays when direction is switched. That means that the chip
|
||||
// can NOT provide a standard conformant DCC signal independent how hard we try. If your
|
||||
// Decoders tolerate that signal, use it by all mean but it is not recommended. Without
|
||||
// modifications it uses the following pins below which means no HA waveform and no
|
||||
// RailCom on an Arduino Mega 2560 but the DCC signal is broken anyway.
|
||||
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
|
||||
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
|
||||
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
|
||||
@@ -77,17 +127,17 @@
|
||||
|
||||
// FunduMoto Motor Shield
|
||||
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
|
||||
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(10, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
|
||||
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
|
||||
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
|
||||
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
// YFROBOT Motor Shield (V3.1)
|
||||
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
|
||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
|
||||
// Makeblock ORION UNO like sized board with integrated motor driver
|
||||
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
|
||||
@@ -104,7 +154,7 @@
|
||||
// to an NANO EVERY board. You have to make the connectons from the shield to the board
|
||||
// as in this example or adjust the values yourself.
|
||||
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
|
||||
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\
|
||||
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\
|
||||
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
|
||||
|
||||
#endif
|
||||
|
@@ -22,7 +22,7 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
|
||||
|
||||
# What’s in this Repository?
|
||||
|
||||
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.
|
||||
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
|
||||
|
||||
To utilize this sketch, you can use the following:
|
||||
|
||||
@@ -69,7 +69,7 @@ in config.h.
|
||||
* Automatic slot (register) management
|
||||
* Automation (coming soon)
|
||||
|
||||
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).
|
||||
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means, you can read [HERE](https://dcc-ex.com/about/rewrite.html).
|
||||
|
||||
# More information
|
||||
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
|
||||
|
75
Release_Notes/ThrottleAssists.md
Normal file
75
Release_Notes/ThrottleAssists.md
Normal file
@@ -0,0 +1,75 @@
|
||||
Throttle Assist updates for versiuon 4.?
|
||||
|
||||
Chris Harlow April 2022
|
||||
|
||||
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
|
||||
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
|
||||
|
||||
Turnouts:
|
||||
|
||||
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons.
|
||||
|
||||
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
|
||||
e.g. response ```<jT 1 17 22 19>```
|
||||
|
||||
```<JT 17>`` requests info on turnout 17.
|
||||
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```
|
||||
(T=thrown, C=closed)
|
||||
or ```<jT 17 C "">``` indicating turnout description not given.
|
||||
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
|
||||
|
||||
Note: It is still the throttles responsibility to monitor the status broadcasts.
|
||||
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)
|
||||
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
|
||||
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
|
||||
|
||||
|
||||
Automations/Routes
|
||||
|
||||
A throttle need to know which EXRAIL Automations and Routes it can show the user.
|
||||
|
||||
```<JA>``` Returns a list of Automations/Routes
|
||||
e.g. ```<jA 13 16 23>```
|
||||
Indicates route/automation ids.
|
||||
Information on each route needs to be obtained by
|
||||
```<JA 13>```
|
||||
returns e.g. ```<jA 13 R "description">``` for a route
|
||||
or ```<jA 13 A "description">``` for an automation.
|
||||
or ```<jA 13 X>``` for id not found
|
||||
|
||||
Whats the difference:
|
||||
A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
|
||||
Thus a route can be triggered by sending in for example ```</START 13>```.
|
||||
|
||||
An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
|
||||
Thus an Automation expects a start command with a cab id
|
||||
e.g. ```</START 13 3>```
|
||||
|
||||
|
||||
Roster Information:
|
||||
The ```<JR>``` command requests a list of cab ids from the roster.
|
||||
e.g. responding ```<jR 3 200 6336>```
|
||||
or <jR> for none.
|
||||
|
||||
Each Roster entry had a name and function map obtained by:
|
||||
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
|
||||
|
||||
Refer to EXRAIL ROSTER command for function map format.
|
||||
|
||||
|
||||
Obtaining throttle status.
|
||||
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast.
|
||||
```<l cabid slot speedbyte functionMap>```
|
||||
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
|
||||
|
||||
|
||||
COMMANDS TO AVOID
|
||||
|
||||
```<f cab func1 func2>``` Use ```<F cab function 1/0>```
|
||||
```<t slot cab speed dir>``` Just drop the slot number
|
||||
```<T commands>``` other than ```<T id 0/1>```
|
||||
```<s>```
|
||||
```<c>```
|
||||
|
||||
|
||||
|
139
Release_Notes/TrackManager.md
Normal file
139
Release_Notes/TrackManager.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# DCC++EX Track Manager
|
||||
|
||||
Chris Harlow 2022/03/23
|
||||
|
||||
**If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.**
|
||||
|
||||
What follows is for advanced users interested in managing power districts and/or running DC locomotives through DCC++EX.
|
||||
|
||||
## What is the Track Manager
|
||||
Track Manger (TM from now on) is an integral part of DCC++EX software that is responsible for:
|
||||
- Managing track power state.
|
||||
- Monitoring track overloads and shorts.
|
||||
- Routing the DCC main or prog track waveforms to the correct Motor Driver and thus track.
|
||||
- Managing the JOIN feature.
|
||||
- Intercepting throttle commands to locos running on DC tracks.
|
||||
- Handling user or EXRAIL commands to switch track status.
|
||||
|
||||
In the default scenario of a single DCC track and a PROG track, the TM behaves as for the previous versions of DCC++EX so if thats what you want, you dont need to mess with it.
|
||||
|
||||
The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog.
|
||||
|
||||
Unlike the previous version of DCC++EX, where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows :
|
||||
- None, any or all the tracks can be DCC Main.
|
||||
- None or ONE track may be DCC prog at any given time.
|
||||
- Any track may be powered on or off independently of the others.
|
||||
- Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later)
|
||||
|
||||
With such flexibility comes responsibility... the potential for making mistakes means taking extra care with your configuration!
|
||||
|
||||
**NOTE** TM does NOT use "zero stretching" to control your DC motor. Instead, it uses true Pulse Width Modulation (PWM) to efficiently run your loco using the same method a decoder uses to control a DCC loco's motor. DC locos can even run better on TM than they can on a normal analog throttle, especially at low speed, since it is always applying the full track voltage, albeit in pulses of varying duration.
|
||||
|
||||
## Using the Track Manager (DCC)
|
||||
TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog.
|
||||
|
||||
There is a new user command `<=>` which is used to control the TM but the `<0>` and `<1>` commands operate as before.
|
||||
|
||||
- `<=>` lists the current track settings.
|
||||
In a default setup this will normally return
|
||||
```
|
||||
<=A DCC>
|
||||
<=B PROG>
|
||||
```
|
||||
- `<=t DCC>` sets track t (A..H) to use the DCC main track. For example `<=C DCC>` sets track C. All tracks that are set to DCC will receive the same DCC signal waveform.
|
||||
- `<=t PROG>` Sets track t (A..H) to be the one and only PROG track. Any previous PROG track is turned off.
|
||||
- `<=t OFF>` turns off the track t. It will not power on with `<1>` because it will not know what signal to send.
|
||||
|
||||
In an all-DCC environment it is unlikely that you will need to do anything other than setting any additional tracks (C...H) as DCC in your `mySetup.h` file.
|
||||
|
||||
Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice.
|
||||
|
||||
Note that when setting a track to PROG or OFF, its power is switched off automatically. (The PROG track manages power on an as-needed basis under normal circumstances.
|
||||
When setting a track to MAIN (or DC, DCX see later) the power is applied according to the most recent `<1>` or `<0>` command as being the most compatible with previous versions.
|
||||
|
||||
## using the Track Manager (DC)
|
||||
|
||||
TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC.
|
||||
Your throttle (JMRI, EX-Webthrottle, Withrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change.
|
||||
|
||||
For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address.
|
||||
|
||||
The command to set a track to a DC address is as follows
|
||||
- `<=t DC a>` Sets track t (A..H) to use loco address a. e.g. <=A DC 3>
|
||||
|
||||
A simple 2 separate loop DC track, wired the traditional way in opposite directions, may be set like this to use loco addresses 1 and 2.
|
||||
```
|
||||
<=A DC 1>
|
||||
<=A DC 2>
|
||||
```
|
||||
|
||||
### Crossing between DC tracks
|
||||
|
||||
There are some slightly mind-bending issues to be addressed, especially if you want to be able to cross between two separate DC tracks or use your layout in DCC or DC mode. This is because the control of DC loco direction is relative to the TRACK and not the LOCO. (you turn a DC loco round on the track and it continues in the same geographical direction. You turn a DCC loco around and it continues to go forwards or backwards in the opposite geographical direction.)
|
||||
|
||||
Generally DC tracks are wired so that two mainline tracks are in opposite direction which makes operation easy BUT crossovers between tracks will cause shorts unless you have very complex switching arrangements.
|
||||
This is generally incompatible with DCC wiring which expects to be able to cross between tracks with impunity because they are all wired with the same polarity.
|
||||
|
||||
To get over this issue TM allows the polarity of a DC track to be swapped so that tracks wired for DCC may be switched to DC with a polarity chosen at run time according to your operations. So, for example, you may have two loops with a crossing between them. Normally you need them in opposite directions, but when you need to drive over the crossing, you need to switch one or other track so that they are at the same polarity.
|
||||
(This is a good case for using EXRAIL to help)
|
||||
|
||||
The command `<=t DCX a>` will set track t (A..H) to be DC but with reversed polarity compared with a track set to DC.
|
||||
|
||||
Its perfectly OK to cross between DC tracks by setting them to the same loco address and making sure you get the polarity right!
|
||||
|
||||
## Connecting Hardware
|
||||
Each track requires hardware to control it
|
||||
- Power on/off
|
||||
- Polarity (direction, signal etc)
|
||||
- Brake (shorts tracks together)
|
||||
- Current (analog reading)
|
||||
|
||||
The standard motor shields provide this for two separate tracks and are predictable and easy to use. However STACKING shields is not a viable way of adding more tracks because it prevents the software from gaining access to the individual track pins. Similarly, wiring all the signal pins together for example, will give you a shared DCC signal but it will eliminate any possibility of switching the track purpose at run time. So, you are going to have to understand enough to wire track drivers to various pins if you wish to extend beyond 2 tracks and take advantage of TM.
|
||||
|
||||
You will also need to consider the implications of differing electronic implementations that would cause unexpected issues when a loco moves between tracks. We know this works fine for a typical shield because we use `<1 JOIN>` quite happily but this may be different if you mix hardware types..... (NOT MY PROBLEM !)
|
||||
|
||||
The easiest way to consider the wiring is to treat each track individually (either as a separate driver or as half of a shield).
|
||||
|
||||
You will require,for each track, on the Arduino:
|
||||
- A GPIO pin (or a HAL vpin perhaps on an I2C extender, code TBA!!!) to switch power.
|
||||
- A GPIO pin to switch the signal direction
|
||||
- A GPIO pin with PWM capability to switch the Brake (you may omit this if you dont want any DC capability)
|
||||
- Optionally An Analog pin to read the current (unless your hardware cant do that, perhaps its just feeding a booster)
|
||||
- Optionally a GPIO fault pin if thats how your hardware works. (NOT recommended as you're going to run out of pins)
|
||||
|
||||
IF you have no more than 3 tracks and you can arrange for the signal pins to be one of 11,12,13 on a Mega, THEN there is a slight advantage internally and the waveform will be super-sharp.
|
||||
|
||||
**Hardware that has two signal pins still needs some code thought!!!!!!!!**
|
||||
|
||||
|
||||
## Configuring the Software
|
||||
|
||||
Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h
|
||||
Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1)
|
||||
|
||||
## Using EXRAIL to control Track Manager
|
||||
EXRAIL has a single additional command that can be used to automate TM.
|
||||
|
||||
- `SET_TRACK(t,mode)`
|
||||
where t is the track letter A..H and mode is one of
|
||||
- `OFF` track is switched off
|
||||
- `DCC` track gets DCC signal
|
||||
- `PROG` track gets DCC prog signal
|
||||
- `DC` track is set to DC mode with the cab address of the currently executing EXRAIL sequence.
|
||||
- `DCX` as DC but with reversed polarity.
|
||||
|
||||
DC/DCX are designed so that you can be automating a DCC loco, drive it onto a separate track and switch to DC without having to know the cab address. (e.g AUTOMATION)
|
||||
If however you are just running a ROUTE you can always do something like this:
|
||||
```
|
||||
ROUTE(77,"Set track G to DC 123")
|
||||
SETLOCO(123)
|
||||
SET_TRACK(G,DC)
|
||||
DONE
|
||||
```
|
||||
|
||||
## Where and How for the Code.
|
||||
The TM code is primarily in TrackManager.cpp which is responsible for coordinating the track settings and commands.
|
||||
|
||||
Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h
|
||||
|
||||
Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow.
|
284
Release_Notes/release_notes_v4.0.0.md
Normal file
284
Release_Notes/release_notes_v4.0.0.md
Normal file
@@ -0,0 +1,284 @@
|
||||
Version 4.0 Release Notes
|
||||
*************************
|
||||
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production Release. Release v4.0.0 is a Major release that adds significant new product design, plus Automation features and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.2.0 to v3.2.0 rc13.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
|
||||
|
||||
**All New Major DCC++EX 4.0.0 features**
|
||||
|
||||
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
|
||||
- HAL Support for;
|
||||
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
- PCA9685 PWM (servo & signal) control modules.
|
||||
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
- MP3 sound playback via DFPlayer module.
|
||||
- HC-SR04 Ultrasonic range sensor module.
|
||||
- VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
|
||||
|
||||
**New Command Station Broadcast throttle logic**
|
||||
|
||||
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
|
||||
|
||||
**New ‘Discovered Servers’ on WiFi Throttles**
|
||||
|
||||
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
|
||||
|
||||
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
|
||||
|
||||
- Use to control your entire layout or as a separate accessory/animation controller
|
||||
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
|
||||
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
|
||||
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
|
||||
|
||||
**New EX-RAIL ‘Roster’ Feature**
|
||||
|
||||
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
|
||||
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
|
||||
- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps.
|
||||
|
||||
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
|
||||
|
||||
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
|
||||
|
||||
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
|
||||
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information).
|
||||
|
||||
**DCC++EX 4.0.0 additional product enhancements**
|
||||
|
||||
- Additional Motor Shields and Motor Board {boosters) supported
|
||||
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
|
||||
- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI
|
||||
- Automatic retry on failed ACK detection to give decoders another chance
|
||||
- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts
|
||||
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
|
||||
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
|
||||
- Turnout user names display in Engine Driver & WiThrottles
|
||||
- Output class now allows ID > 255.
|
||||
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
|
||||
- Increased use of display for showing loco decoder programming information.
|
||||
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
|
||||
- Can define border between long and short addresses
|
||||
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
|
||||
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
|
||||
**4.0.0 Bug Fixes**
|
||||
|
||||
- Compiles on Nano Every
|
||||
- Diagnostic display of ack pulses >32ms
|
||||
- Current read from wrong ADC during interrupt
|
||||
- AT(+) Command Pass Through
|
||||
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
|
||||
- One-off error in CIPSEND drop
|
||||
- Common Fault Pin Error
|
||||
- Uno Memory Utilization optimized
|
||||
|
||||
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||
|
||||
- Send function commands just once instead of repeating them 4 times
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||
|
||||
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||
|
||||
- Fix for CAB Functions greater than 127
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||
|
||||
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||
|
||||
- Reorganized files for support of 128 speed steps
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||
|
||||
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||
|
||||
- Rearranges serial newlines for the benefit of JMRI
|
||||
- Major update for efficiencies in displays (LCD, OLED)
|
||||
- Add I2C Support functions
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||
|
||||
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||
|
||||
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- Improved overload messages with raw values (relative to offset)
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||
|
||||
- Prevent compiler warning about deprecated B constants
|
||||
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- `<D RESET>` command to reboot Arduino
|
||||
- Automatic sensor offset detect
|
||||
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||
|
||||
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||
- Removed ethernet mac config and made it automatic
|
||||
- Show wifi ip and port on lcd
|
||||
- Auto load config.example.h with warning
|
||||
- Dropped example .ino files
|
||||
- Corrected .ino comments
|
||||
- Add Pololu fault pin handling
|
||||
- Waveform speed/simplicity improvements
|
||||
- Improved pin speed in waveform
|
||||
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- Analog read speed improvements
|
||||
- Drop need for DIO2 library
|
||||
- Improved current check code
|
||||
- Linear command
|
||||
- Removed need for ArduinoTimers files
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for automation in future version
|
||||
- Fixed Turnout list
|
||||
- Allow command keywords in mixed case
|
||||
- Dropped unused memstream
|
||||
- PWM pin accuracy if requirements met
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||
|
||||
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
- WiFi Startup Fixes
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
|
||||
- Command to write loco address and clear consist
|
||||
- Command will allow for consist address
|
||||
- Startup commands implemented
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
|
||||
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- Simultaneously update JMRI to handle new current meter
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
|
||||
- Add back fix for jitter
|
||||
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||
|
||||
**CommandStation-EX V3.0.0:**
|
||||
|
||||
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - `<#>` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**CommandStation-EX Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - Oregon, USA (MSteveTodd)
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**Engine Driver and JMRI Interface**
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**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 - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Colin Grabham - Central NSW, Australia (Kebbin)
|
||||
|
||||
**WebThrotle-EX**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
- Keith Ledbetter
|
||||
- Brad Van der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sam Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz)
|
110
RingStream.cpp
110
RingStream.cpp
@@ -18,9 +18,17 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
// NOTE: The use of a marker byte without an escape algorithm means
|
||||
// RingStream is unsuitable for binary data. Should binary data need to be
|
||||
// streamed it will be necessary to implementr an escape strategy to handle the
|
||||
// marker char when embedded in data.
|
||||
|
||||
#include "RingStream.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
const byte FLASH_INSERT_MARKER=0xff;
|
||||
|
||||
RingStream::RingStream( const uint16_t len)
|
||||
{
|
||||
_len=len;
|
||||
@@ -31,6 +39,7 @@ RingStream::RingStream( const uint16_t len)
|
||||
_overflow=false;
|
||||
_mark=0;
|
||||
_count=0;
|
||||
_flashInsert=0;
|
||||
}
|
||||
|
||||
size_t RingStream::write(uint8_t b) {
|
||||
@@ -46,8 +55,78 @@ size_t RingStream::write(uint8_t b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ideally, I would prefer to override the Print:print(_FlashStringHelper) function
|
||||
// but the library authors omitted to make this virtual.
|
||||
// Therefore we obveride the only other simple function that has no side effects
|
||||
// in order that StringFormatter can recognise a RingStream and call its
|
||||
// printFlash() directly.
|
||||
int RingStream::availableForWrite() {
|
||||
return THIS_IS_A_RINGSTREAM;
|
||||
}
|
||||
|
||||
size_t RingStream::printFlash(const FSH * flashBuffer) {
|
||||
// We are about to add a PROGMEM string to the buffer.
|
||||
// To save RAM we can insert a marker and the
|
||||
// progmem address into the buffer instead.
|
||||
// The buffer reading code must recognise this marker and
|
||||
// silently extract the progmem bytes.
|
||||
// In addition, we must make the count correct as if the
|
||||
// string had been embedded so that things like the wifi code
|
||||
// can read the expected count before reading the buffer.
|
||||
|
||||
// Establish the actual length of the progmem string.
|
||||
char * flash=(char *)flashBuffer;
|
||||
int16_t plength=strlen_P(flash);
|
||||
if (plength==0) return 0; // just ignore empty string
|
||||
|
||||
// Retain the buffer count as it will be modified by the marker+address insert
|
||||
int prevCount=_count;
|
||||
write(FLASH_INSERT_MARKER); // write the marker
|
||||
uintptr_t iFlash=reinterpret_cast<uintptr_t>(flash); // expect size match with pointer
|
||||
|
||||
// write address bytes LSB first (size depends on CPU)
|
||||
for (byte f=0;f<sizeof(iFlash); f++) {
|
||||
write((byte) (iFlash & 0xFF));
|
||||
iFlash>>=8;
|
||||
}
|
||||
|
||||
// correct the buffer count to reflect the flash length, not the marker/addr.
|
||||
_count=prevCount+plength;
|
||||
return plength;
|
||||
}
|
||||
|
||||
int RingStream::read() {
|
||||
if (_flashInsert) {
|
||||
// we are reading out of a flash string
|
||||
byte fb=GETFLASH(_flashInsert);
|
||||
_flashInsert++;
|
||||
if (fb) return fb; // we have a byte from the flash
|
||||
// flash insert complete, clear and drop through to next buffer byte
|
||||
_flashInsert=NULL;
|
||||
}
|
||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||
byte b=readRawByte();
|
||||
if (b!=FLASH_INSERT_MARKER) return b;
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// Detected a flash insert
|
||||
// read address bytes LSB first (size depends on CPU)
|
||||
uintptr_t iFlash=0;
|
||||
for (byte f=0; f<sizeof(iFlash); f++) {
|
||||
uintptr_t bf=readRawByte();
|
||||
bf&=0x00ff;
|
||||
bf<<= (8*f); // shift byte to correct position in iFlash
|
||||
iFlash |= bf;
|
||||
}
|
||||
_flashInsert=reinterpret_cast<char * >( iFlash);
|
||||
// and try again... so will read the first byte of the insert.
|
||||
return read();
|
||||
#else
|
||||
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
|
||||
return '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
byte RingStream::readRawByte() {
|
||||
byte b=_buffer[_pos_read];
|
||||
_pos_read++;
|
||||
if (_pos_read==_len) _pos_read=0;
|
||||
@@ -55,9 +134,8 @@ int RingStream::read() {
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
int RingStream::count() {
|
||||
return (read()<<8) | read();
|
||||
return (readRawByte()<<8) | readRawByte();
|
||||
}
|
||||
|
||||
int RingStream::freeSpace() {
|
||||
@@ -69,6 +147,8 @@ int RingStream::freeSpace() {
|
||||
|
||||
// mark start of message with client id (0...9)
|
||||
void RingStream::mark(uint8_t b) {
|
||||
//DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID());
|
||||
_ringClient = b;
|
||||
_mark=_pos_write;
|
||||
write(b); // client id
|
||||
write((uint8_t)0); // count MSB placemarker
|
||||
@@ -79,20 +159,27 @@ void RingStream::mark(uint8_t b) {
|
||||
// peekTargetMark is used by the parser stash routines to know which client
|
||||
// to send a callback response to some time later.
|
||||
uint8_t RingStream::peekTargetMark() {
|
||||
return _buffer[_mark];
|
||||
return _ringClient;
|
||||
}
|
||||
|
||||
void RingStream::info() {
|
||||
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||
}
|
||||
|
||||
bool RingStream::commit() {
|
||||
_flashInsert=NULL; // prepared for first read
|
||||
if (_overflow) {
|
||||
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||
//DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||
// just throw it away
|
||||
_pos_write=_mark;
|
||||
_overflow=false;
|
||||
return false; // commit failed
|
||||
}
|
||||
if (_count==0) {
|
||||
// ignore empty response
|
||||
//DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID());
|
||||
// ignore empty response
|
||||
_pos_write=_mark;
|
||||
_ringClient = NO_CLIENT; //XXX make else clause later
|
||||
return true; // true=commit ok
|
||||
}
|
||||
// Go back to the _mark and inject the count 1 byte later
|
||||
@@ -102,14 +189,19 @@ bool RingStream::commit() {
|
||||
_mark++;
|
||||
if (_mark==_len) _mark=0;
|
||||
_buffer[_mark]=lowByte(_count);
|
||||
{ char s[_count+2];
|
||||
strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
|
||||
s[_count]=0;
|
||||
//DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
|
||||
}
|
||||
_ringClient = NO_CLIENT;
|
||||
return true; // commit worked
|
||||
}
|
||||
void RingStream::flush() {
|
||||
_pos_write=0;
|
||||
_pos_read=0;
|
||||
_buffer[0]=0;
|
||||
_flashInsert=NULL; // prepared for first read
|
||||
_ringClient = NO_CLIENT;
|
||||
}
|
||||
void RingStream::printBuffer(Print * stream) {
|
||||
_buffer[_pos_write]='\0';
|
||||
stream->print((char *)_buffer);
|
||||
}
|
||||
|
||||
|
22
RingStream.h
22
RingStream.h
@@ -21,22 +21,38 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
|
||||
class RingStream : public Print {
|
||||
|
||||
public:
|
||||
RingStream( const uint16_t len);
|
||||
|
||||
static const int THIS_IS_A_RINGSTREAM=77;
|
||||
virtual size_t write(uint8_t b);
|
||||
|
||||
// This availableForWrite function is subverted from its original intention so that a caller
|
||||
// can destinguish between a normal stream and a RingStream.
|
||||
// The Arduino compiler does not support runtime dynamic cast to perform
|
||||
// an instranceOf check.
|
||||
// This is necessary since the Print functions are mostly not virtual so
|
||||
// we cant override the print(__FlashStringHelper *) function.
|
||||
virtual int availableForWrite() override;
|
||||
using Print::write;
|
||||
size_t printFlash(const FSH * flashBuffer);
|
||||
int read();
|
||||
int count();
|
||||
int freeSpace();
|
||||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
void printBuffer(Print * streamer);
|
||||
void flush();
|
||||
void info();
|
||||
byte readRawByte();
|
||||
inline int peek() {
|
||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||
return _buffer[_pos_read];
|
||||
};
|
||||
static const byte NO_CLIENT=255;
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
@@ -45,6 +61,8 @@ class RingStream : public Print {
|
||||
int _mark;
|
||||
int _count;
|
||||
byte * _buffer;
|
||||
char * _flashInsert;
|
||||
byte _ringClient = NO_CLIENT;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
@@ -21,6 +22,20 @@
|
||||
|
||||
#include "SerialManager.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
#include <BluetoothSerial.h>
|
||||
//#include <BleSerial.h>
|
||||
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
|
||||
#error No Bluetooth library available
|
||||
#endif //ENABLED
|
||||
BluetoothSerial SerialBT;
|
||||
//BleSerial SerialBT;
|
||||
#endif //COMMANDS
|
||||
#endif //ESP32
|
||||
|
||||
SerialManager * SerialManager::first=NULL;
|
||||
|
||||
SerialManager::SerialManager(Stream * myserial) {
|
||||
@@ -32,9 +47,10 @@ SerialManager::SerialManager(Stream * myserial) {
|
||||
}
|
||||
|
||||
void SerialManager::init() {
|
||||
while (!Serial && millis() < 5000); // wait max 5s for Serial to start
|
||||
Serial.begin(115200);
|
||||
new SerialManager(&Serial);
|
||||
USB_SERIAL.begin(115200);
|
||||
while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start
|
||||
new SerialManager(&USB_SERIAL);
|
||||
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
Serial3.begin(115200);
|
||||
new SerialManager(&Serial3);
|
||||
@@ -47,13 +63,25 @@ void SerialManager::init() {
|
||||
Serial1.begin(115200);
|
||||
new SerialManager(&Serial1);
|
||||
#endif
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
{
|
||||
//SerialBT.setPin("6666"); // choose other pin
|
||||
uint64_t chipid = ESP.getEfuseMac();
|
||||
char idstr[16] = {0};
|
||||
snprintf(idstr, 15, "DCCEX-%08X",
|
||||
__builtin_bswap32((uint32_t)(chipid>>16)));
|
||||
SerialBT.begin(idstr);
|
||||
new SerialManager(&SerialBT);
|
||||
delay(1000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialManager::broadcast(RingStream * ring) {
|
||||
for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
|
||||
void SerialManager::broadcast(char * stringBuffer) {
|
||||
for (SerialManager * s=first;s;s=s->next) s->broadcast2(stringBuffer);
|
||||
}
|
||||
void SerialManager::broadcast2(RingStream * ring) {
|
||||
ring->printBuffer(serial);
|
||||
void SerialManager::broadcast2(char * stringBuffer) {
|
||||
serial->print(stringBuffer);
|
||||
}
|
||||
|
||||
void SerialManager::loop() {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "defines.h"
|
||||
#include "RingStream.h"
|
||||
|
||||
|
||||
#ifndef COMMAND_BUFFER_SIZE
|
||||
#define COMMAND_BUFFER_SIZE 100
|
||||
@@ -33,13 +33,13 @@ class SerialManager {
|
||||
public:
|
||||
static void init();
|
||||
static void loop();
|
||||
static void broadcast(RingStream * ring);
|
||||
static void broadcast(char * stringBuffer);
|
||||
|
||||
private:
|
||||
static SerialManager * first;
|
||||
SerialManager(Stream * myserial);
|
||||
void loop2();
|
||||
void broadcast2(RingStream * ring);
|
||||
void broadcast2(char * stringBuffer);
|
||||
Stream * serial;
|
||||
SerialManager * next;
|
||||
byte bufferLength;
|
||||
|
45
StringBuffer.cpp
Normal file
45
StringBuffer.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "StringBuffer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
StringBuffer::StringBuffer() {
|
||||
flush();
|
||||
};
|
||||
|
||||
char * StringBuffer::getString() {
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
void StringBuffer::flush() {
|
||||
_pos_write=0;
|
||||
_buffer[0]='\0';
|
||||
}
|
||||
|
||||
size_t StringBuffer::write(uint8_t b) {
|
||||
if (_pos_write>=buffer_max) return 0;
|
||||
_buffer[_pos_write] = b;
|
||||
++_pos_write;
|
||||
_buffer[_pos_write]='\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
38
StringBuffer.h
Normal file
38
StringBuffer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef StringBuffer_h
|
||||
#define StringBuffer_h
|
||||
#include <Arduino.h>
|
||||
|
||||
class StringBuffer : public Print {
|
||||
public:
|
||||
StringBuffer();
|
||||
// Override Print default
|
||||
virtual size_t write(uint8_t b);
|
||||
void flush();
|
||||
char * getString();
|
||||
private:
|
||||
static const int buffer_max=64; // enough for long text msgs to throttles
|
||||
int16_t _pos_write;
|
||||
char _buffer[buffer_max+1];
|
||||
};
|
||||
|
||||
#endif
|
@@ -18,15 +18,6 @@
|
||||
*/
|
||||
#include "StringFormatter.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
#else
|
||||
Print * StringFormatter::diagSerial=&Serial;
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
|
||||
bool Diag::ACK=false;
|
||||
@@ -38,22 +29,21 @@ bool Diag::LCN=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
if (!diagSerial) return;
|
||||
diagSerial->print(F("<* "));
|
||||
USB_SERIAL.print(F("<* "));
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(diagSerial,input,args);
|
||||
diagSerial->print(F(" *>\n"));
|
||||
send2(&USB_SERIAL,input,args);
|
||||
USB_SERIAL.print(F(" *>\n"));
|
||||
}
|
||||
|
||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
va_list args;
|
||||
|
||||
// Issue the LCD as a diag first
|
||||
send(diagSerial,F("<* LCD%d:"),row);
|
||||
send(&USB_SERIAL,F("<* LCD%d:"),row);
|
||||
va_start(args, input);
|
||||
send2(diagSerial,input,args);
|
||||
send(diagSerial,F(" *>\n"));
|
||||
send2(&USB_SERIAL,input,args);
|
||||
send(&USB_SERIAL,F(" *>\n"));
|
||||
|
||||
if (!LCDDisplay::lcdDisplay) return;
|
||||
LCDDisplay::lcdDisplay->setRow(row);
|
||||
@@ -97,14 +87,32 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 's': stream->print(va_arg(args, char*)); break;
|
||||
case 'e': printEscapes(stream,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 'S':
|
||||
{
|
||||
const FSH* flash= (const FSH*)va_arg(args, char*);
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// On ESP32 the reading flashstring from rinstream code
|
||||
// crashes, so don't use the flashstream hack on ESP32
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// RingStream has special logic to handle flash strings
|
||||
// but is not implemented unless wifi or ethernet are enabled.
|
||||
// The define prevents RingStream code being added unnecessariliy.
|
||||
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
|
||||
((RingStream *)stream)->printFlash(flash);
|
||||
else
|
||||
#endif
|
||||
#endif
|
||||
stream->print(flash);
|
||||
break;
|
||||
}
|
||||
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
||||
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
|
||||
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
||||
case 'b': stream->print(va_arg(args, int), BIN); break;
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print(va_arg(args, int), HEX); break;
|
||||
case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
formatLeft=true;
|
||||
@@ -150,7 +158,7 @@ void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape( char c) {
|
||||
printEscape(diagSerial,c);
|
||||
printEscape(&USB_SERIAL,c);
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape(Print * stream, char c) {
|
||||
|
@@ -20,11 +20,7 @@
|
||||
#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>
|
||||
#endif
|
||||
|
||||
#include "RingStream.h"
|
||||
#include "LCDDisplay.h"
|
||||
class Diag {
|
||||
public:
|
||||
@@ -48,7 +44,6 @@ class StringFormatter
|
||||
static void printEscape(Print * serial, char c);
|
||||
|
||||
// DIAG support
|
||||
static Print * diagSerial;
|
||||
static void diag( const FSH* input...);
|
||||
static void lcd(byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
|
411
TrackManager.cpp
Normal file
411
TrackManager.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "TrackManager.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
#define APPLY_BY_MODE(findmode,function) \
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
track[t]->function;
|
||||
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
|
||||
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
||||
byte TrackManager::lastTrack=0;
|
||||
bool TrackManager::progTrackSyncMain=false;
|
||||
bool TrackManager::progTrackBoosted=false;
|
||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
|
||||
#endif
|
||||
|
||||
|
||||
// The setup call is done this way so that the tracks can be in a list
|
||||
// from the config... the tracks default to NULL in the declaration
|
||||
void TrackManager::Setup(const FSH * shieldname,
|
||||
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
||||
MotorDriver * track3, MotorDriver * track4, MotorDriver * track5,
|
||||
MotorDriver * track6, MotorDriver * track7 ) {
|
||||
addTrack(0,track0);
|
||||
addTrack(1,track1);
|
||||
addTrack(2,track2);
|
||||
addTrack(3,track3);
|
||||
addTrack(4,track4);
|
||||
addTrack(5,track5);
|
||||
addTrack(6,track6);
|
||||
addTrack(7,track7);
|
||||
|
||||
// Default the first 2 tracks (which may be null) and perform HA waveform check.
|
||||
setTrackMode(0,TRACK_MODE_MAIN);
|
||||
setTrackMode(1,TRACK_MODE_PROG);
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DCC::begin(shieldname);
|
||||
}
|
||||
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
lastTrack=t;
|
||||
}
|
||||
}
|
||||
|
||||
// setDCCSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
// MotorDriver::setDCSignal handles shadowed IO port changes.
|
||||
// with interrupts turned off around the critical section
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab) continue;
|
||||
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// remove pin from MUX matrix and turn it off
|
||||
pinpair p = track[trackToSet]->getSignalPin();
|
||||
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
||||
gpio_reset_pin((gpio_num_t)p.pin);
|
||||
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
||||
gpio_reset_pin((gpio_num_t)p.invpin);
|
||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
}
|
||||
#endif
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
}
|
||||
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
||||
} else {
|
||||
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
|
||||
// When a track is switched, we must clear any side effects of its previous
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// re-evaluate HighAccuracy mode
|
||||
// We can only do this is all main and prog tracks agree
|
||||
bool canDo=true;
|
||||
FOR_EACH_TRACK(t) {
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
} else {
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
}
|
||||
}
|
||||
if (!canDo) {
|
||||
// if we discover that HA mode was globally impossible
|
||||
// we must adjust the trackPWM capabilities
|
||||
FOR_EACH_TRACK(t) {
|
||||
track[t]->trackPWM=false;
|
||||
//DIAG(F("Track %c trackPWM 0 (global override)"), t+'A');
|
||||
}
|
||||
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
||||
}
|
||||
#else
|
||||
// For ESP32 we just reinitialize the DCC Waveform
|
||||
DCCWaveform::begin();
|
||||
#endif
|
||||
|
||||
// This block must be AFTER the PWM-Timer modifications
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
// DC tracks need to be given speed of the throttle for that cab address
|
||||
// otherwise will not match other tracks on same cab.
|
||||
// This also needs to allow for inverted DCX
|
||||
applyDCSpeed(trackToSet);
|
||||
}
|
||||
|
||||
// Normal running tracks are set to the global power state
|
||||
track[trackToSet]->setPower(
|
||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
||||
mainPowerGuess : POWERMODE::OFF);
|
||||
//DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (trackMode[t]==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
|
||||
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]!=NULL) {
|
||||
StringFormatter::send(stream,F("<= %c "),'A'+t);
|
||||
switch(trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
StringFormatter::send(stream,F("MAIN"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
StringFormatter::send(stream,F("PROG"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
StringFormatter::send(stream,F("OFF"));
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
StringFormatter::send(stream,F("EXT"));
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
|
||||
return setTrackMode(p[0],TRACK_MODE_OFF);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
|
||||
void TrackManager::loop() {
|
||||
DCCWaveform::loop();
|
||||
DCCACK::loop();
|
||||
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
|
||||
nextCycleTrack++;
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackMode[t]==TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tempProgTrack != MAX_TRACKS+1) {
|
||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
|
||||
tempProgTrack = MAX_TRACKS+1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
progTrackSyncMain=joined;
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
||||
}
|
98
TrackManager.h
Normal file
98
TrackManager.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <vector>
|
||||
#endif
|
||||
#ifndef TrackManager_h
|
||||
#define TrackManager_h
|
||||
#include "FSH.h"
|
||||
#include "MotorDriver.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
|
||||
enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG,
|
||||
TRACK_MODE_DC, TRACK_MODE_DCX, TRACK_MODE_EXT};
|
||||
|
||||
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
|
||||
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
|
||||
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
|
||||
const byte TRACK_NUMBER_2=2, TRACK_NUMBER_C=2;
|
||||
const byte TRACK_NUMBER_3=3, TRACK_NUMBER_D=3;
|
||||
const byte TRACK_NUMBER_4=4, TRACK_NUMBER_E=4;
|
||||
const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
MotorDriver * track0,
|
||||
MotorDriver * track1=NULL,
|
||||
MotorDriver * track2=NULL,
|
||||
MotorDriver * track3=NULL,
|
||||
MotorDriver * track4=NULL,
|
||||
MotorDriver * track5=NULL,
|
||||
MotorDriver * track6=NULL,
|
||||
MotorDriver * track7=NULL
|
||||
);
|
||||
|
||||
static void setDCCSignal( bool on);
|
||||
static void setCutout( bool on);
|
||||
static void setPROGSignal( bool on);
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
#endif
|
||||
static void setPower2(bool progTrack,POWERMODE mode);
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
||||
static POWERMODE getProgPower();
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
static byte nextCycleTrack;
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
static TRACK_MODE trackMode[MAX_TRACKS];
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static byte tempProgTrack; // holds the prog track number during join
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
12
Turnouts.h
12
Turnouts.h
@@ -3,7 +3,7 @@
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -60,7 +60,8 @@ protected:
|
||||
union {
|
||||
struct {
|
||||
bool closed : 1;
|
||||
bool _rfu: 2;
|
||||
bool hidden : 1;
|
||||
bool _rfu : 1;
|
||||
uint8_t turnoutType : 5;
|
||||
};
|
||||
uint8_t flags;
|
||||
@@ -83,6 +84,7 @@ protected:
|
||||
_turnoutData.id = id;
|
||||
_turnoutData.turnoutType = turnoutType;
|
||||
_turnoutData.closed = closed;
|
||||
_turnoutData.hidden=false;
|
||||
add(this);
|
||||
}
|
||||
|
||||
@@ -104,11 +106,11 @@ protected:
|
||||
* Static functions
|
||||
*/
|
||||
|
||||
static Turnout *get(uint16_t id);
|
||||
|
||||
static void add(Turnout *tt);
|
||||
|
||||
public:
|
||||
static Turnout *get(uint16_t id);
|
||||
/*
|
||||
* Static data
|
||||
*/
|
||||
@@ -120,6 +122,8 @@ public:
|
||||
*/
|
||||
inline bool isClosed() { return _turnoutData.closed; };
|
||||
inline bool isThrown() { return !_turnoutData.closed; }
|
||||
inline bool isHidden() { return _turnoutData.hidden; }
|
||||
inline void setHidden(bool h) { _turnoutData.hidden=h; }
|
||||
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
|
||||
inline uint16_t getId() { return _turnoutData.id; }
|
||||
inline Turnout *next() { return _nextTurnout; }
|
||||
@@ -169,7 +173,7 @@ public:
|
||||
#endif
|
||||
static void printAll(Print *stream) {
|
||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
|
||||
|
||||
|
190
WiThrottle.cpp
190
WiThrottle.cpp
@@ -55,18 +55,80 @@
|
||||
#include "version.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
||||
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
||||
|
||||
WiThrottle * WiThrottle::firstThrottle=NULL;
|
||||
|
||||
static uint8_t xstrcmp(const char *s1, const char *s2) {
|
||||
while(*s1 != '\0' && *s2 != '\0') {
|
||||
if (*s1 != *s2) return 1;
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
if (*s1 == '\0' && *s2 == '\0')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void WiThrottle::findUniqThrottle(int id, char *u) {
|
||||
WiThrottle *wtmyid = NULL;
|
||||
WiThrottle *wtmyuniq = NULL;
|
||||
u[16] = '\0';
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
//DIAG(F("looking at %d as %s"),wt->clientid, wt->uniq);
|
||||
if (wtmyid == NULL && wt->clientid == id)
|
||||
wtmyid = wt;
|
||||
if (wtmyuniq == NULL && xstrcmp(u, wt->uniq) == 0)
|
||||
wtmyuniq = wt;
|
||||
}
|
||||
if (wtmyid == NULL) { // should not happen
|
||||
DIAG(F("Did not find my own wiThrottle handle"));
|
||||
return;
|
||||
}
|
||||
if (wtmyid == wtmyuniq) { // all well, just return;
|
||||
return;
|
||||
}
|
||||
if (wtmyuniq == NULL) { // register uniq in the found id
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
|
||||
return;
|
||||
}
|
||||
// do the copy (all other options above)
|
||||
for(int n=0; n < MAX_MY_LOCO; n++)
|
||||
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
|
||||
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
|
||||
wtmyid->heartBeat = wtmyuniq->heartBeat;
|
||||
wtmyid->initSent = wtmyuniq->initSent;
|
||||
wtmyid->exRailSent = wtmyuniq->exRailSent;
|
||||
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
|
||||
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
|
||||
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE)
|
||||
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
|
||||
forget(wtmyuniq->clientid); // do not use wtmyid after this
|
||||
}
|
||||
|
||||
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==wifiClient) return wt;
|
||||
return new WiThrottle( wifiClient);
|
||||
}
|
||||
|
||||
void WiThrottle::forget( byte clientId) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==clientId) {
|
||||
delete wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool WiThrottle::isThrottleInUse(int cab) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->areYouUsingThrottle(cab)) return true;
|
||||
@@ -95,6 +157,7 @@ WiThrottle::WiThrottle( int wificlientid) {
|
||||
}
|
||||
|
||||
WiThrottle::~WiThrottle() {
|
||||
if (Diag::WITHROTTLE) DIAG(F("Deleting WiThrottle client %d"),this->clientid);
|
||||
if (firstThrottle== this) {
|
||||
firstThrottle=this->nextThrottle;
|
||||
return;
|
||||
@@ -119,14 +182,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
StringFormatter::send(stream,F("]\\[%d}|{"), id);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitTurnoutDescription(stream,id);
|
||||
#else
|
||||
StringFormatter::send(stream,F("%d"), id);
|
||||
#endif
|
||||
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||
@@ -136,7 +202,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
||||
exRailSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitWithrottleRouteList(stream);
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
for (byte pass=0;pass<2;pass++) {
|
||||
// first pass automations, second pass routes.
|
||||
for (int ix=0;;ix++) {
|
||||
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
||||
pass?'A':'R',id,desc, pass?'4':'2');
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#endif
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||
@@ -151,9 +228,12 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
break;
|
||||
case 'P':
|
||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
TrackManager::setMainPower(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
/* TODO
|
||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
*/
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
@@ -192,24 +272,31 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
}
|
||||
break;
|
||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||
if (initSent) {
|
||||
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
|
||||
}
|
||||
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
|
||||
break;
|
||||
case 'M': // multithrottle
|
||||
multithrottle(stream, cmd);
|
||||
break;
|
||||
case 'H': // send initial connection info after receiving "HU" message
|
||||
if (cmd[1] == 'U') {
|
||||
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitWithrottleRoster(stream);
|
||||
#endif
|
||||
// set heartbeat to 1 second because we need to sync the metadata
|
||||
StringFormatter::send(stream,F("*1\n"));
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
stream->write('\n'); // end roster
|
||||
#endif
|
||||
|
||||
|
||||
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
|
||||
initSent = true;
|
||||
}
|
||||
break;
|
||||
@@ -231,11 +318,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
|
||||
int WiThrottle::getInt(byte * cmd) {
|
||||
int i=0;
|
||||
bool negate=cmd[0]=='-';
|
||||
if (negate) cmd++;
|
||||
while (cmd[0]>='0' && cmd[0]<='9') {
|
||||
i=i*10 + (cmd[0]-'0');
|
||||
cmd++;
|
||||
}
|
||||
return i;
|
||||
if (negate) i=0-i;
|
||||
return i ;
|
||||
}
|
||||
|
||||
int WiThrottle::getLocoId(byte * cmd) {
|
||||
@@ -381,9 +471,13 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
|
||||
case 'q':
|
||||
if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR
|
||||
// just flag the loco for broadcast and it will happen.
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
bool foundone = false;
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
foundone = true;
|
||||
myLocos[loco].broadcastPending=true;
|
||||
}
|
||||
}
|
||||
if (!foundone)
|
||||
StringFormatter::send(stream,F("HMCS loco list empty\n"));
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
@@ -391,7 +485,10 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
|
||||
bool forward=aval[1]!='0';
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
mostRecentCab=myLocos[loco].cab;
|
||||
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
|
||||
int8_t speed = DCC::getThrottleSpeed(myLocos[loco].cab);
|
||||
if (speed < 0) //can not find any speed for this cab
|
||||
speed = 0;
|
||||
DCC::setThrottle(myLocos[loco].cab, speed, forward);
|
||||
// setThrottle will cause a broadcast so notification will be sent
|
||||
}
|
||||
}
|
||||
@@ -444,9 +541,11 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
if (myLocos[loco].throttle!='\0') {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
||||
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
|
||||
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
|
||||
}
|
||||
}
|
||||
delete this;
|
||||
//haba no, not necessary the only throttle and it may come back
|
||||
//delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,27 +611,38 @@ byte WiThrottle::stashClient;
|
||||
char WiThrottle::stashThrottleChar;
|
||||
|
||||
void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
//DIAG(F("LocoCallback mark client %d"), stashClient);
|
||||
stashStream->mark(stashClient);
|
||||
|
||||
if (locoid<=0)
|
||||
if (locoid<=0) {
|
||||
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||
else {
|
||||
// short or long
|
||||
char addrchar;
|
||||
if (locoid & LONG_ADDR_MARKER) { // long addr
|
||||
locoid = locoid ^ LONG_ADDR_MARKER;
|
||||
addrchar = 'L';
|
||||
} else
|
||||
addrchar = 'S';
|
||||
if (addrchar == 'L' && locoid <= HIGHEST_SHORT_ADDR )
|
||||
StringFormatter::send(stashStream,F("HMLong addr %d <= %d not supported\n"), locoid,HIGHEST_SHORT_ADDR);
|
||||
else {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||
}
|
||||
//DIAG(F("LocoCallback commit (noloco)"));
|
||||
stashStream->commit(); // done here, commit and return
|
||||
return;
|
||||
}
|
||||
|
||||
// short or long
|
||||
char addrchar;
|
||||
if (locoid & LONG_ADDR_MARKER) { // maker bit indicates long addr
|
||||
locoid = locoid ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
||||
if (locoid <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
||||
StringFormatter::send(stashStream,F("HMLong addr %d <= %d unsupported\n"), locoid, HIGHEST_SHORT_ADDR);
|
||||
//DIAG(F("LocoCallback commit (error)"));
|
||||
stashStream->commit(); // done here, commit and return
|
||||
return;
|
||||
}
|
||||
addrchar = 'L';
|
||||
} else {
|
||||
addrchar = 'S';
|
||||
}
|
||||
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
//DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
|
||||
}
|
||||
|
@@ -37,7 +37,9 @@ class WiThrottle {
|
||||
void parse(RingStream * stream, byte * cmd);
|
||||
static WiThrottle* getThrottle( int wifiClient);
|
||||
static void markForBroadcast(int cab);
|
||||
|
||||
static void forget(byte clientId);
|
||||
static void findUniqThrottle(int id, char *u);
|
||||
|
||||
private:
|
||||
WiThrottle( int wifiClientId);
|
||||
~WiThrottle();
|
||||
@@ -54,6 +56,7 @@ class WiThrottle {
|
||||
bool areYouUsingThrottle(int cab);
|
||||
WiThrottle* nextThrottle;
|
||||
int clientid;
|
||||
char uniq[17] = "";
|
||||
|
||||
MYLOCO myLocos[MAX_MY_LOCO];
|
||||
bool heartBeatEnable;
|
||||
@@ -63,6 +66,7 @@ class WiThrottle {
|
||||
uint16_t mostRecentCab;
|
||||
int turnoutListHash; // used to check for changes to turnout list
|
||||
bool lastPowerState; // last power state sent to this client
|
||||
|
||||
int DCCToWiTSpeed(int DCCSpeed);
|
||||
int WiTToDCCSpeed(int WiTSpeed);
|
||||
void multithrottle(RingStream * stream, byte * cmd);
|
||||
|
360
WifiESP32.cpp
Normal file
360
WifiESP32.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
© 2021, Harald Barth.
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
This is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <vector>
|
||||
#include "defines.h"
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiESP32.h"
|
||||
#include "DIAG.h"
|
||||
#include "RingStream.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
/*
|
||||
#include "soc/rtc_wdt.h"
|
||||
#include "esp_task_wdt.h"
|
||||
*/
|
||||
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
void feedTheDog0(){
|
||||
// feed dog 0
|
||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
TIMERG0.wdt_feed=1; // feed dog
|
||||
TIMERG0.wdt_wprotect=0; // write protect
|
||||
// feed dog 1
|
||||
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
//TIMERG1.wdt_feed=1; // feed dog
|
||||
//TIMERG1.wdt_wprotect=0; // write protect
|
||||
}
|
||||
|
||||
/*
|
||||
void enableCoreWDT(byte core){
|
||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||
if(idle == NULL){
|
||||
DIAG(F("Get idle rask on core %d failed"),core);
|
||||
} else {
|
||||
if(esp_task_wdt_add(idle) != ESP_OK){
|
||||
DIAG(F("Failed to add Core %d IDLE task to WDT"),core);
|
||||
} else {
|
||||
DIAG(F("Added Core %d IDLE task to WDT"),core);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disableCoreWDT(byte core){
|
||||
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||
if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){
|
||||
DIAG(F("Failed to remove Core %d IDLE task from WDT"),core);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class NetworkClient {
|
||||
public:
|
||||
NetworkClient(WiFiClient c) {
|
||||
wifi = c;
|
||||
};
|
||||
bool ok() {
|
||||
return (inUse && wifi.connected());
|
||||
};
|
||||
bool recycle(WiFiClient c) {
|
||||
|
||||
if (inUse == true) return false;
|
||||
|
||||
// return false here until we have
|
||||
// implemented a LRU timer
|
||||
// if (LRU too recent) return false;
|
||||
return false;
|
||||
|
||||
wifi = c;
|
||||
inUse = true;
|
||||
return true;
|
||||
};
|
||||
WiFiClient wifi;
|
||||
bool inUse = true;
|
||||
};
|
||||
|
||||
static std::vector<NetworkClient> clients; // a list to hold all clients
|
||||
static WiFiServer *server = NULL;
|
||||
static RingStream *outboundRing = new RingStream(10240);
|
||||
static bool APmode = false;
|
||||
|
||||
#ifdef WIFI_TASK_ON_CORE0
|
||||
void wifiLoop(void *){
|
||||
for(;;){
|
||||
WifiESP::loop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WifiESP::setup(const char *SSid,
|
||||
const char *password,
|
||||
const char *hostname,
|
||||
int port,
|
||||
const byte channel) {
|
||||
bool havePassword = true;
|
||||
bool haveSSID = true;
|
||||
bool wifiUp = false;
|
||||
uint8_t tries = 40;
|
||||
|
||||
//#ifdef SERIAL_BT_COMMANDS
|
||||
//return false;
|
||||
//#endif
|
||||
|
||||
// tests
|
||||
// enableCoreWDT(1);
|
||||
// disableCoreWDT(0);
|
||||
|
||||
// clean start
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true);
|
||||
// differnet settings that did not improve for haba
|
||||
// WiFi.useStaticBuffers(true);
|
||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SECURITY);
|
||||
|
||||
const char *yourNetwork = "Your network ";
|
||||
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
||||
haveSSID = false;
|
||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||
havePassword = false;
|
||||
|
||||
if (haveSSID && havePassword) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
#else
|
||||
WiFi.setSleep(false);
|
||||
#endif
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.begin(SSid, password);
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
} else {
|
||||
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
||||
DIAG(F("Forcing one more Wifi restart"));
|
||||
esp_wifi_start();
|
||||
esp_wifi_connect();
|
||||
tries=40;
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
DIAG(F("Wifi STA IP 2nd try %s"),WiFi.localIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
} else {
|
||||
DIAG(F("Wifi STA mode FAIL. Will revert to AP mode"));
|
||||
haveSSID=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!haveSSID) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strPass("PASS_");
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
#else
|
||||
WiFi.setSleep(false);
|
||||
#endif
|
||||
if (WiFi.softAP(strSSID.c_str(),
|
||||
havePassword ? password : strPass.c_str(),
|
||||
channel, false, 8)) {
|
||||
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||
wifiUp = true;
|
||||
APmode = true;
|
||||
} else {
|
||||
DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!wifiUp) {
|
||||
DIAG(F("Wifi setup all fail (STA and AP mode)"));
|
||||
// no idea to go on
|
||||
return false;
|
||||
}
|
||||
server = new WiFiServer(port); // start listening on tcp port
|
||||
server->begin();
|
||||
// server started here
|
||||
|
||||
#ifdef WIFI_TASK_ON_CORE0
|
||||
//start loop task
|
||||
if (pdPASS != xTaskCreatePinnedToCore(
|
||||
wifiLoop, /* Task function. */
|
||||
"wifiLoop",/* name of task. */
|
||||
10000, /* Stack size of task */
|
||||
NULL, /* parameter of the task */
|
||||
1, /* priority of the task */
|
||||
NULL, /* Task handle to keep track of created task */
|
||||
0)) { /* pin task to core 0 */
|
||||
DIAG(F("Could not create wifiLoop task"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// report server started after wifiLoop creation
|
||||
// when everything looks good
|
||||
DIAG(F("Server starting (core 0) port %d"),port);
|
||||
#else
|
||||
DIAG(F("Server will be started on port %d"),port);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *wlerror[] = {
|
||||
"WL_IDLE_STATUS",
|
||||
"WL_NO_SSID_AVAIL",
|
||||
"WL_SCAN_COMPLETED",
|
||||
"WL_CONNECTED",
|
||||
"WL_CONNECT_FAILED",
|
||||
"WL_CONNECTION_LOST",
|
||||
"WL_DISCONNECTED"
|
||||
};
|
||||
|
||||
void WifiESP::loop() {
|
||||
int clientId; //tmp loop var
|
||||
|
||||
// really no good way to check for LISTEN especially in AP mode?
|
||||
wl_status_t wlStatus;
|
||||
if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) {
|
||||
// loop over all clients and remove inactive
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
// check if client is there and alive
|
||||
if(clients[clientId].inUse && !clients[clientId].wifi.connected()) {
|
||||
DIAG(F("Remove client %d"), clientId);
|
||||
CommandDistributor::forget(clientId);
|
||||
clients[clientId].wifi.stop();
|
||||
clients[clientId].inUse = false;
|
||||
//Do NOT clients.erase(clients.begin()+clientId) as
|
||||
//that would mix up clientIds for later.
|
||||
}
|
||||
}
|
||||
if (server->hasClient()) {
|
||||
WiFiClient client;
|
||||
while (client = server->available()) {
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
if (clients[clientId].recycle(client)) {
|
||||
DIAG(F("Recycle client %d %s"), clientId, client.remoteIP().toString().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clientId>=clients.size()) {
|
||||
NetworkClient nc(client);
|
||||
clients.push_back(nc);
|
||||
DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop over all connected clients
|
||||
for (clientId=0; clientId<clients.size(); clientId++){
|
||||
if(clients[clientId].ok()) {
|
||||
int len;
|
||||
if ((len = clients[clientId].wifi.available()) > 0) {
|
||||
// read data from client
|
||||
byte cmd[len+1];
|
||||
for(int i=0; i<len; i++) {
|
||||
cmd[i]=clients[clientId].wifi.read();
|
||||
}
|
||||
cmd[len]=0;
|
||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||
}
|
||||
}
|
||||
} // all clients
|
||||
|
||||
WiThrottle::loop(outboundRing);
|
||||
|
||||
// something to write out?
|
||||
clientId=outboundRing->read();
|
||||
if (clientId >= 0) {
|
||||
// We have data to send in outboundRing
|
||||
// and we have a valid clientId.
|
||||
// First read it out to buffer
|
||||
// and then look if it can be sent because
|
||||
// we can not leave it in the ring for ever
|
||||
int count=outboundRing->count();
|
||||
{
|
||||
char buffer[count+1]; // one extra for '\0'
|
||||
for(int i=0;i<count;i++) {
|
||||
int c = outboundRing->read();
|
||||
if (c >= 0) // Panic check, should never be false
|
||||
buffer[i] = (char)c;
|
||||
else {
|
||||
DIAG(F("Ringread fail at %d"),i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// buffer filled, end with '\0' so we can use it as C string
|
||||
buffer[count]='\0';
|
||||
if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) {
|
||||
if (Diag::CMD || Diag::WITHROTTLE)
|
||||
DIAG(F("SEND %d:%s"), clientId, buffer);
|
||||
clients[clientId].wifi.write(buffer,count);
|
||||
} else {
|
||||
DIAG(F("Unsent(%d): %s"), clientId, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!APmode) { // in STA mode but not connected any more
|
||||
// kick it again
|
||||
if (wlStatus <= 6) {
|
||||
DIAG(F("Wifi aborted with error %s. Kicking Wifi!"), wlerror[wlStatus]);
|
||||
esp_wifi_start();
|
||||
esp_wifi_connect();
|
||||
uint8_t tries=40;
|
||||
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||
Serial.print('.');
|
||||
tries--;
|
||||
delay(500);
|
||||
}
|
||||
} else {
|
||||
// all well, probably
|
||||
//DIAG(F("Running BT"));
|
||||
}
|
||||
}
|
||||
|
||||
// when loop() is running on core0 we must
|
||||
// feed the core0 wdt ourselves as yield()
|
||||
// is not necessarily yielding to a low
|
||||
// prio task. On core1 this is not a problem
|
||||
// as there the wdt is disabled by the
|
||||
// arduio IDE startup routines.
|
||||
if (xPortGetCoreID() == 0)
|
||||
feedTheDog0();
|
||||
yield();
|
||||
}
|
||||
#endif //ESP32
|
39
WifiESP32.h
Normal file
39
WifiESP32.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* © 2021, Harald Barth.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#ifndef WifiESP32_h
|
||||
#define WifiESP32_h
|
||||
|
||||
#include "FSH.h"
|
||||
|
||||
class WifiESP
|
||||
{
|
||||
|
||||
public:
|
||||
static bool setup(const char *wifiESSID,
|
||||
const char *wifiPassword,
|
||||
const char *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
#endif //WifiESP8266_h
|
||||
#endif //ESP8266
|
@@ -84,13 +84,7 @@ void WifiInboundHandler::loop1() {
|
||||
cmd[count]=0;
|
||||
if (Diag::WIFI) DIAG(F("%e"),cmd);
|
||||
|
||||
outboundRing->mark(clientId); // remember start of outbound data
|
||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||
// The commit call will either write the lenbgth bytes
|
||||
// OR rollback to the mark because the reply is empty or commend generated more than fits the buffer
|
||||
if (!outboundRing->commit()) {
|
||||
DIAG(F("OUTBOUND FULL processing cmd:%s"),cmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#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 <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
@@ -276,6 +276,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
checkForOK(2000, true);
|
||||
}
|
||||
}
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||
checkForOK(1000, true); // ignore result in case it already was off
|
||||
@@ -291,7 +292,6 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||
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(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
||||
@@ -381,7 +381,7 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
|
||||
int ch = wifiStream->read();
|
||||
if (echo) {
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else StringFormatter::diagSerial->print((char)ch);
|
||||
else USB_SERIAL.print((char)ch);
|
||||
}
|
||||
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||
if (ch == GETFLASH(locator)) {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "FSH.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
|
||||
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
|
||||
|
||||
|
@@ -29,7 +29,7 @@ The configuration file for DCC-EX Command Station
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: Before connecting these boards and selecting one in this software
|
||||
// check the quick install guides!!! Some of these boards require a voltage
|
||||
// generating resitor on the current sense pin of the device. Failure to select
|
||||
// generating resistor on the current sense pin of the device. Failure to select
|
||||
// the correct resistor could damage the sense pin on your Arduino or destroy
|
||||
// the device.
|
||||
//
|
||||
@@ -37,6 +37,7 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
@@ -85,7 +86,7 @@ The configuration file for DCC-EX Command Station
|
||||
// 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).
|
||||
// Your password may not contain ``"'' (double quote, ASCII 0x22).
|
||||
#define WIFI_PASSWORD "Your network passwd"
|
||||
//
|
||||
// WIFI_HOSTNAME: You probably don't need to change this
|
||||
@@ -125,10 +126,13 @@ The configuration file for DCC-EX Command Station
|
||||
|
||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||
// Also 132x64 I2C SH1106 devices
|
||||
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
|
||||
// #define OLED_DRIVER 128,32
|
||||
|
||||
// Define scroll mode as 0, 1 or 2
|
||||
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||
// * #define SCROLLMODE 1 is by page (alternate between pages),
|
||||
// * #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||
#define SCROLLMODE 1
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -137,7 +141,7 @@ The configuration file for DCC-EX Command Station
|
||||
// If you do not need the EEPROM at all, you can disable all the code that saves
|
||||
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
|
||||
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
|
||||
// to do that. Of course, then none of the EEPROM related commands works.
|
||||
// to do that. Of course, then none of the EEPROM related commands work.
|
||||
//
|
||||
// #define DISABLE_EEPROM
|
||||
|
||||
@@ -174,7 +178,7 @@ The configuration file for DCC-EX Command Station
|
||||
|
||||
// If you have issues with that the direction of the accessory commands is
|
||||
// reversed (for example when converting from another CS to DCC-EX) then
|
||||
// you can use this to revese the sense of all accessory commmands sent
|
||||
// you can use this to reverse the sense of all accessory commmands sent
|
||||
// over DCC++. This #define likewise inverts the behaviour of the <a> command
|
||||
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
||||
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||
@@ -192,5 +196,24 @@ The configuration file for DCC-EX Command Station
|
||||
//#define SERIAL1_COMMANDS
|
||||
//#define SERIAL2_COMMANDS
|
||||
//#define SERIAL3_COMMANDS
|
||||
//
|
||||
// BLUETOOTH SERIAL ON ESP32
|
||||
// On ESP32 you have the possibility to use the builtin BT serial to connect to
|
||||
// the CS.
|
||||
//
|
||||
// The CS shows up as a pairable BT Clasic device. Name is "DCCEX-hexnumber".
|
||||
// BT is as an additional serial port, debug messages are still sent over USB,
|
||||
// not BT serial.
|
||||
//
|
||||
// If you enable this there are some implications:
|
||||
// 1. WiFi will sleep more (as WiFi and BT share the radio. So WiFi performance
|
||||
// may suffer
|
||||
// 2. The app will be bigger that 1.2MB, so the default partition scheme will not
|
||||
// work any more. You need to choose a partition scheme with 2MB (or bigger).
|
||||
// For example "NO OTA (2MB APP, 2MB SPIFFS)" in the Arduino IDE.
|
||||
// 3. There is no securuity (PIN) implemented. Everyone in radio range can pair
|
||||
// with your CS.
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
121
defines.h
121
defines.h
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
@@ -24,7 +25,6 @@
|
||||
|
||||
#ifndef DEFINES_H
|
||||
#define DEFINES_H
|
||||
|
||||
// defines.h relies on macros defined in config.h
|
||||
// but it may have already been included (for cosmetic convenence) by the .ino
|
||||
#ifndef MOTOR_SHIELD_TYPE
|
||||
@@ -35,16 +35,123 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create a cpu type we can share and
|
||||
// figure out if we have enough memory for advanced features
|
||||
// so define HAS_ENOUGH_MEMORY until proved otherwise.
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#undef USB_SERIAL // Teensy has this defined by default...
|
||||
#define USB_SERIAL Serial
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_MEGA)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_TEENSY31)
|
||||
#define ARDUINO_TYPE "TEENSY3132"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// Teensy support for I2C is awaiting development
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// SAMD no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define ARDUINO_TYPE "STM32"
|
||||
// STM32 no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
#define ARDUINO_TYPE "RP2040"
|
||||
*/
|
||||
|
||||
#else
|
||||
#define CPU_TYPE_ERROR
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY)
|
||||
#define BIG_RAM
|
||||
#endif
|
||||
#if ENABLE_WIFI
|
||||
#if defined(BIG_RAM)
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#define WIFI_ON true
|
||||
#ifndef WIFI_CHANNEL
|
||||
#define WIFI_CHANNEL 1
|
||||
@@ -58,7 +165,7 @@
|
||||
#endif
|
||||
|
||||
#if ENABLE_ETHERNET
|
||||
#if defined(BIG_RAM)
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#define ETHERNET_WARNING
|
||||
@@ -80,7 +187,7 @@
|
||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
|
||||
#define EXRAIL_ACTIVE
|
||||
#else
|
||||
#define EXRAIL_WARNING
|
||||
|
112
freeMemory.cpp
112
freeMemory.cpp
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2020 Harald Barth
|
||||
*
|
||||
* This file is part of Asbelos DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "freeMemory.h"
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
#if defined(__arm__)
|
||||
extern "C" char* sbrk(int);
|
||||
#elif defined(__AVR__)
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
#else
|
||||
#error Unsupported board type
|
||||
#endif
|
||||
|
||||
|
||||
static volatile int minimum_free_memory = __INT_MAX__;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
#error bailed out already above
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
byte sreg_save = SREG;
|
||||
noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
//byte sreg_save = SREG;
|
||||
//noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
//SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
|
270
installer.json
Normal file
270
installer.json
Normal file
@@ -0,0 +1,270 @@
|
||||
[
|
||||
{
|
||||
"Name": "BaseStationClassic",
|
||||
"Git": "DCC-EX/BaseStation-Classic",
|
||||
"Libraries": [
|
||||
{
|
||||
"Name": "Ethernet",
|
||||
"Repo": "arduino-libraries/Ethernet",
|
||||
"Location": "libraries/Ethernet",
|
||||
"LibraryDownloadAvailable": true
|
||||
}
|
||||
],
|
||||
"SupportedBoards": [
|
||||
{
|
||||
"Name": "Uno",
|
||||
"FQBN": "arduino:avr:uno",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Mega",
|
||||
"FQBN": "arduino:avr:mega",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
}
|
||||
],
|
||||
"DisplayName": "Base Station Classic",
|
||||
"InputFileLocation": "DCCpp",
|
||||
"AllowAdvanced": false,
|
||||
"ConfigFile": "DCCpp/Config.h"
|
||||
},
|
||||
{
|
||||
"Name": "CommandStation-EX",
|
||||
"Git": "DCC-EX/CommandStation-EX",
|
||||
"Libraries": [
|
||||
{
|
||||
"Name": "Ethernet",
|
||||
"Repo": "arduino-libraries/Ethernet",
|
||||
"Location": "libraries/Ethernet",
|
||||
"LibraryDownloadAvailable": true
|
||||
},
|
||||
{
|
||||
"Name": "DIO2",
|
||||
"Repo": "",
|
||||
"Location": "libraries/DIO2",
|
||||
"LibraryDownloadAvailable": true
|
||||
}
|
||||
],
|
||||
"SupportedBoards": [
|
||||
{
|
||||
"Name": "Uno",
|
||||
"FQBN": "arduino:avr:uno",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Mega",
|
||||
"FQBN": "arduino:avr:mega",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Mega Wifi",
|
||||
"FQBN": "arduino:avr:mega",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Nano",
|
||||
"FQBN": "arduino:avr:nano",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Nano Every",
|
||||
"FQBN": "arduino:megaavr:nanoevery",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "megaavr",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Teensy 3.x",
|
||||
"FQBN": "teensy:avr:teensy31",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "teensy",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "Teensy 4.x",
|
||||
"FQBN": "teensy:avr:teensy41",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "teensy",
|
||||
"Package": "arduino"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
},
|
||||
{
|
||||
"Name": "SAMD21",
|
||||
"FQBN": "SparkFun:samd",
|
||||
"Platforms": [
|
||||
{
|
||||
"Architecture": "avr",
|
||||
"Package": "arduino"
|
||||
},
|
||||
{
|
||||
"Architecture": "samd",
|
||||
"Package": "arduino"
|
||||
},
|
||||
{
|
||||
"Architecture": "samd",
|
||||
"Package": "SparkFun"
|
||||
}
|
||||
],
|
||||
"SupportedMotoShields": [
|
||||
{
|
||||
"Name": "Arduino Motor Shield",
|
||||
"Declaration": "STANDARD_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "Pololu MC33926 Motor Shield",
|
||||
"Declaration": "POLOLU_MOTOR_SHIELD"
|
||||
},
|
||||
{
|
||||
"Name": "FireBox MK1",
|
||||
"Declaration": "FIREBOX_MK1"
|
||||
},
|
||||
{
|
||||
"Name": "FireBox MK1S",
|
||||
"Declaration": "FIREBOX_MK1S"
|
||||
}
|
||||
],
|
||||
"ExtraLibraries": []
|
||||
}
|
||||
],
|
||||
"DisplayName": "CommandStation EX",
|
||||
"InputFileLocation": "",
|
||||
"AllowAdvanced": true,
|
||||
"ConfigFile": "config.h"
|
||||
}
|
||||
]
|
@@ -1,19 +1,18 @@
|
||||
/* This is an automation example file.
|
||||
* The presence of a file called "myAutomation.h" brings EX-RAIL code into
|
||||
* the command station.
|
||||
* The auotomation may have multiple concurrent tasks.
|
||||
* The automation may have multiple concurrent tasks.
|
||||
* A task may
|
||||
* - Act as a ROUTE setup macro for a user to drive over
|
||||
* - drive a loco through an AUTOMATION
|
||||
* - automate some cosmetic part of the layout without any loco.
|
||||
*
|
||||
* At startup, a single task is created to execute the first
|
||||
* instruction after E$XRAIL.
|
||||
* At startup, a single task is created to execute the startup sequence.
|
||||
* This task may simply follow a route, or may START
|
||||
* further tasks (thats is.. send a loco out along a route).
|
||||
* further tasks (that is.. send a loco out along a route).
|
||||
*
|
||||
* Where the loco id is not known at compile time, a new task
|
||||
* can be creatd with the command:
|
||||
* can be created with the command:
|
||||
* </ START [cab] route>
|
||||
*
|
||||
* A ROUTE, AUTOMATION or SEQUENCE are internally identical in ExRail terms
|
||||
@@ -24,11 +23,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
EXRAIL // myAutomation must start with the EXRAIL instruction
|
||||
// This is the default starting route, AKA SEQUENCE(0)
|
||||
SENDLOCO(3,1) // send loco 3 off along route 1
|
||||
SENDLOCO(10,2) // send loco 10 off along route 2
|
||||
DONE // This just ends the startup thread, leaving 2 others running.
|
||||
// This is the startup sequence, AKA SEQUENCE(0)
|
||||
SENDLOCO(3,1) // send loco 3 off along route 1
|
||||
SENDLOCO(10,2) // send loco 10 off along route 2
|
||||
DONE // This just ends the startup thread, leaving 2 others running.
|
||||
|
||||
/* SEQUENCE(1) is a simple shuttle between 2 sensors
|
||||
* S20 and S21 are sensors on arduino pins 20 and 21
|
||||
@@ -78,7 +76,3 @@ EXRAIL // myAutomation must start with the EXRAIL instruction
|
||||
AT(33) STOP
|
||||
DELAY(20000) // wait 20 seconds
|
||||
FOLLOW(2) // follow sequence 2... ie repeat the process
|
||||
|
||||
ENDEXRAIL // marks the end of the EXRAIL program.
|
||||
|
||||
|
||||
|
192
myAutomation2.h
192
myAutomation2.h
@@ -1,192 +0,0 @@
|
||||
|
||||
/* This is an automation example file.
|
||||
* The presence of a file called "myAutomation.h" brings EX-RAIL code into
|
||||
* the command station.
|
||||
* The auotomation may have multiple concurrent tasks.
|
||||
* A task may drive one loco through a ROUTE or may simply
|
||||
* automate some other part of the layout without any loco.
|
||||
*
|
||||
* At startup, a single task is created to execute the first
|
||||
* instruction after ROUTES.
|
||||
* This task may simply follow a route, or may SCHEDULE
|
||||
* further tasks (thats is.. send a loco out along a route).
|
||||
*
|
||||
* Where the loco id is not known at compile time, a new task
|
||||
* can be creatd with the command:
|
||||
* </ SCHEDULE [cab] route>
|
||||
*
|
||||
*/
|
||||
|
||||
// Include the name to pin mappings for my layout
|
||||
#include "myLayout.h"
|
||||
|
||||
ALIAS(ROUTE_1,1)
|
||||
ALIAS(UP_MOUNTAIN,8)
|
||||
ALIAS(UP_MOUNTAIN_FROM_PROG,88)
|
||||
ALIAS(INNER_LOOP,7)
|
||||
ALIAS(INNER_FROM_PROG,77)
|
||||
|
||||
//EXRAIL // myAutomation must start with the EXRAIL instruction
|
||||
// This is the default starting route, AKA ROUTE(0)
|
||||
// START(999) // this is just a diagnostic test cycle
|
||||
PRINT("started")
|
||||
LCD(0,"EXRAIL RULES")
|
||||
SERIAL("I had one of them but the leg fell off!")
|
||||
DONE // This just ends the startup thread
|
||||
|
||||
|
||||
/*AUTOSTART*/ ROUTE(ROUTE_1,"Close All")
|
||||
LCD(1,"Bingo")
|
||||
CLOSE(TOP_TURNOUT) DELAY(10)
|
||||
CLOSE(Y_TURNOUT) DELAY(10)
|
||||
CLOSE(MIDDLE_TURNOUT) DELAY(10)
|
||||
CLOSE(JOIN_TURNOUT) DELAY(10)
|
||||
CLOSE(LOWER_TURNOUT) DELAY(10)
|
||||
CLOSE(CROSSOVER_TURNOUT) DELAY(10)
|
||||
CLOSE(PROG_TURNOUT) DELAY(10)
|
||||
PRINT("Close All completed")
|
||||
|
||||
ENDTASK
|
||||
|
||||
|
||||
SEQUENCE(UP_MOUNTAIN) // starting at the lower closed turnout siding and going up the mountain
|
||||
PRINT("Up Mountain started")
|
||||
DELAY(10000) // wait 10 seconds
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||
CLOSE(LOWER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||
FWD(60) AT(Y_LOWER)
|
||||
RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||
FREE(BLOCK_X_MOUNTAIN) FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
DELAY(10000)
|
||||
RESERVE(BLOCK_UPPER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(MIDDLE_TURNOUT) THROW(Y_TURNOUT) THROW(TOP_TURNOUT)
|
||||
REV(55)
|
||||
AFTER(Y_UPPER) FREE(BLOCK_X_MOUNTAIN)
|
||||
REV(55) AT(TOP_T_BUFFER) STOP // At top of mountain
|
||||
FREE(BLOCK_UPPER_MOUNTAIN)
|
||||
DELAY(5000)
|
||||
RESERVE(BLOCK_UPPER_MOUNTAIN)
|
||||
THROW(TOP_TURNOUT)
|
||||
FWD(60) AT(Y_UPPER)
|
||||
RESERVE(BLOCK_X_MOUNTAIN)
|
||||
THROW(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||
FREE(BLOCK_UPPER_MOUNTAIN) FREE(BLOCK_X_MOUNTAIN)
|
||||
DELAY(6000)
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(MIDDLE_TURNOUT) CLOSE(Y_TURNOUT) CLOSE(JOIN_TURNOUT) CLOSE(LOWER_TURNOUT)
|
||||
REV(60)
|
||||
AFTER(Y_LOWER) FREE(BLOCK_X_MOUNTAIN)
|
||||
AT(LOWER_C_BUFFER) STOP
|
||||
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
FOLLOW(UP_MOUNTAIN)
|
||||
|
||||
AUTOMATION(UP_MOUNTAIN_FROM_PROG,"Send up mountain from prog")
|
||||
JOIN
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||
RESERVE(BLOCK_X_INNER)
|
||||
RESERVE(BLOCK_X_OUTER)
|
||||
// safe to cross
|
||||
THROW(PROG_TURNOUT) THROW(CROSSOVER_TURNOUT) THROW(JOIN_TURNOUT)
|
||||
FWD(45)
|
||||
AFTER(JOIN_AFTER) STOP
|
||||
CLOSE(PROG_TURNOUT) CLOSE(CROSSOVER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||
FREE(BLOCK_X_OUTER) FREE(BLOCK_X_INNER)
|
||||
CLOSE(LOWER_TURNOUT)
|
||||
REV(40) AT(LOWER_C_BUFFER) STOP
|
||||
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
FOLLOW(UP_MOUNTAIN)
|
||||
|
||||
SEQUENCE(INNER_LOOP)
|
||||
FWD(50)
|
||||
AT(CROSSOVER_INNER_BEFORE)
|
||||
RESERVE(BLOCK_X_INNER)
|
||||
CLOSE(CROSSOVER_TURNOUT)
|
||||
FWD(50)
|
||||
AFTER(CROSSOVER_INNER_AFTER)
|
||||
FREE(BLOCK_X_INNER)
|
||||
FOLLOW(INNER_LOOP)
|
||||
|
||||
|
||||
// Turnout definitions
|
||||
TURNOUT(TOP_TURNOUT, TOP_TURNOUT,0,"Top Station")
|
||||
TURNOUT(Y_TURNOUT, Y_TURNOUT,0,"Mountain join")
|
||||
TURNOUT(MIDDLE_TURNOUT, MIDDLE_TURNOUT,0,"Middle Station")
|
||||
TURNOUT(JOIN_TURNOUT,JOIN_TURNOUT,0)
|
||||
TURNOUT(LOWER_TURNOUT,LOWER_TURNOUT,0)
|
||||
TURNOUT(CROSSOVER_TURNOUT,CROSSOVER_TURNOUT,0)
|
||||
TURNOUT(PROG_TURNOUT,PROG_TURNOUT,0)
|
||||
|
||||
// Single slip protection
|
||||
ONTHROW(2)
|
||||
THROW(1)
|
||||
DONE
|
||||
ONCLOSE(1)
|
||||
CLOSE(2)
|
||||
DONE
|
||||
|
||||
|
||||
ROUTE(61,"Call return test")
|
||||
PRINT("In 61 test 1")
|
||||
CALL(62)
|
||||
PRINT("In 61 test 2")
|
||||
CALL(62)
|
||||
PRINT("In 61 test 3")
|
||||
ACTIVATE(100,2)
|
||||
DEACTIVATE(100,2)
|
||||
DONE
|
||||
|
||||
SEQUENCE(62)
|
||||
PRINT("In seq 62")
|
||||
RETURN
|
||||
|
||||
ROUTE(63,"Signal test 40,41,42")
|
||||
SIGNAL(40,41,42)
|
||||
DELAY(2000)
|
||||
RED(40)
|
||||
DELAY(2000)
|
||||
AMBER(40)
|
||||
DELAY(2000)
|
||||
GREEN(40)
|
||||
FOLLOW(63)
|
||||
|
||||
|
||||
ROUTE(64,"Func test 6772")
|
||||
XFON(6772,1)
|
||||
DELAY(5000)
|
||||
XFOFF(6772,1)
|
||||
DELAY(5000)
|
||||
FOLLOW(64)
|
||||
|
||||
ROUTE(65,"Negative sensor test")
|
||||
PRINT(" WAIT for -176")
|
||||
AT(-176)
|
||||
PRINT(" WAIT for 176")
|
||||
AT(176)
|
||||
PRINT("done")
|
||||
DONE
|
||||
|
||||
ROUTE(123,"Activate stuff")
|
||||
ACTIVATEL(5)
|
||||
ACTIVATE(7,2)
|
||||
DEACTIVATE(3,2)
|
||||
DEACTIVATEL(6)
|
||||
DONE
|
||||
|
||||
ONACTIVATEL(5)
|
||||
PRINT("ACT 5")
|
||||
DONE
|
||||
ONACTIVATE(7,2)
|
||||
PRINT("ACT 7,2")
|
||||
DONE
|
||||
ONDEACTIVATE(7,2)
|
||||
PRINT("DEACT 7,2")
|
||||
DONE
|
||||
ONDEACTIVATEL(5)
|
||||
PRINT("DEACT 5")
|
||||
DONE
|
||||
|
||||
|
||||
|
101
myEX-Turntable.example.h
Normal file
101
myEX-Turntable.example.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**************************************************************************************************
|
||||
* This is an example automation file to control EX-Turntable using recommended techniques.
|
||||
**************************************************************************************************
|
||||
* INSTRUCTIONS
|
||||
**************************************************************************************************
|
||||
* To use this example file as the starting point for your layout, there are two options:
|
||||
*
|
||||
* 1. If you don't have an existing "myAutomation.h" file, simply rename "myEX-Turntable.example.h" to
|
||||
* "myAutomation.h".
|
||||
* 2. If you have an existing "myAutomation.h" file, rename "myEX-Turntable.example.h" to "myEX-Turntable.h",
|
||||
* and then include it by adding the line below at the end of your existing "myAutomation.h", on a
|
||||
* line of its own:
|
||||
*
|
||||
* #include "myEX-Turntable.h"
|
||||
*
|
||||
* Note that there are further instructions in the documentation at https://dcc-ex.com/.
|
||||
*************************************************************************************************/
|
||||
|
||||
/**************************************************************************************************
|
||||
* The MOVETT() command below will automatically move your turntable to the defined step position on
|
||||
* start up.
|
||||
*
|
||||
* If you do not wish this to occur, simply comment the line out.
|
||||
*
|
||||
* NOTE: If you are including this file at the end of an existing "myAutomation.h" file, you will likely
|
||||
* need to move this line to the beginning of your existing "myAutomation.h" file in order for it to
|
||||
* be effective.
|
||||
*************************************************************************************************/
|
||||
MOVETT(600, 114, Turn)
|
||||
DONE
|
||||
|
||||
// For Conductor level users who wish to just use EX-Turntable, you don't need to understand this
|
||||
// and can move to defining the turntable positions below. You must, however, ensure this remains
|
||||
// before any position definitions or you will get compile errors when uploading.
|
||||
//
|
||||
// Definition of the EX_TURNTABLE macro to correctly create the ROUTEs required for each position.
|
||||
// This includes RESERVE()/FREE() to protect any automation activities.
|
||||
//
|
||||
#define EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) \
|
||||
ROUTE(route_id, desc) \
|
||||
RESERVE(reserve_id) \
|
||||
MOVETT(vpin, steps, activity) \
|
||||
WAITFOR(vpin) \
|
||||
FREE(reserve_id) \
|
||||
DONE
|
||||
|
||||
/**************************************************************************************************
|
||||
* TURNTABLE POSITION DEFINITIONS
|
||||
*************************************************************************************************/
|
||||
// EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc)
|
||||
//
|
||||
// route_id = A unique number for each defined route, the route is what appears in throttles
|
||||
// reserve_id = A unique reservation number (0 - 255) to ensure nothing interferes with automation
|
||||
// vpin = The Vpin defined for the Turntable-EX device driver, default is 600
|
||||
// steps = The target step position
|
||||
// activity = The activity performed for this ROUTE (Note do not enclose in quotes "")
|
||||
// desc = Description that will appear in throttles (Must use quotes "")
|
||||
//
|
||||
EX_TURNTABLE(TTRoute1, Turntable, 600, 114, Turn, "Position 1")
|
||||
EX_TURNTABLE(TTRoute2, Turntable, 600, 227, Turn, "Position 2")
|
||||
EX_TURNTABLE(TTRoute3, Turntable, 600, 341, Turn, "Position 3")
|
||||
EX_TURNTABLE(TTRoute4, Turntable, 600, 2159, Turn, "Position 4")
|
||||
EX_TURNTABLE(TTRoute5, Turntable, 600, 2273, Turn, "Position 5")
|
||||
EX_TURNTABLE(TTRoute6, Turntable, 600, 2386, Turn, "Position 6")
|
||||
EX_TURNTABLE(TTRoute7, Turntable, 600, 0, Home, "Home turntable")
|
||||
|
||||
// Pre-defined aliases to ensure unique IDs are used.
|
||||
// Turntable reserve ID, valid is 0 - 255
|
||||
ALIAS(Turntable, 255)
|
||||
|
||||
// Turntable ROUTE ID reservations, using <? TTRouteX> for uniqueness:
|
||||
ALIAS(TTRoute1)
|
||||
ALIAS(TTRoute2)
|
||||
ALIAS(TTRoute3)
|
||||
ALIAS(TTRoute4)
|
||||
ALIAS(TTRoute5)
|
||||
ALIAS(TTRoute6)
|
||||
ALIAS(TTRoute7)
|
||||
ALIAS(TTRoute8)
|
||||
ALIAS(TTRoute9)
|
||||
ALIAS(TTRoute10)
|
||||
ALIAS(TTRoute11)
|
||||
ALIAS(TTRoute12)
|
||||
ALIAS(TTRoute13)
|
||||
ALIAS(TTRoute14)
|
||||
ALIAS(TTRoute15)
|
||||
ALIAS(TTRoute16)
|
||||
ALIAS(TTRoute17)
|
||||
ALIAS(TTRoute18)
|
||||
ALIAS(TTRoute19)
|
||||
ALIAS(TTRoute20)
|
||||
ALIAS(TTRoute21)
|
||||
ALIAS(TTRoute22)
|
||||
ALIAS(TTRoute23)
|
||||
ALIAS(TTRoute24)
|
||||
ALIAS(TTRoute25)
|
||||
ALIAS(TTRoute26)
|
||||
ALIAS(TTRoute27)
|
||||
ALIAS(TTRoute28)
|
||||
ALIAS(TTRoute29)
|
||||
ALIAS(TTRoute30)
|
@@ -2,7 +2,7 @@ ECHO ON
|
||||
FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i
|
||||
echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt
|
||||
SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf
|
||||
|
||||
set PATH="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\";%PATH%
|
||||
avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||
avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||
|
111
platformio.ini
111
platformio.ini
@@ -15,22 +15,52 @@ default_envs =
|
||||
mega328
|
||||
unowifiR2
|
||||
nano
|
||||
samd21-dev-usb
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-STM32F411RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
Teensy4.0
|
||||
Teensy4.1
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
|
||||
[env:samd21]
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
board = sparkfun_samd21_dev_usb
|
||||
framework = arduino
|
||||
upload_protocol = sam-ba
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:samd21-zero-usb]
|
||||
platform = atmelsam
|
||||
board = zeroUSB
|
||||
framework = arduino
|
||||
upload_protocol = sam-ba
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:samc21-firebox]
|
||||
platform = atmelsam
|
||||
board = firebox
|
||||
framework = arduino
|
||||
upload_protocol = atmel-ice
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
SparkFun External EEPROM Arduino Library
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -41,7 +71,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
build_flags = -DDIAG_IO -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
@@ -53,7 +83,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
@@ -65,7 +95,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
build_flags = -DI2C_USE_WIRE
|
||||
|
||||
[env:mega2560]
|
||||
@@ -77,7 +107,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -88,7 +118,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
|
||||
[env:unowifiR2]
|
||||
platform = atmelmegaavr
|
||||
@@ -99,7 +129,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"
|
||||
|
||||
[env:nanoevery]
|
||||
@@ -111,7 +141,7 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
upload_speed = 19200
|
||||
build_flags = -DDIAG_IO
|
||||
|
||||
@@ -124,14 +154,69 @@ lib_deps =
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
|
||||
[env:nano]
|
||||
platform = atmelavr
|
||||
board = nanoatmega328new
|
||||
board_upload.maximum_size = 32256
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
monitor_echo = yes
|
||||
|
||||
[env:ESP32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Nucleo-STM32F411RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Teensy3.2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.5]
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.6]
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.0]
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.1]
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
@@ -1,16 +1,88 @@
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production Release. Release v4.0.0 is a Major release that adds significant new product design, plus Automation features and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.2.0 to v3.2.0 rc13.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
|
||||
|
||||
#### Summary of key features and/or bug fixes by Point Release
|
||||
**All New Major DCC++EX 4.0.0 features**
|
||||
|
||||
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
|
||||
- HAL Support for;
|
||||
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
- PCA9685 PWM (servo & signal) control modules.
|
||||
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
- MP3 sound playback via DFPlayer module.
|
||||
- HC-SR04 Ultrasonic range sensor module.
|
||||
- VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
|
||||
|
||||
**New Command Station Broadcast throttle logic**
|
||||
|
||||
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
|
||||
|
||||
**New ‘Discovered Servers’ on WiFi Throttles**
|
||||
|
||||
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
|
||||
|
||||
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
|
||||
|
||||
- Use to control your entire layout or as a separate accessory/animation controller
|
||||
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
|
||||
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
|
||||
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
|
||||
|
||||
**New EX-RAIL ‘Roster’ Feature**
|
||||
|
||||
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
|
||||
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
|
||||
- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps.
|
||||
|
||||
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
|
||||
|
||||
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
|
||||
|
||||
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
|
||||
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information).
|
||||
|
||||
**DCC++EX 4.0.0 additional product enhancements**
|
||||
|
||||
- Additional Motor Shields and Motor Board {boosters) supported
|
||||
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
|
||||
- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI
|
||||
- Automatic retry on failed ACK detection to give decoders another chance
|
||||
- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts
|
||||
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
|
||||
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
|
||||
- Turnout user names display in Engine Driver & WiThrottles
|
||||
- Output class now allows ID > 255.
|
||||
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
|
||||
- Increased use of display for showing loco decoder programming information.
|
||||
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
|
||||
- Can define border between long and short addresses
|
||||
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
|
||||
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
|
||||
**4.0.0 Bug Fixes**
|
||||
|
||||
- Compiles on Nano Every
|
||||
- Diagnostic display of ack pulses >32ms
|
||||
- Current read from wrong ADC during interrupt
|
||||
- AT(+) Command Pass Through
|
||||
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
|
||||
- One-off error in CIPSEND drop
|
||||
- Common Fault Pin Error
|
||||
- Uno Memory Utilization optimized
|
||||
|
||||
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
@@ -154,7 +226,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd -
|
||||
- M Steve Todd - Oregon, USA (MSteveTodd)
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
@@ -174,6 +246,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Colin Grabham - Central NSW, Australia (Kebbin)
|
||||
|
||||
**WebThrotle-EX**
|
||||
|
||||
@@ -185,13 +258,14 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Brad Van der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sim Brigden
|
||||
- Sam Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
@@ -202,5 +276,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
83
version.h
83
version.h
@@ -3,32 +3,87 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "3.2.0 rc13"
|
||||
// 3.2.0 Major functional and non-functional changes.
|
||||
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
||||
|
||||
#define VERSION "4.2.4"
|
||||
// 4.2.4 ESP32 experimental BT support
|
||||
// More DC configurations possible and lower frequency
|
||||
// Handle decoders that do not ack at write better
|
||||
// 4.2.3 Bugfix direction when togging between MAIN and DC
|
||||
// Bugfix return fail when F/f argument out of range
|
||||
// More error checking for out of bounds motor driver current trip limit
|
||||
// 4.2.2 ESP32 beta
|
||||
// JOIN/UMJOIN on ESP32
|
||||
// 4.2.1 ESP32 alpha
|
||||
// Ready for alpha test on ESP32. Track switching with <=> untested
|
||||
// Send DCC signal on MAIN
|
||||
// Detects ACK on PROG
|
||||
// 4.2.0 Track Manager additions:
|
||||
// Broadcast improvements to separate <> and Withrottle responses
|
||||
// Float eliminated saving >1.5kb PROGMEM and speed.
|
||||
// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF)
|
||||
// New DC track function and DCX reverse polarity function
|
||||
// TrackManager DCC & DC up to 8 Districts Architecture
|
||||
// Automatic ALIAS(name)
|
||||
// Command Parser now accepts Underscore in Alias Names
|
||||
// 4.1.1 Bugfix: preserve turnout format
|
||||
// Bugfix: parse multiple commands in one buffer string correct
|
||||
// Bugfix: </> command signal status in Exrail
|
||||
// 4.1.0 ...
|
||||
//
|
||||
// 4.0.2 EXRAIL additions:
|
||||
// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX
|
||||
// myFilter automatic detection (no need to call setFilter)
|
||||
// FIX negative route ids in WIthrottle problem.
|
||||
// IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
// </RED signal_id> </AMBER signal_id> </GREEN signal_id> commands
|
||||
// <t cab> command to obtain current throttle settings
|
||||
// JA, JR, JT commands to obtain route, roster and turnout descriptions
|
||||
// HIDDEN turnouts
|
||||
// PARSE <> commands in EXRAIL
|
||||
// VIRTUAL_TURNOUT
|
||||
// </KILL ALL> and KILLALL command to stop all tasks.
|
||||
// FORGET forgets the current loco in DCC reminder tables.
|
||||
// Servo signals (SERVO_SIGNAL)
|
||||
// High-On signal pins (SIGNALH)
|
||||
// Wait for analog value (ATGTE, ATLT)
|
||||
// 4.0.1 Small EXRAIL updates
|
||||
// EXRAIL BROADCAST("msg")
|
||||
// EXRAIL POWERON
|
||||
// 4.0.0 Major functional and non-functional changes.
|
||||
// Engine Driver "DriveAway" feature enhancement
|
||||
// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station
|
||||
// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability.
|
||||
// EX-Rail Function commands for creating Automation, Route & Sequence Scripts
|
||||
// EX-RAIL “ROSTER” Engines Id & Function key layout on Engine Driver or WiThrottle
|
||||
// EX-RAIL DCC++EX Commands to Control EX-RAIL via JMRI Send pane and IDE Serial monitors
|
||||
// New JMRI feature enhancements;
|
||||
// Reads DCC++EX EEPROM & automatically uploades any Signals, DCC Turnouts, Servo Turnouts, Vpin Turnouts , & Output pane
|
||||
// Turnout class revised to expand turnout capabilities, new commands added.
|
||||
// Provides for multiple additional DCC++EX WiFi connections as accessory controllers or CS for a programming track when Motor Shields are added
|
||||
// Supports Multiple Command Station connections and individual tracking of Send DCC++ Command panes and DCC++ Traffic Monitor panes
|
||||
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc)
|
||||
// Automatically detects & connects to supported devices included in your config.h file
|
||||
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
// Support for PCA9685 PWM (servo) control modules.
|
||||
// Support for analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
// Support for MP3 sound playback via DFPlayer module.
|
||||
// Support for HC-SR04 Ultrasonic range sensor module.
|
||||
// Support for VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
// Native non-blocking I2C drivers for AVR and Nano architectures (fallback
|
||||
// to blocking Wire library for other platforms).
|
||||
// EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
// New EX-RAIL automation capability.
|
||||
// Turnout class revised to expand turnout capabilities, new commands added.
|
||||
// Output class now allows ID > 255.
|
||||
// Configuration options to globally flip polarity of DCC Accessory states when driven
|
||||
// from <a> command and <T> command.
|
||||
// Increased use of display for showing loco decoder programming information.
|
||||
// Added <D HAL SHOW> diagnostic command to show configured devices
|
||||
// New Processor Support added
|
||||
// Compiles on Nano Every and Teensy
|
||||
// Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
|
||||
// Can disable EEPROM code
|
||||
// EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
// Output class now allows ID > 255.
|
||||
// Configuration options to globally flip polarity of DCC Accessory states when driven from <a> command and <T> command.
|
||||
// Increased use of display for showing loco decoder programming information.
|
||||
// Can define border between long and short addresses
|
||||
// Turnout and accessory states (thrown/closed = 0/1 or 1/0) can be set to match RCN-213
|
||||
// Bugfix: one-off error in CIPSEND drop
|
||||
// Compiles on Nano Every
|
||||
// Bugfix: disgnostic display of ack pulses >32kus
|
||||
// Bugfix: Current read from wrong ADC during interrupt
|
||||
// ...
|
||||
// 3.2.0 Development Release Includes all of 3.1.1 thru 3.1.7 enhancements
|
||||
// 3.1.7 Bugfix: Unknown locos should have speed forward
|
||||
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM
|
||||
// 3.1.5 Fix LCD corruption on power-up
|
||||
|
Reference in New Issue
Block a user