mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 02:43:45 +02:00
Compare commits
324 Commits
TrackManag
...
devel-rot
Author | SHA1 | Date | |
---|---|---|---|
|
f9e08b1283 | ||
|
ee4963dfca | ||
|
5e2b416c30 | ||
|
eed1237b9f | ||
|
07f1d6fc20 | ||
|
45504db1ad | ||
|
d60a55091f | ||
|
90897ff2d1 | ||
|
88c7e540fa | ||
|
888165e987 | ||
|
57aa1457e0 | ||
|
1a67930af4 | ||
|
604a69e0a8 | ||
|
f56a9a2c43 | ||
|
56ed6ab6dc | ||
|
9afce0a7df | ||
|
e8180603ba | ||
|
f792e07d40 | ||
|
2a51fa6f5d | ||
|
1c73a3d7bf | ||
|
050eb52a22 | ||
|
81b5b25430 | ||
|
f39a9d1510 | ||
|
578cbd08e5 | ||
|
cf89fe2a72 | ||
|
65364212ca | ||
|
eb766aa27f | ||
|
4d205be007 | ||
|
208905e7b9 | ||
|
c36234df73 | ||
|
452ffc5725 | ||
|
08c114fd22 | ||
|
e0bf978f2b | ||
|
3e214ab77a | ||
|
ee568fcd11 | ||
|
367d2dfe20 | ||
|
b7295c4923 | ||
|
5e616a9eb2 | ||
|
24e5e648b8 | ||
|
24a7475482 | ||
|
c47e9b79ca | ||
|
052f5807f0 | ||
|
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 | ||
|
712ed0674d | ||
|
1d36b03e7a | ||
|
c2d7e7169a | ||
|
661d042744 | ||
|
5f568c05b9 | ||
|
ebebd0dc11 | ||
|
506b65d0ea | ||
|
dd58e2c462 | ||
|
6135272c32 | ||
|
a1a2c9ce5b | ||
|
3c01bd9012 | ||
|
d2fa44eec7 | ||
|
632d777fe7 | ||
|
ff73a60874 | ||
|
f8a19de9fb | ||
|
357560b226 | ||
|
589336eac3 | ||
|
cb365579d8 | ||
|
e13afd064d | ||
|
2d37947246 | ||
|
9367d708f7 | ||
|
a614a616fa | ||
|
2fd7a31ae4 | ||
|
977802f160 | ||
|
8fa1ba3039 | ||
|
a52551babe | ||
|
084ddf01e1 | ||
|
083c5b5cd3 | ||
|
63c9ca414d | ||
|
46e4dc2628 | ||
|
5ccef35074 | ||
|
7312951b2b | ||
|
5dfc014f49 | ||
|
b847419a55 | ||
|
cd0dfc565c | ||
|
f878c1d01c | ||
|
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 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -8,12 +8,14 @@ Release/*
|
||||
.vscode/
|
||||
config.h
|
||||
.vscode/*
|
||||
mySetup.h
|
||||
# mySetup.h
|
||||
mySetup.cpp
|
||||
myHal.cpp
|
||||
myAutomation.h
|
||||
# myAutomation.h
|
||||
myFilter.cpp
|
||||
myAutomation.h
|
||||
myFilter.cpp
|
||||
myLayout.h
|
||||
# myAutomation.h
|
||||
# myLayout.h
|
||||
my*.h
|
||||
!my*.example.h
|
||||
.vscode/extensions.json
|
||||
.vscode/extensions.json
|
||||
|
@@ -30,91 +30,135 @@
|
||||
#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
|
||||
}
|
||||
@@ -130,14 +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) {
|
||||
StringFormatter::send(broadcastBufferWriter,F("%S"),msg);
|
||||
broadcast(false);
|
||||
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||
#endif
|
||||
}
|
||||
|
@@ -23,9 +23,23 @@
|
||||
#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);
|
||||
@@ -33,16 +47,8 @@ public :
|
||||
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,15 +83,20 @@ 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
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
@@ -101,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();
|
||||
}
|
||||
|
||||
@@ -127,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
|
||||
|
48
DCC.cpp
48
DCC.cpp
@@ -62,15 +62,15 @@ byte DCC::globalSpeedsteps=128;
|
||||
|
||||
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));
|
||||
|
||||
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
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32 /* On ESP32 started in TrackManager::setTrackMode() */
|
||||
DCCWaveform::begin();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -163,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
|
||||
@@ -183,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
|
||||
@@ -202,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
|
||||
@@ -496,8 +498,8 @@ 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,
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
@@ -514,14 +516,18 @@ 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
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
@@ -588,9 +594,10 @@ void DCC::loop() {
|
||||
|
||||
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;
|
||||
@@ -601,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) {
|
||||
|
2
DCC.h
2
DCC.h
@@ -62,7 +62,7 @@ public:
|
||||
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);
|
||||
|
29
DCCACK.cpp
29
DCCACK.cpp
@@ -67,23 +67,24 @@ 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) {
|
||||
callback(-3); // we dont have a prog track!
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
progDriver->setResetCounterPointer(&(DCCWaveform::progTrack.sentResetsSincePacket));
|
||||
|
||||
ackManagerRejoin=TrackManager::isJoined();
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
TrackManager::setJoin(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
autoPowerOff=false;
|
||||
if (progDriver->getPower() == POWERMODE::OFF) {
|
||||
@@ -94,7 +95,7 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C
|
||||
/* TODO !!! in MotorDriver surely!
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
**/
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ 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.sentResetsSincePacket < numResets;
|
||||
return DCCWaveform::progTrack.getResets() < numResets;
|
||||
}
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
@@ -424,7 +425,7 @@ void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
return;
|
||||
}
|
||||
|
||||
int current=progDriver->getCurrentRawInInterrupt();
|
||||
int current=progDriver->getCurrentRaw(true); // true means "from interrupt"
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
4
DCCEX.h
4
DCCEX.h
@@ -32,7 +32,11 @@
|
||||
#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
|
||||
|
122
DCCEXParser.cpp
122
DCCEXParser.cpp
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Herb Morton
|
||||
@@ -39,9 +40,7 @@
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
#ifdef HAS_AVR_WDT
|
||||
#include <avr/wdt.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
@@ -68,6 +67,7 @@ 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_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
@@ -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
|
||||
@@ -294,16 +308,17 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
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
|
||||
|| ((onoff & 0x01) != onoff) // invalid onoff 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,onoff);
|
||||
@@ -340,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++) {
|
||||
@@ -530,8 +546,8 @@ 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)
|
||||
@@ -673,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;
|
||||
}
|
||||
|
||||
//===================================
|
||||
@@ -886,15 +898,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESET:
|
||||
{
|
||||
#ifdef HAS_AVR_WDT
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
#else
|
||||
ESP.restart();
|
||||
#endif
|
||||
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>
|
||||
@@ -929,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;
|
||||
}
|
||||
|
@@ -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);
|
||||
@@ -70,7 +71,7 @@ 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[]);
|
||||
|
||||
};
|
||||
|
235
DCCRMT.cpp
Normal file
235
DCCRMT.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* © 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 "DCCTimer.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();
|
||||
if (channel == 0)
|
||||
DCCTimer::updateMinimumFreeMemoryISR(0);
|
||||
}
|
||||
|
||||
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
|
93
DCCTimer.h
93
DCCTimer.h
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -70,58 +71,60 @@ class DCCTimer {
|
||||
// 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) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
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
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create a cpu type we can share and
|
||||
// gigure out if we have enough memory for advanced features
|
||||
// so define HAS_ENOUGH_MEMORY until proved otherwise.
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#define HAS_AVR_WDT
|
||||
|
||||
#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"
|
||||
#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"
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#undef HAS_AVR_WDT
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#undef HAS_AVR_WDT
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h
|
||||
#endif
|
||||
// Class ADCee implements caching of the ADC value for platforms which
|
||||
// have a too slow ADC read to wait for. On these platforms the ADC is
|
||||
// scanned continiously in the background from an ISR. On such
|
||||
// architectures that use the analog read during DCC waveform with
|
||||
// specially configured ADC, for example AVR, init must be called
|
||||
// PRIOR to the start of the waveform. It returns the current value so
|
||||
// that an offset can be initialized.
|
||||
class ADCee {
|
||||
public:
|
||||
// init does add the pin to the list of scanned pins (if this
|
||||
// platform's implementation scans pins) and returns the first
|
||||
// read value. It is called before the regular scan is started.
|
||||
static int init(uint8_t pin);
|
||||
// read does read the pin value from the scanned cache or directly
|
||||
// if this is a platform that does not scan. fromISR is a hint if
|
||||
// it was called from ISR because for some implementations that
|
||||
// makes a difference.
|
||||
static int read(uint8_t pin, bool fromISR=false);
|
||||
// returns possible max value that the ADC can return
|
||||
static int16_t ADCmax();
|
||||
private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
static int *analogvals;
|
||||
// friend so that we can call scan() and begin()
|
||||
friend class DCCWaveform;
|
||||
};
|
||||
#endif
|
||||
|
116
DCCTimerAVR.cpp
116
DCCTimerAVR.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
@@ -26,8 +26,8 @@
|
||||
// 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;
|
||||
|
||||
@@ -43,8 +43,7 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
noInterrupts();
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
@@ -114,4 +113,113 @@ int DCCTimer::freeMemory() {
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 7
|
||||
#else
|
||||
#define NUM_ADC_INPUTS 15
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
|
||||
/*
|
||||
* Register a new pin to be scanned
|
||||
* Returns current reading of pin and
|
||||
* stores that as well
|
||||
*/
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint8_t id = pin - A0;
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
pinMode(pin, INPUT);
|
||||
int value = analogRead(pin);
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 1023;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
(void)fromISR; // AVR does ignore this arg
|
||||
uint8_t id = pin - A0;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static byte id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
byte low, high;
|
||||
if (bit_is_set(ADCSRA, ADSC))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
analogvals[id] = (high << 8) | low;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADMUX=(1<<REFS0)|id; //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
// Set up ADC for free running mode
|
||||
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
|
||||
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
|
||||
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
|
@@ -78,6 +78,23 @@ int DCCTimer::freeMemory() {
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#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;
|
||||
}
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
@@ -87,6 +104,8 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
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
|
||||
@@ -101,6 +120,8 @@ bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
}
|
||||
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]) {
|
||||
@@ -125,5 +146,33 @@ int DCCTimer::getMinimumFreeMemory() {
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
||||
void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
int ADCee::init(uint8_t pin) {
|
||||
pinMode(pin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
|
||||
return adc1_get_raw(pinToADC1Channel(pin));
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
return local_adc1_get_raw(pinToADC1Channel(pin));
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
}
|
||||
|
||||
#endif //ESP32
|
||||
|
||||
|
@@ -75,7 +75,7 @@ extern char *__malloc_heap_start;
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TCB0_INT_vect){
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm;
|
||||
TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
@@ -118,4 +118,11 @@ int DCCTimer::freeMemory() {
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
void DCCTimer::reset() {
|
||||
CPU_CCP=0xD8;
|
||||
WDT.CTRLA=0x4;
|
||||
while(true){}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
292
DCCTimerSAMD.cpp
Normal file
292
DCCTimerSAMD.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 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 "DCCTimer.h"
|
||||
#include <wiring_private.h>
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
// 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
|
||||
int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority
|
||||
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority
|
||||
// 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) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
int value = 0;
|
||||
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
|
||||
// Dummy read using Arduino library
|
||||
analogReadResolution(12);
|
||||
value = analogRead(pin);
|
||||
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
// Permanently configure SAMD IO MUX for that pin
|
||||
pinPeripheral(pin, PIO_ANALOG);
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
|
||||
// Start conversion
|
||||
ADC->SWTRIG.bit.START = 1;
|
||||
|
||||
// Wait for the conversion to be ready
|
||||
while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete
|
||||
|
||||
// Read the value
|
||||
value = ADC->RESULT.reg;
|
||||
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
uint8_t id = pin - A0;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
if (ADC->INTFLAG.bit.RESRDY == 0)
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC->RESULT.reg;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
// Start conversion
|
||||
ADC->SWTRIG.bit.START = 1;
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
|
||||
// and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables
|
||||
// 12-bit mode
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
162
DCCTimerSTM32.cpp
Normal file
162
DCCTimerSTM32.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* © 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
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable
|
||||
// for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the F411RE)
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(STM32F446ZE)
|
||||
// STM32F446ZE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
|
||||
#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) {};
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(pin);
|
||||
}
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
void ADCee::scan() {
|
||||
}
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -88,8 +89,20 @@ void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
}
|
||||
#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__)
|
||||
static inline int freeMemory() {
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
}
|
||||
@@ -110,7 +123,7 @@ static inline int freeMemory() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
int DCCTimer::freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
@@ -123,4 +136,9 @@ static inline int freeMemory() {
|
||||
}
|
||||
|
||||
#endif
|
||||
void DCCTimer::reset() {
|
||||
// found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
108
DCCWaveform.cpp
108
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.
|
||||
*
|
||||
@@ -62,6 +62,7 @@ const bool signalTransform[]={
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
ADCee::begin();
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
@@ -80,19 +81,21 @@ void DCCWaveform::interruptHandler() {
|
||||
// Set the signal state for both tracks
|
||||
TrackManager::setDCCSignal(sigMain);
|
||||
TrackManager::setPROGSignal(sigProg);
|
||||
|
||||
|
||||
// Refresh the values in the ADCee object buffering the values of the ADC HW
|
||||
ADCee::scan();
|
||||
|
||||
// Move on in the state engine
|
||||
mainTrack.state=stateTransform[mainTrack.state];
|
||||
progTrack.state=stateTransform[progTrack.state];
|
||||
|
||||
|
||||
// WAVE_PENDING means we dont yet know what the next bit is
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||
else DCCACK::checkAck(progTrack.sentResetsSincePacket);
|
||||
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.
|
||||
@@ -115,7 +118,7 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
@@ -160,18 +163,19 @@ 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) {
|
||||
@@ -188,6 +192,90 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
packetPending = true;
|
||||
sentResetsSincePacket=0;
|
||||
clearResets();
|
||||
}
|
||||
#endif
|
||||
bool DCCWaveform::getPacketPending() {
|
||||
return packetPending;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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;
|
||||
}
|
||||
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 */
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@@ -25,6 +25,10 @@
|
||||
#define DCCWaveform_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "DCCRMT.h"
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -49,12 +53,38 @@ class DCCWaveform {
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
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);
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
bool getPacketPending();
|
||||
|
||||
private:
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
#else
|
||||
volatile uint32_t resetPacketBase;
|
||||
#endif
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
|
||||
@@ -71,6 +101,9 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
|
||||
#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
|
26
ESP32-fixes.h
Normal file
26
ESP32-fixes.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
#endif
|
||||
|
260
EXRAIL2.cpp
260
EXRAIL2.cpp
@@ -87,6 +87,10 @@ 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;
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
|
||||
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
|
||||
@@ -116,65 +120,51 @@ 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);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED);
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
|
||||
int progCounter;
|
||||
for (progCounter=0;; SKIPOP){
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
@@ -182,6 +172,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: {
|
||||
@@ -191,6 +182,15 @@ int16_t LookList::find(int16_t value) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_ATGTE:
|
||||
case OPCODE_ATLT:
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
case OPCODE_DRIVE: {
|
||||
IODevice::configureAnalogIn((VPIN)operand);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_TURNOUT: {
|
||||
VPIN id=operand;
|
||||
int addr=GET_OPERAND(1);
|
||||
@@ -215,32 +215,11 @@ int16_t LookList::find(int16_t value) {
|
||||
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
|
||||
@@ -249,9 +228,7 @@ 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
|
||||
}
|
||||
@@ -320,12 +297,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;
|
||||
}
|
||||
@@ -372,13 +360,14 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter 0 to MAX_FLAGS-1
|
||||
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
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]) {
|
||||
task->kill(F("KILL"));
|
||||
@@ -391,20 +380,16 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
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 true;
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
@@ -450,7 +435,7 @@ RMFT2::RMFT2(int progCtr) {
|
||||
invert=false;
|
||||
timeoutFlag=false;
|
||||
stackDepth=0;
|
||||
onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE
|
||||
onEventStartPosition=-1; // Not handling an ONxxx
|
||||
|
||||
// chain into ring of RMFTs
|
||||
if (loopTask==NULL) {
|
||||
@@ -544,7 +529,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() {
|
||||
@@ -730,9 +723,13 @@ void RMFT2::loop2() {
|
||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||
skipIf=readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFRE: // do next operand if rotary encoder != position
|
||||
skipIf=IODevice::readAnalogue(operand)!=(int)(GET_OPERAND(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
skipIf=(int16_t)random(100)>=operand;
|
||||
skipIf=(int16_t)(micros()%100) >= operand;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||
@@ -776,7 +773,7 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_RANDWAIT:
|
||||
delayMe(random(operand)*100L);
|
||||
delayMe(operand==0 ? 0 : (micros()%operand) *100L);
|
||||
break;
|
||||
|
||||
case OPCODE_RED:
|
||||
@@ -950,6 +947,11 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONTHROW:
|
||||
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -966,12 +968,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) {
|
||||
@@ -985,9 +988,9 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
||||
delete this;
|
||||
}
|
||||
|
||||
int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) { // end of signal list
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return -1;
|
||||
@@ -996,12 +999,19 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
// 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 & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking
|
||||
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
/* static */ void RMFT2::doSignal(VPIN id,char rag) {
|
||||
/* 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;
|
||||
|
||||
@@ -1014,9 +1024,11 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
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"),sigid,redpin,amberpin,greenpin);
|
||||
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
|
||||
if (sigid & SERVO_SIGNAL_FLAG) {
|
||||
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);
|
||||
@@ -1025,7 +1037,14 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LED or similar 3 pin signal
|
||||
|
||||
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
|
||||
@@ -1037,10 +1056,9 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(VPIN id,char rag) {
|
||||
/* 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;
|
||||
@@ -1048,46 +1066,40 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
|
||||
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::changeEvent(int16_t vpin, bool change) {
|
||||
// Hunt for an ONCHANGE for this sensor
|
||||
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
29
EXRAIL2.h
29
EXRAIL2.h
@@ -53,6 +53,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -63,7 +65,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_IFTIMEOUT,
|
||||
OPCODE_IF,OPCODE_IFNOT,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED, OPCODE_IFTHROWN
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
};
|
||||
|
||||
|
||||
@@ -106,8 +109,11 @@ class LookList {
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static void changeEvent(int16_t id, bool change);
|
||||
static 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;
|
||||
@@ -124,13 +130,16 @@ 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,char rag);
|
||||
static bool isSignal(VPIN id,char rag);
|
||||
static int16_t getSignalSlot(VPIN id);
|
||||
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);
|
||||
@@ -143,7 +152,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[];
|
||||
@@ -153,7 +161,11 @@ private:
|
||||
static LookList * onCloseLookup;
|
||||
static LookList * onActivateLookup;
|
||||
static LookList * onDeactivateLookup;
|
||||
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
static LookList * onChangeLookup;
|
||||
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
@@ -171,8 +183,7 @@ private:
|
||||
bool forward;
|
||||
bool invert;
|
||||
byte speedo;
|
||||
int16_t onTurnoutId;
|
||||
int16_t onActivateAddr;
|
||||
int onEventStartPosition;
|
||||
byte stackDepth;
|
||||
int callStack[MAX_STACK_DEPTH];
|
||||
};
|
||||
|
@@ -37,6 +37,7 @@
|
||||
#undef BROADCAST
|
||||
#undef CALL
|
||||
#undef CLOSE
|
||||
#undef DCC_SIGNAL
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
#undef DELAY
|
||||
@@ -58,6 +59,7 @@
|
||||
#undef FREE
|
||||
#undef FWD
|
||||
#undef GREEN
|
||||
#undef HAL
|
||||
#undef IF
|
||||
#undef IFAMBER
|
||||
#undef IFCLOSED
|
||||
@@ -70,18 +72,24 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFRE
|
||||
#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 ONCHANGE
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
@@ -120,6 +128,7 @@
|
||||
#undef TURNOUT
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#undef XFOFF
|
||||
@@ -140,6 +149,7 @@
|
||||
#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)
|
||||
@@ -161,6 +171,7 @@
|
||||
#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)
|
||||
@@ -173,18 +184,24 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFRE(sensor_id,value)
|
||||
#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 ONCHANGE(sensor_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
@@ -223,6 +240,7 @@
|
||||
#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)
|
||||
|
@@ -61,6 +61,14 @@
|
||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
|
||||
// Pass 2 create throttle route list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
@@ -185,6 +193,11 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#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 };
|
||||
@@ -218,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,
|
||||
@@ -234,6 +248,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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),
|
||||
@@ -246,18 +261,24 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#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 ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
@@ -296,6 +317,7 @@ 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),
|
||||
|
@@ -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();
|
||||
|
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_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte(addr)
|
||||
#define GETFLASHW(addr) (*(const unsigned int8_t *)(addr)) | ((*(const unsigned int8_t *)(addr+1)) << 8)
|
||||
#ifdef FLASH
|
||||
#undef FLASH
|
||||
#endif
|
||||
#define FLASH PROGMEM
|
||||
#else
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "TM-PORTX-20220607-1"
|
||||
#define GITHUB_SHA "devel-202210311845Z"
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
244
I2CManager_SAMD.h
Normal file
244
I2CManager_SAMD.h
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* © 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, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities
|
||||
// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest 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 */
|
109
IODevice.cpp
109
IODevice.cpp
@@ -25,6 +25,7 @@
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define USE_FAST_IO
|
||||
@@ -32,7 +33,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 +48,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 +78,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
|
||||
@@ -191,7 +196,17 @@ int IODevice::readAnalogue(VPIN vpin) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
return -1023;
|
||||
}
|
||||
int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
if (dev->owns(vpin))
|
||||
return dev->_configureAnalogIn(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
|
||||
// Write value to virtual pin(s). If multiple devices are allocated the same pin
|
||||
@@ -274,7 +289,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
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
@@ -328,11 +372,10 @@ int IODevice::read(VPIN vpin) {
|
||||
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
|
||||
}
|
||||
int IODevice::readAnalogue(VPIN vpin) {
|
||||
pinMode(vpin, INPUT);
|
||||
noInterrupts();
|
||||
int value = analogRead(vpin);
|
||||
interrupts();
|
||||
return value;
|
||||
return ADCee::read(vpin);
|
||||
}
|
||||
int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
return ADCee::init(vpin);
|
||||
}
|
||||
void IODevice::loop() {}
|
||||
void IODevice::DumpAll() {
|
||||
@@ -434,7 +477,18 @@ int ArduinoPins::_read(VPIN vpin) {
|
||||
|
||||
// Device-specific readAnalogue function (analogue input)
|
||||
int ArduinoPins::_readAnalogue(VPIN vpin) {
|
||||
int pin = vpin;
|
||||
if (vpin > 255) return -1023;
|
||||
uint8_t pin = vpin;
|
||||
int value = ADCee::read(pin);
|
||||
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||
if (vpin > 255) return -1023;
|
||||
uint8_t pin = vpin;
|
||||
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
|
||||
uint8_t index = (pin-_firstVpin) / 8;
|
||||
if (_pinModes[index] & mask) {
|
||||
@@ -446,22 +500,9 @@ int ArduinoPins::_readAnalogue(VPIN vpin) {
|
||||
else
|
||||
pinMode(pin, INPUT);
|
||||
}
|
||||
|
||||
// Since AnalogRead is also called from interrupt code, disable interrupts
|
||||
// while we're using it. There's only one ADC shared by all analogue inputs
|
||||
// on the Arduino, so we don't want interruptions.
|
||||
//******************************************************************************
|
||||
// NOTE: If the HAL is running on a computer without the DCC signal generator,
|
||||
// then interrupts needn't be disabled. Also, the DCC signal generator puts
|
||||
// the ADC into fast mode, so if it isn't present, analogueRead calls will be much
|
||||
// slower!!
|
||||
//******************************************************************************
|
||||
noInterrupts();
|
||||
int value = analogRead(pin);
|
||||
interrupts();
|
||||
|
||||
int value = ADCee::init(pin);
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
|
||||
DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
65
IODevice.h
65
IODevice.h
@@ -143,6 +143,7 @@ public:
|
||||
|
||||
// read invokes the IODevice instance's _readAnalogue method.
|
||||
static int readAnalogue(VPIN vpin);
|
||||
static int configureAnalogIn(VPIN vpin);
|
||||
|
||||
// loop invokes the IODevice instance's _loop method.
|
||||
static void loop();
|
||||
@@ -168,6 +169,7 @@ protected:
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
_I2CAddress=0;
|
||||
}
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
@@ -200,6 +202,10 @@ protected:
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
virtual int _configureAnalogIn(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Method to perform updates on an ongoing basis (optionally implemented within device class)
|
||||
virtual void _loop(unsigned long currentMicros) {
|
||||
@@ -220,13 +226,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 +248,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 +265,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 +276,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 +289,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 +324,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 +347,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.
|
||||
@@ -354,6 +361,7 @@ private:
|
||||
// Device-specific read functions.
|
||||
int _read(VPIN vpin) override;
|
||||
int _readAnalogue(VPIN vpin) override;
|
||||
int _configureAnalogIn(VPIN vpin) override;
|
||||
void _display() override;
|
||||
|
||||
|
||||
@@ -362,10 +370,43 @@ 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"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
|
||||
#endif // iodevice_h
|
||||
#endif // iodevice_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;
|
||||
|
||||
@@ -160,7 +161,7 @@ protected:
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
// Validate parameter.
|
||||
volume = min(30,volume);
|
||||
volume = min((uint8_t)30,volume);
|
||||
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
|
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;
|
||||
|
21
IO_HCSR04.h
21
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);
|
||||
@@ -137,7 +138,7 @@ private:
|
||||
//
|
||||
void read_HCSR04device() {
|
||||
// uint16 enough to time up to 65ms
|
||||
uint16_t startTime, waitTime, currentTime, maxTime;
|
||||
uint16_t startTime, waitTime = 0, currentTime, maxTime;
|
||||
|
||||
// If receive pin is still set on from previous call, abort the read.
|
||||
if (ArduinoPins::fastReadDigital(_echoPin))
|
||||
@@ -185,4 +186,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_HCSR04_H
|
||||
#endif //IO_HCSR04_H
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
256
MotorDriver.cpp
256
MotorDriver.cpp
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
@@ -22,20 +23,31 @@
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#endif
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
volatile byte fakePORTA;
|
||||
volatile byte fakePORTB;
|
||||
volatile byte fakePORTC;
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
|
||||
MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
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;
|
||||
IODevice::write(powerPin,LOW);// set to OUTPUT and off
|
||||
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);
|
||||
@@ -45,17 +57,17 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTA;
|
||||
fastSignalPin.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTB;
|
||||
fastSignalPin.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTC;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
@@ -79,8 +91,7 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
pinMode(currentPin, INPUT);
|
||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||
senseOffset = ADCee::init(currentPin);
|
||||
}
|
||||
|
||||
faultPin=fault_pin;
|
||||
@@ -89,16 +100,35 @@ MotorDriver::MotorDriver(VPIN 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 > ADCee::ADCmax()) {
|
||||
// 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(ADCee::ADCmax()-senseOffset));
|
||||
rawCurrentTripValue=ADCee::ADCmax()-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();
|
||||
@@ -114,11 +144,17 @@ bool MotorDriver::isPWMCapable() {
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (on) {
|
||||
IODevice::write(powerPin,HIGH);
|
||||
if (resetsCounterP != NULL)
|
||||
*resetsCounterP = 0;
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
else {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
}
|
||||
else IODevice::write(powerPin,LOW);
|
||||
powerMode=mode;
|
||||
}
|
||||
|
||||
@@ -130,10 +166,14 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||
// (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();}
|
||||
}
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
@@ -142,20 +182,18 @@ bool MotorDriver::canMeasureCurrent() {
|
||||
/*
|
||||
* Return the current reading as pin reading 0 to 1023. If the fault
|
||||
* pin is activated return a negative current to show active fault pin.
|
||||
* As there is no -0, create a little and return -1 in that case.
|
||||
* As there is no -0, cheat a little and return -1 in that case.
|
||||
*
|
||||
* senseOffset handles the case where a shield returns values above or below
|
||||
* a central value depending on direction.
|
||||
*
|
||||
* Bool fromISR should be adjusted dependent how function is called
|
||||
*/
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
int MotorDriver::getCurrentRaw(bool fromISR) {
|
||||
(void)fromISR;
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
// This function should NOT be called in an interruot so we
|
||||
// dont need to fart about saving and restoring CPU specific
|
||||
// interrupt registers.
|
||||
noInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
interrupts();
|
||||
current = ADCee::read(currentPin, fromISR)-senseOffset;
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
return (current == 0 ? -1 : -current);
|
||||
@@ -163,71 +201,129 @@ int MotorDriver::getCurrentRaw() {
|
||||
|
||||
}
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
/*
|
||||
* This should only be called in interrupt context
|
||||
* Copies current value from HW to cached value in
|
||||
* Motordriver.
|
||||
*/
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
bool MotorDriver::sampleCurrentFromHW() {
|
||||
byte low, high;
|
||||
//if (!bit_is_set(ADCSRA, ADIF))
|
||||
if (bit_is_set(ADCSRA, ADSC))
|
||||
return false;
|
||||
// if ((ADMUX & mask) != (currentPin - A0))
|
||||
// return false;
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
sampleCurrent = (high << 8) | low;
|
||||
sampleCurrentTimestamp = millis();
|
||||
return true;
|
||||
}
|
||||
void MotorDriver::startCurrentFromHW() {
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
const byte mask = 7;
|
||||
#else
|
||||
const byte mask = 31;
|
||||
#endif
|
||||
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
249, 392, 440, 494,
|
||||
523, 587, 659, 698,
|
||||
494, 440, 392, 249,
|
||||
330, 284, 262, 247,
|
||||
220, 196, 175, 165 };
|
||||
#endif
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
#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 defined(ARDUINO_ARCH_ESP32)
|
||||
{
|
||||
int f = 131;
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
DIAG(F("setDCSignal: speedcode=%d BrakePin=%d brake=%d dir=%d"),speedcode, brakePin, brake, tDir);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
// 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 during
|
||||
// that time as otherwise setDCCSignal() which is called from interrupt
|
||||
// contect can undo whatever we do here.
|
||||
if (fastSignalPin.shadowinout != NULL) {
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTA"));
|
||||
noInterrupts();
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTB"));
|
||||
noInterrupts();
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTC"));
|
||||
noInterrupts();
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
interrupts();
|
||||
}
|
||||
} else {
|
||||
#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::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;
|
||||
return analogRead(currentPin)-senseOffset;
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
return (unsigned int)(raw * senseFactor);
|
||||
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
|
||||
return (int32_t)raw * senseFactorInternal / senseScale;
|
||||
}
|
||||
int MotorDriver::mA2raw( unsigned int mA) {
|
||||
return (int)(mA / senseFactor);
|
||||
unsigned int MotorDriver::mA2raw( unsigned int mA) {
|
||||
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
|
||||
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
|
||||
@@ -255,14 +351,14 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
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 - TOGGLED POWER on %d"), trackno);
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
|
||||
} else {
|
||||
DIAG(F("TRACK %d FAULT PIN ACTIVE - OVERLOAD"), trackno);
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
@@ -280,7 +376,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %d POWER OVERLOAD current=%d max=%d offtime=%d"), trackno, mA, maxmA, sampleDelay);
|
||||
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
|
||||
@@ -292,7 +388,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
setPower(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %d POWER RESET delay=%d"), trackno, sampleDelay);
|
||||
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
|
124
MotorDriver.h
124
MotorDriver.h
@@ -1,11 +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
|
||||
@@ -42,6 +43,20 @@
|
||||
#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
|
||||
@@ -62,32 +77,51 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
struct FASTPIN {
|
||||
volatile uint32_t *inout;
|
||||
uint32_t maskHIGH;
|
||||
uint32_t maskLOW;
|
||||
volatile uint32_t *shadowinout;
|
||||
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;
|
||||
volatile uint8_t *shadowinout;
|
||||
};
|
||||
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(VPIN 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( POWERMODE mode);
|
||||
virtual POWERMODE getPower() { return powerMode;}
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
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);
|
||||
}
|
||||
@@ -108,32 +142,52 @@ class MotorDriver {
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
};
|
||||
virtual void setBrake( bool on);
|
||||
virtual void setDCSignal(byte speedByte);
|
||||
virtual int getCurrentRaw();
|
||||
virtual int getCurrentRawInInterrupt();
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
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(bool fromISR=false);
|
||||
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();
|
||||
bool trackPWM;
|
||||
static bool usePWM; // TODO: Remove
|
||||
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 setResetCounterPointer(volatile byte *bp) { // load resetPacketCounter pointer
|
||||
resetsCounterP = bp;
|
||||
inline void makeProgTrack(bool on) { // let this output know it's a prog track.
|
||||
isProgTrack = on;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
#endif
|
||||
private:
|
||||
volatile byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track
|
||||
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);
|
||||
@@ -143,7 +197,15 @@ class MotorDriver {
|
||||
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;
|
||||
@@ -153,6 +215,10 @@ class MotorDriver {
|
||||
unsigned int sampleDelay;
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
volatile unsigned long sampleCurrentTimestamp;
|
||||
volatile uint16_t sampleCurrent;
|
||||
#endif
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
|
102
MotorDrivers.h
102
MotorDrivers.h
@@ -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,56 @@
|
||||
#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) || defined(ARDUINO_ARCH_STM32)
|
||||
// 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, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
|
||||
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
#define STM32_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 +105,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 +128,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 +155,34 @@
|
||||
// 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)
|
||||
|
||||
// This is an example how to stack two standard motor shields. The upper shield
|
||||
// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and
|
||||
// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5
|
||||
// Pin assigment table:
|
||||
// 2 Enable C jumpered
|
||||
// 3 Enable A direct
|
||||
// 4 Dir D jumpered
|
||||
// 5 Enable D jumpered
|
||||
// 6 Brake D jumpered
|
||||
// 7 Brake C jumpered
|
||||
// 8 Brake B direct
|
||||
// 9 Brake A direct
|
||||
// 10 Dir C jumpered
|
||||
// 11 Enable B direct
|
||||
// 12 Dir A direct
|
||||
// 13 Dir B direct
|
||||
// A0 Sense A direct
|
||||
// A1 Sense B direct
|
||||
// A4 Sense C jumpered
|
||||
// A5 Sense D jumpered
|
||||
//
|
||||
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN)
|
||||
//
|
||||
#endif
|
||||
|
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,22 @@ 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 SERIAL6_COMMANDS
|
||||
Serial6.begin(115200);
|
||||
new SerialManager(&Serial6);
|
||||
#endif
|
||||
#ifdef SERIAL5_COMMANDS
|
||||
Serial5.begin(115200);
|
||||
new SerialManager(&Serial5);
|
||||
#endif
|
||||
#ifdef SERIAL4_COMMANDS
|
||||
Serial4.begin(115200);
|
||||
new SerialManager(&Serial4);
|
||||
#endif
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
Serial3.begin(115200);
|
||||
new SerialManager(&Serial3);
|
||||
@@ -47,13 +75,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);
|
||||
|
182
TrackManager.cpp
182
TrackManager.cpp
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
@@ -49,7 +50,53 @@ 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
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
/*
|
||||
* sampleCurrent() runs from Interrupt
|
||||
*/
|
||||
void TrackManager::sampleCurrent() {
|
||||
static byte tr = 0;
|
||||
byte trAtStart = tr;
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
if (! track[tr]->sampleCurrentFromHW()) {
|
||||
return; // no result, continue to wait
|
||||
}
|
||||
// found value, advance at least one track
|
||||
// for scope debug track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
|
||||
return; // We could continue but for prog track we
|
||||
// rather do it in next interrupt beacuse
|
||||
// that gives us well defined sampling point.
|
||||
// For other tracks we care less unless we
|
||||
// have only few (max 2) tracks.
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
|
||||
track[tr]->startCurrentFromHW();
|
||||
// for scope debug track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
break;
|
||||
}
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (tr == trAtStart) // we are through and nothing found to do
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#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
|
||||
@@ -73,8 +120,6 @@ void TrackManager::Setup(const FSH * shieldname,
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||
DCC::begin(shieldname);
|
||||
}
|
||||
|
||||
@@ -87,23 +132,16 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
}
|
||||
}
|
||||
|
||||
// The port registers that are shadowing
|
||||
// the real port registers. These are
|
||||
// defined in Motordriver.cpp
|
||||
extern byte fakePORTA;
|
||||
extern byte fakePORTB;
|
||||
extern byte fakePORTC;
|
||||
|
||||
// setDCCSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
@@ -115,13 +153,13 @@ void TrackManager::setCutout( bool on) {
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
@@ -135,11 +173,10 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
DIAG(F("Track=%c"),trackToSet+'A');
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
@@ -147,32 +184,40 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
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;
|
||||
}
|
||||
// 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]->setResetCounterPointer(NULL); // only the prog track has this pointer set
|
||||
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.
|
||||
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);
|
||||
|
||||
}
|
||||
else {
|
||||
// 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.
|
||||
// 128 is speed=0 and dir=0 and then loosen brake.
|
||||
track[trackToSet]->setDCSignal(128);
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
@@ -181,6 +226,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// 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;
|
||||
@@ -194,34 +240,41 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
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');
|
||||
//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);
|
||||
//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');
|
||||
//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
|
||||
}
|
||||
if (MotorDriver::usePWM != canDo)
|
||||
DIAG(F("HA mode changed from %d to %d"), MotorDriver::usePWM, canDo);
|
||||
MotorDriver::usePWM=canDo;
|
||||
#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);
|
||||
//DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -242,9 +295,13 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
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"));
|
||||
@@ -312,6 +369,15 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||
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) {
|
||||
@@ -367,6 +433,22 @@ void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
/*
|
||||
* © 2022 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
|
||||
@@ -17,14 +18,18 @@
|
||||
* 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};
|
||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
|
||||
|
||||
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
|
||||
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
|
||||
@@ -54,6 +59,9 @@ class TrackManager {
|
||||
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);}
|
||||
@@ -68,10 +76,10 @@ class TrackManager {
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void sampleCurrent();
|
||||
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);
|
||||
@@ -83,6 +91,9 @@ class TrackManager {
|
||||
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
|
||||
|
@@ -60,9 +60,9 @@ protected:
|
||||
union {
|
||||
struct {
|
||||
bool closed : 1;
|
||||
bool _rfu: 2;
|
||||
bool hidden: 1;
|
||||
uint8_t turnoutType : 4;
|
||||
bool hidden : 1;
|
||||
bool _rfu : 1;
|
||||
uint8_t turnoutType : 5;
|
||||
};
|
||||
uint8_t flags;
|
||||
};
|
||||
|
100
WiThrottle.cpp
100
WiThrottle.cpp
@@ -63,12 +63,83 @@
|
||||
|
||||
WiThrottle * WiThrottle::firstThrottle=NULL;
|
||||
|
||||
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
|
||||
if (n == 0)
|
||||
return 0;
|
||||
do {
|
||||
if (*s1 != *s2++)
|
||||
return 1;
|
||||
if (*s1++ == 0)
|
||||
break;
|
||||
} while (--n != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WiThrottle::findUniqThrottle(int id, char *u) {
|
||||
WiThrottle *wtmyid = NULL;
|
||||
WiThrottle *wtmyuniq = NULL;
|
||||
|
||||
// search 1, look for clientid match
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
if (wt->clientid == id) {
|
||||
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
|
||||
return;
|
||||
wtmyid = wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// search 2, look for string match
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
if (xstrncmp(u, wt->uniq, 16) == 0) {
|
||||
wtmyuniq = wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// analyse result of the two for loops:
|
||||
if (wtmyid == NULL) { // should not happen
|
||||
DIAG(F("Did not find my own wiThrottle handle"));
|
||||
return;
|
||||
}
|
||||
// wtmyuniq == wtmyid has already returned in for loop 1
|
||||
if (wtmyuniq == NULL) { // register uniq in the found id
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
|
||||
return;
|
||||
}
|
||||
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
|
||||
// so we need to do the copy (all other options covered above)
|
||||
for(int n=0; n < MAX_MY_LOCO; n++)
|
||||
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
|
||||
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
|
||||
wtmyid->heartBeat = wtmyuniq->heartBeat;
|
||||
wtmyid->initSent = wtmyuniq->initSent;
|
||||
wtmyid->exRailSent = wtmyuniq->exRailSent;
|
||||
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
|
||||
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
|
||||
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE)
|
||||
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
|
||||
forget(wtmyuniq->clientid); // do not use wtmyid after this
|
||||
}
|
||||
|
||||
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==wifiClient) return wt;
|
||||
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;
|
||||
@@ -97,6 +168,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;
|
||||
@@ -211,15 +283,14 @@ 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"));
|
||||
@@ -227,7 +298,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
|
||||
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r*2);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
@@ -235,8 +306,8 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
#endif
|
||||
|
||||
|
||||
// set heartbeat to 1 second because we need to sync the metadata
|
||||
StringFormatter::send(stream,F("*1\n"));
|
||||
// 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;
|
||||
@@ -411,9 +482,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':
|
||||
@@ -477,9 +552,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;
|
||||
}
|
||||
|
||||
@@ -545,10 +622,12 @@ 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) {
|
||||
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||
//DIAG(F("LocoCallback commit (noloco)"));
|
||||
stashStream->commit(); // done here, commit and return
|
||||
return;
|
||||
}
|
||||
@@ -559,6 +638,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
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;
|
||||
}
|
||||
@@ -571,7 +651,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(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;
|
||||
}
|
||||
}
|
||||
|
@@ -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)) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
@@ -37,6 +38,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
|
||||
@@ -142,6 +144,9 @@ The configuration file for DCC-EX Command Station
|
||||
// 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 work.
|
||||
//
|
||||
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
|
||||
// at least until it works.
|
||||
//
|
||||
// #define DISABLE_EEPROM
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -187,13 +192,36 @@ The configuration file for DCC-EX Command Station
|
||||
// HANDLING MULTIPLE SERIAL THROTTLES
|
||||
// The command station always operates with the default Serial port.
|
||||
// Diagnostics are only emitted on the default serial port and not broadcast.
|
||||
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
|
||||
// which may or may not exist on your CPU. (Mega has all 3)
|
||||
// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4,
|
||||
// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3,
|
||||
// SAMD/SAMC and STM32 have up to 6.)
|
||||
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
||||
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
||||
//
|
||||
//#define SERIAL1_COMMANDS
|
||||
//#define SERIAL2_COMMANDS
|
||||
//#define SERIAL3_COMMANDS
|
||||
//#define SERIAL4_COMMANDS
|
||||
//#define SERIAL5_COMMANDS
|
||||
//#define SERIAL6_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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
127
defines.h
127
defines.h
@@ -1,8 +1,9 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -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,127 @@
|
||||
#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"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#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) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
#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 +169,7 @@
|
||||
#endif
|
||||
|
||||
#if ENABLE_ETHERNET
|
||||
#if defined(BIG_RAM)
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#define ETHERNET_WARNING
|
||||
@@ -80,7 +191,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
|
||||
|
101
installer.sh
Executable file
101
installer.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# © 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/>.
|
||||
#
|
||||
#
|
||||
# Usage: mkdir DIRNAME ; cd DIRNAME ; ../installer.sh
|
||||
# or from install directory ./installer.sh
|
||||
#
|
||||
|
||||
DCCEXGITURL="https://github.com/DCC-EX/CommandStation-EX"
|
||||
ACLIINSTALL="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh"
|
||||
ACLI="./bin/arduino-cli"
|
||||
|
||||
function need () {
|
||||
type -p $1 > /dev/null && return
|
||||
sudo apt-get install $1
|
||||
type -p $1 > /dev/null && return
|
||||
echo "Could not install $1, abort"
|
||||
exit 255
|
||||
}
|
||||
|
||||
|
||||
need git
|
||||
if test -d .git ; then
|
||||
: assume we are right here
|
||||
git pull
|
||||
else
|
||||
git clone "$DCCEXGITURL"
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
if test -f config.h ; then
|
||||
: all well
|
||||
else
|
||||
# need to do this config better
|
||||
cp -p config.example.h config.h
|
||||
fi
|
||||
need curl
|
||||
if test -x "$ACLI" ; then
|
||||
: all well
|
||||
else
|
||||
curl "$ACLIINSTALL" > acliinstall.sh
|
||||
chmod +x acliinstall.sh
|
||||
./acliinstall.sh
|
||||
fi
|
||||
|
||||
$ACLI core update-index || exit 255
|
||||
|
||||
# Board discovery
|
||||
BOARDS=/tmp/boards.$$
|
||||
$ACLI board list | grep serial > $BOARDS
|
||||
if test x`< $BOARDS wc -l` = 'x1' ; then
|
||||
LINE=`cat $BOARDS`
|
||||
else
|
||||
# ask user
|
||||
echo "What board to use? (give line number)"
|
||||
cat -n $BOARDS
|
||||
echo -n "> "
|
||||
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $BOARDS`
|
||||
fi
|
||||
rm $BOARDS
|
||||
PORT=`echo $LINE | cut -d" " -f1`
|
||||
echo Will use port: $PORT
|
||||
|
||||
# FQBN discovery
|
||||
FQBN=`echo $LINE | egrep 'arduino:avr:[a-z][a-z]*' | sed 's/.*\(arduino:avr:[a-z][a-z]*\) .*/\1/1'`
|
||||
if test x$FQBN = x ; then
|
||||
# ask user
|
||||
cat > /tmp/fqbn.$$ <<EOF
|
||||
arduino:avr:uno
|
||||
arduino:avr:mega
|
||||
esp32:esp32:esp32
|
||||
EOF
|
||||
echo "What board type? (give line number)"
|
||||
cat -n /tmp/fqbn.$$
|
||||
echo -n "> "
|
||||
FQBN=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' /tmp/fqbn.$$`
|
||||
fi
|
||||
rm /tmp/fqbn.$$
|
||||
echo FQBN is $FQBN
|
||||
|
||||
# Install phase
|
||||
$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package
|
||||
$ACLI board attach -p $PORT --fqbn $FQBN $PWD
|
||||
$ACLI compile --fqbn $FQBN $PWD
|
||||
$ACLI upload -v -t -p $PORT $PWD
|
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 =
|
23
version.h
23
version.h
@@ -4,13 +4,32 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.0.3"
|
||||
// 4.0.3 Track Manager additions:
|
||||
#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)
|
||||
|
Reference in New Issue
Block a user