mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
225 Commits
v4.2.4-Dev
...
devel-powe
Author | SHA1 | Date | |
---|---|---|---|
|
802f9c96b4 | ||
|
0e36b3b997 | ||
|
9482041799 | ||
|
2ada89f918 | ||
|
0b0aba7aef | ||
|
9c95eb6905 | ||
|
47cda83210 | ||
|
d8d785877e | ||
|
3b82a94d83 | ||
|
acadf241e6 | ||
|
8cc5f7ddf4 | ||
|
f1c17c3606 | ||
|
d36ac7dcfd | ||
|
6b67760db1 | ||
|
6874ddca9b | ||
|
06827a42b7 | ||
|
f59fe6e83b | ||
|
c768bdc361 | ||
|
ad97260055 | ||
|
938b4cfbd6 | ||
|
2a3d48dc00 | ||
|
5efb0c5013 | ||
|
e53ed7b46d | ||
|
4d31cd64a5 | ||
|
6031a0fb7f | ||
|
d375723a13 | ||
|
fa38583772 | ||
|
984ef6fead | ||
|
cf2817d7c4 | ||
|
0c2f8428df | ||
|
53215b496e | ||
|
d41b5e0938 | ||
|
d8cbdb24e1 | ||
|
93ac1b6d61 | ||
|
ad4a0a9592 | ||
|
deb49f2943 | ||
|
5cb216dd79 | ||
|
afc94a75bb | ||
|
2848ba616b | ||
|
3d480ee9ef | ||
|
57292c2250 | ||
|
c870940dde | ||
|
754639c7e3 | ||
|
a478ad7112 | ||
|
abe79b854e | ||
|
ec83a345dc | ||
|
4e32c707b9 | ||
|
73e1dfc192 | ||
|
1ae74d9487 | ||
|
a7366b42c1 | ||
|
84431d1841 | ||
|
e12c5292fa | ||
|
e76197faa9 | ||
|
bdc8aec9a6 | ||
|
052256e2ed | ||
|
1073e142e6 | ||
|
a18c06d021 | ||
|
77b20e6a16 | ||
|
1d27eb67e4 | ||
|
7f19a92d2a | ||
|
28caa9e8d3 | ||
|
95945eab4c | ||
|
638682f05c | ||
|
ffb08523da | ||
|
d8a1bcaf34 | ||
|
212bf8d80e | ||
|
a17c02444d | ||
|
290d878063 | ||
|
2a7588b1b5 | ||
|
be33bafa66 | ||
|
6cc66e26c1 | ||
|
c91d66549c | ||
|
9e5d780c14 | ||
|
2c0886bc2f | ||
|
762742b4af | ||
|
88b572a148 | ||
|
fcf16c1367 | ||
|
c69b8d85c8 | ||
|
006c85e6ae | ||
|
4f233de726 | ||
|
4acf46db54 | ||
|
20b3e9064c | ||
|
459904e5dd | ||
|
878549d538 | ||
|
7f4e3d9cea | ||
|
f2aeb4069f | ||
|
aaf25d5426 | ||
|
fb9170ab8b | ||
|
286bdc3c4d | ||
|
cd46d3c9e0 | ||
|
fb36bd1380 | ||
|
b62c4da04d | ||
|
8fac20a451 | ||
|
1be382a6ed | ||
|
1f433d0c17 | ||
|
046e62a8b3 | ||
|
a2c7c7d12a | ||
|
9b36bdcf46 | ||
|
a8646a2f32 | ||
|
22e20f9092 | ||
|
873d470f86 | ||
|
ff7260b9bc | ||
|
de4954ca3e | ||
|
c26f53e1fa | ||
|
e48a40fafb | ||
|
5c120efa16 | ||
|
9abcfb9e4f | ||
|
e01893bcf1 | ||
|
6eff836473 | ||
|
402e16727c | ||
|
658fca2601 | ||
|
3fccf6a484 | ||
|
ace9c1642a | ||
|
ec4dfb8c1e | ||
|
6482a421b4 | ||
|
d02c6b1f61 | ||
|
94c8dafeb2 | ||
|
322cb3db54 | ||
|
ffdf023de6 | ||
|
8f32ae712f | ||
|
eea1396997 | ||
|
b1bd28273d | ||
|
0be25f6e7f | ||
|
71ce913712 | ||
|
70845b4932 | ||
|
c44fb0ac44 | ||
|
1c7103c21e | ||
|
5170147e3e | ||
|
2ad08029a4 | ||
|
25b3250345 | ||
|
3973996344 | ||
|
943494385f | ||
|
c8fea3a4a7 | ||
|
1d61a8f3f9 | ||
|
a480a5a3d2 | ||
|
070daa37dc | ||
|
75f1a8f43a | ||
|
ad294ea17e | ||
|
8ecb408da7 | ||
|
3862f7250d | ||
|
785b515f9e | ||
|
9699a44081 | ||
|
cb9a8bb7a6 | ||
|
1d5897d2d2 | ||
|
7bc0433197 | ||
|
9104956009 | ||
|
af4d8d4075 | ||
|
06945bb114 | ||
|
2d27cb052d | ||
|
8cbcf5df32 | ||
|
13368c319a | ||
|
cb1fc75077 | ||
|
b671d70dfe | ||
|
45b36c48cb | ||
|
d95096ded8 | ||
|
a6ae1a48a2 | ||
|
913f0a0c86 | ||
|
984fd2fa08 | ||
|
d062de2eb8 | ||
|
e1ec63464c | ||
|
aa02cd11e3 | ||
|
ce5d546b9b | ||
|
644cb29a3a | ||
|
70203c3733 | ||
|
f4aa572df2 | ||
|
01e5d49332 | ||
|
6a3a891682 | ||
|
f5b48619bf | ||
|
4a3d8729c6 | ||
|
d874ad8cc3 | ||
|
bd8439c2f9 | ||
|
65714ed1f2 | ||
|
280e61e1fc | ||
|
b061c0b347 | ||
|
7f3d547541 | ||
|
eb0861959c | ||
|
f1d445e056 | ||
|
4f19a60621 | ||
|
2b3ba514b0 | ||
|
a199de6d3e | ||
|
c0cb643cb5 | ||
|
be2f3b0db7 | ||
|
f939ea0768 | ||
|
863c839563 | ||
|
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 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -8,13 +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
|
||||
|
@@ -29,6 +29,11 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// variables to hold clock time
|
||||
int16_t lastclocktime;
|
||||
int8_t lastclockrate;
|
||||
|
||||
|
||||
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
|
||||
@@ -97,7 +102,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
#endif
|
||||
@@ -155,6 +160,50 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
// The JMRI clock command is of the form : PFT65871<;>4
|
||||
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// be safe for both types.
|
||||
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
|
||||
// opt - case 1 save the latest time if changed
|
||||
// case 2 broadcast the time when requested
|
||||
// case 3 display latest time
|
||||
switch (opt)
|
||||
{
|
||||
case 1:
|
||||
if (clocktime != lastclocktime){
|
||||
if (Diag::CMD) {
|
||||
DIAG(F("Clock Command Received"));
|
||||
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
|
||||
}
|
||||
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
|
||||
// look for an event for this time
|
||||
RMFT2::clockEvent(clocktime,1);
|
||||
// Now tell everyone else what the time is.
|
||||
CommandDistributor::broadcastClockTime(clocktime, clockrate);
|
||||
lastclocktime = clocktime;
|
||||
lastclockrate = clockrate;
|
||||
}
|
||||
return;
|
||||
|
||||
case 2:
|
||||
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int16_t CommandDistributor::retClockTime() {
|
||||
return lastclocktime;
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "RingStream.h"
|
||||
#include "StringBuffer.h"
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// Command Distributor must handle a RingStream of clients
|
||||
@@ -45,10 +46,14 @@ public :
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastClockTime(int16_t time, int8_t rate);
|
||||
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastText(const FSH * msg);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -99,6 +99,9 @@ void setup()
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::begin();
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
|
41
DCC.cpp
41
DCC.cpp
@@ -595,30 +595,15 @@ void DCC::loop() {
|
||||
void DCC::issueReminders() {
|
||||
// if the main track transmitter still has a pending packet, skip this time around.
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Move to next loco slot. If occupied, send a reminder.
|
||||
int reg = lastLocoReminder+1;
|
||||
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||
if (speedTable[reg].loco > 0) {
|
||||
// have found loco to remind
|
||||
if (issueReminder(reg))
|
||||
lastLocoReminder = reg;
|
||||
} else
|
||||
lastLocoReminder = reg;
|
||||
}
|
||||
|
||||
bool DCC::issueReminder(int reg) {
|
||||
@@ -698,6 +683,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
speedTable[reg].groupFlags=0;
|
||||
speedTable[reg].functions=0;
|
||||
}
|
||||
if (reg > highestUsedReg) highestUsedReg = reg;
|
||||
return reg;
|
||||
}
|
||||
|
||||
@@ -705,7 +691,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
|
||||
if (loco==0) {
|
||||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
for (int reg = 0; reg < highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco==0) continue;
|
||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
if (speedTable[reg].speedCode != newspeed) {
|
||||
@@ -725,13 +711,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
}
|
||||
|
||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||
int DCC::nextLoco = 0;
|
||||
int DCC::lastLocoReminder = 0;
|
||||
int DCC::highestUsedReg = 0;
|
||||
|
||||
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
|
||||
int used=0;
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco>0) {
|
||||
used ++;
|
||||
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||
|
3
DCC.h
3
DCC.h
@@ -108,7 +108,8 @@ private:
|
||||
static void updateLocoReminder(int loco, byte speedCode);
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||
static bool issueReminder(int reg);
|
||||
static int nextLoco;
|
||||
static int lastLocoReminder;
|
||||
static int highestUsedReg;
|
||||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
|
||||
|
@@ -425,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)
|
||||
|
@@ -41,6 +41,14 @@
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
// flashlist requires a far pointer for high flash access.
|
||||
#define SENDFLASHLIST(stream,flashList) \
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==0) break; \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
@@ -73,6 +81,7 @@ const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_X='X';
|
||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
@@ -501,6 +510,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
@@ -561,15 +571,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if ((params<1) | (params>2)) break; // <J>
|
||||
if ((params<1) | (params>3)) break; // <J>
|
||||
//if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
|
||||
if (params==1) { // <JC> returns latest time
|
||||
int16_t x = CommandDistributor::retClockTime();
|
||||
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
||||
return;
|
||||
}
|
||||
CommandDistributor::setClockTime(p[1], p[2], 1);
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
sendFlashList(stream,RMFT2::routeIdList);
|
||||
sendFlashList(stream,RMFT2::automationIdList);
|
||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
@@ -588,7 +608,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
|
||||
if (params==1) {
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||
#endif
|
||||
@@ -605,14 +627,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
else {
|
||||
const FSH *tdesc = NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (tdesc == NULL)
|
||||
tdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
tdesc);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -633,14 +658,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
StringFormatter::send(stream, F("<X>\n"));
|
||||
}
|
||||
|
||||
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t value=GETFLASHW(flashList+i);
|
||||
if (value==0) return;
|
||||
StringFormatter::send(stream,F(" %d"),value);
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
@@ -730,15 +747,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
switch (params)
|
||||
{
|
||||
case 0: // <T> list turnout definitions
|
||||
{
|
||||
bool gotOne = false;
|
||||
for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
|
||||
{
|
||||
gotOne = true;
|
||||
tt->print(stream);
|
||||
}
|
||||
return gotOne; // will <X> if none found
|
||||
}
|
||||
return Turnout::printAll(stream); // will <X> if none found
|
||||
|
||||
case 1: // <T id> delete turnout
|
||||
if (!Turnout::remove(p[0]))
|
||||
@@ -759,12 +768,19 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
case HASH_KEYWORD_T:
|
||||
state= false;
|
||||
break;
|
||||
default:
|
||||
return false; // Invalid parameter
|
||||
case HASH_KEYWORD_X:
|
||||
{
|
||||
Turnout *tt = Turnout::get(p[0]);
|
||||
if (tt) {
|
||||
tt->print(stream);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default: // Invalid parameter
|
||||
return false;
|
||||
}
|
||||
if (!Turnout::setClosed(p[0], state)) return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#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"
|
||||
|
||||
@@ -67,6 +68,8 @@ 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) {
|
||||
|
40
DCCTimer.h
40
DCCTimer.h
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -93,4 +93,40 @@ private:
|
||||
|
||||
};
|
||||
|
||||
// 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:
|
||||
// begin is called for any setup that must be done before
|
||||
// **init** can be called. On some architectures this involves ADC
|
||||
// initialisation and clock routing, sampling times etc.
|
||||
static void begin();
|
||||
// init adds the pin to the list of scanned pins (if this
|
||||
// platform's implementation scans pins) and returns the first
|
||||
// read value (which is why it required begin to have been called first!)
|
||||
// It must be called before the regular scan is started.
|
||||
static int init(uint8_t pin);
|
||||
// read does read the pin value from the scanned cache or directly
|
||||
// if this is a platform that does not scan. fromISR is a hint if
|
||||
// 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();
|
||||
// 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
|
||||
|
126
DCCTimerAVR.cpp
126
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,7 +26,6 @@
|
||||
// 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"
|
||||
@@ -44,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;
|
||||
@@ -90,7 +88,10 @@ void DCCTimer::clearPWM() {
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
// take the fist 3 and last 3 of the serial.
|
||||
// the first 5 of 8 are at 0x0E to 0x013
|
||||
// the last 3 of 8 are at 0x15 to 0x017
|
||||
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
@@ -120,4 +121,119 @@ void DCCTimer::reset() {
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 15
|
||||
#else
|
||||
#define NUM_ADC_INPUTS 7
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
bool ADCusesHighPort = false;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
if (id > 7)
|
||||
ADCusesHighPort = true;
|
||||
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
|
||||
#if defined(ADCSRB) && defined(MUX5)
|
||||
if (ADCusesHighPort) { // if we ever have started to use high pins)
|
||||
if (id > 7) // if we use a high ADC pin
|
||||
bitSet(ADCSRB, MUX5); // set MUX5 bit
|
||||
else
|
||||
bitClear(ADCSRB, MUX5);
|
||||
}
|
||||
#endif
|
||||
ADMUX=(1<<REFS0)|(id & 0x07); //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;
|
||||
@@ -133,5 +150,29 @@ int DCCTimer::freeMemory() {
|
||||
void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
#endif
|
||||
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
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -124,5 +125,31 @@ void DCCTimer::reset() {
|
||||
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
|
||||
|
149
DCCTimerSAMD.cpp
149
DCCTimerSAMD.cpp
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
@@ -28,33 +28,14 @@
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
|
||||
#include "FSH.h" //PMA temp debug
|
||||
#include "DIAG.h" //PMA temp debug
|
||||
#include "DCCTimer.h"
|
||||
#include <wiring_private.h>
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
|
||||
// and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now
|
||||
// enables 10-bit mode, although 12-bit is possible
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64
|
||||
ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible
|
||||
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
|
||||
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization
|
||||
|
||||
// Timer setup - setup clock sources first
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
|
||||
GCLK_GENDIV_ID(4); // Apply to GCLK4
|
||||
@@ -88,7 +69,9 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
|
||||
// Set the interrupt condition, priority and enable it in the NVIC
|
||||
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
|
||||
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
|
||||
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();
|
||||
}
|
||||
@@ -173,4 +156,122 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#endif
|
||||
#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;
|
||||
|
||||
// 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -28,18 +28,23 @@
|
||||
// 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
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
|
||||
#elif defined(STM32F446ZE)
|
||||
// STM32F446ZE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||
#else
|
||||
#warning Serial1 not defined
|
||||
#endif
|
||||
@@ -89,10 +94,9 @@ void DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
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 *serno1 = (volatile uint32_t *)0x1FFF7A10;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
@@ -127,4 +131,148 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
int value = 0;
|
||||
PinName stmpin = digitalPin[analogInputPin[id]];
|
||||
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
break;
|
||||
case 0x01:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB
|
||||
gpioBase = GPIOB;
|
||||
break;
|
||||
case 0x02:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set pin mux mode to analog input
|
||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
||||
|
||||
// Set the sampling rate for that analog input
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
|
||||
if (analogvals == NULL)
|
||||
{
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
}
|
||||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
// Was this pin initialised yet?
|
||||
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 (!(ADC1->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
// 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
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -141,4 +141,31 @@ void DCCTimer::reset() {
|
||||
SCB_AIRCR = 0x05FA0004;
|
||||
}
|
||||
|
||||
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
|
||||
|
@@ -80,12 +80,14 @@ 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();
|
||||
|
255
EXRAIL2.cpp
255
EXRAIL2.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -41,6 +41,7 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
@@ -90,11 +91,27 @@ LookList * RMFT2::onDeactivateLookup=NULL;
|
||||
LookList * RMFT2::onRedLookup=NULL;
|
||||
LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
LookList * RMFT2::onClockLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
|
||||
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
// getOperand instance version, uses progCounter from instance.
|
||||
uint16_t RMFT2::getOperand(byte n) {
|
||||
return getOperand(progCounter,n);
|
||||
}
|
||||
|
||||
// getOperand static version, must be provided prog counter from loop etc.
|
||||
uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||
int offset=progCounter+1+(n*3);
|
||||
if (offset&1) {
|
||||
byte lsb=GETHIGHFLASH(RouteCode,offset);
|
||||
byte msb=GETHIGHFLASH(RouteCode,offset+1);
|
||||
return msb<<8|lsb;
|
||||
}
|
||||
return GETHIGHFLASHW(RouteCode,offset);
|
||||
}
|
||||
|
||||
LookList::LookList(int16_t size) {
|
||||
m_size=size;
|
||||
@@ -135,12 +152,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
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);
|
||||
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::begin() {
|
||||
|
||||
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
|
||||
|
||||
bool saved_diag=diag;
|
||||
diag=true;
|
||||
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
|
||||
@@ -153,11 +175,14 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
|
||||
|
||||
// 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);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
@@ -166,7 +191,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
for (progCounter=0;; SKIPOP){
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
VPIN operand=GET_OPERAND(0);
|
||||
VPIN operand=getOperand(progCounter,0);
|
||||
|
||||
switch (opcode) {
|
||||
case OPCODE_AT:
|
||||
@@ -176,31 +201,42 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_IFNOT: {
|
||||
int16_t pin = (int16_t)operand;
|
||||
if (pin<0) pin = -pin;
|
||||
DIAG(F("EXRAIL input vpin %d"),pin);
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_ATGTE:
|
||||
case OPCODE_ATLT:
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
case OPCODE_DRIVE: {
|
||||
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
|
||||
IODevice::configureAnalogIn((VPIN)operand);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_TURNOUT: {
|
||||
VPIN id=operand;
|
||||
int addr=GET_OPERAND(1);
|
||||
byte subAddr=GET_OPERAND(2);
|
||||
int addr=getOperand(progCounter,1);
|
||||
byte subAddr=getOperand(progCounter,2);
|
||||
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_SERVOTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
int activeAngle=GET_OPERAND(2);
|
||||
int inactiveAngle=GET_OPERAND(3);
|
||||
int profile=GET_OPERAND(4);
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
int activeAngle=getOperand(progCounter,2);
|
||||
int inactiveAngle=getOperand(progCounter,3);
|
||||
int profile=getOperand(progCounter,4);
|
||||
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PINTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||
break;
|
||||
}
|
||||
@@ -220,20 +256,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||
|
||||
new RMFT2(0); // add the startup route
|
||||
diag=saved_diag;
|
||||
}
|
||||
|
||||
void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
// turnout descriptions are in low flash F strings
|
||||
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||
}
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(routeIdList+i);
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
if (rid==id) return 'R';
|
||||
if (rid==0) break;
|
||||
}
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(automationIdList+i);
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==id) return 'A';
|
||||
if (rid==0) break;
|
||||
}
|
||||
@@ -295,7 +333,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
// 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);
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
@@ -542,7 +580,7 @@ void RMFT2::loop2() {
|
||||
if (delayTime!=0 && millis()-delayStart < delayTime) return;
|
||||
|
||||
byte opcode = GET_OPCODE;
|
||||
int16_t operand = GET_OPERAND(0);
|
||||
int16_t operand = getOperand(0);
|
||||
|
||||
// skipIf will get set to indicate a failing IF condition
|
||||
bool skipIf=false;
|
||||
@@ -608,13 +646,13 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_ATGTE: // wait for analog sensor>= value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATLT: // wait for analog sensor < value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
|
||||
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
@@ -625,7 +663,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
if (readSensor(operand)) break; // success without timeout
|
||||
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
|
||||
if (millis()-timeoutStart > 100*getOperand(1)) {
|
||||
timeoutFlag=true;
|
||||
break; // and drop through
|
||||
}
|
||||
@@ -668,7 +706,7 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_POM:
|
||||
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
|
||||
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
@@ -702,19 +740,27 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_IFGTE: // do next operand if sensor>= value
|
||||
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
|
||||
skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLT: // do next operand if sensor< value
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLOCO: // do if the loco is the active one
|
||||
skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL
|
||||
break;
|
||||
|
||||
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)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
skipIf=(int16_t)(micros()%100) >= operand;
|
||||
skipIf=(uint8_t)micros() >= operand * 255/100;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||
@@ -789,11 +835,11 @@ void RMFT2::loop2() {
|
||||
}
|
||||
|
||||
case OPCODE_XFON:
|
||||
DCC::setFn(operand,GET_OPERAND(1),true);
|
||||
DCC::setFn(operand,getOperand(1),true);
|
||||
break;
|
||||
|
||||
case OPCODE_XFOFF:
|
||||
DCC::setFn(operand,GET_OPERAND(1),false);
|
||||
DCC::setFn(operand,getOperand(1),false);
|
||||
break;
|
||||
|
||||
case OPCODE_DCCACTIVATE: {
|
||||
@@ -885,7 +931,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_SENDLOCO: // cab, route
|
||||
{
|
||||
int newPc=sequenceLookup->find(GET_OPERAND(1));
|
||||
int newPc=sequenceLookup->find(getOperand(1));
|
||||
if (newPc<0) break;
|
||||
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||
newtask->loco=operand;
|
||||
@@ -903,7 +949,7 @@ void RMFT2::loop2() {
|
||||
|
||||
|
||||
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
|
||||
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3));
|
||||
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
|
||||
break;
|
||||
|
||||
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
|
||||
@@ -935,6 +981,8 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
|
||||
break;
|
||||
|
||||
@@ -952,7 +1000,7 @@ void RMFT2::delayMe(long delay) {
|
||||
delayStart=millis();
|
||||
}
|
||||
|
||||
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
if (FLAGOVERFLOW(id)) return false; // Outside range limit
|
||||
byte f=flags[id];
|
||||
f &= ~offMask;
|
||||
@@ -973,8 +1021,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
||||
}
|
||||
|
||||
int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) { // end of signal list
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return -1;
|
||||
@@ -984,9 +1032,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||
|
||||
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
return sigslot; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
@@ -1003,11 +1052,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
setFlag(sigslot,rag,SIGNAL_MASK);
|
||||
|
||||
// Correct signal definition found, get the rag values
|
||||
int16_t sigpos=sigslot*4;
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
|
||||
int16_t sigpos=sigslot*8;
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
|
||||
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
|
||||
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
|
||||
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
|
||||
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
@@ -1035,11 +1084,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
// Manage invert (HIGH on) pins
|
||||
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
|
||||
|
||||
|
||||
// set the three pins
|
||||
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
|
||||
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
|
||||
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
|
||||
if (redpin) {
|
||||
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
|
||||
if (!aHigh) redval=!redval;
|
||||
IODevice::write(redpin,redval);
|
||||
}
|
||||
if (amberpin) {
|
||||
bool amberval=(rag==SIGNAL_AMBER);
|
||||
if (!aHigh) amberval=!amberval;
|
||||
IODevice::write(amberpin,amberval);
|
||||
}
|
||||
if (greenpin) {
|
||||
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
|
||||
if (!aHigh) greenval=!greenval;
|
||||
IODevice::write(greenpin,greenval);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
@@ -1060,7 +1121,19 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
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::clockEvent(int16_t clocktime, bool change) {
|
||||
// Hunt for an ONTIME for this time
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||
}
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
@@ -1083,3 +1156,95 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
void RMFT2::printMessage2(const FSH * msg) {
|
||||
DIAG(F("EXRAIL(%d) %S"),loco,msg);
|
||||
}
|
||||
static StringBuffer * buffer=NULL;
|
||||
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
|
||||
and handle the oddities like LCD, BROADCAST and PARSE */
|
||||
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
|
||||
Print * stream=NULL;
|
||||
// Find out where the string is going
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco);
|
||||
stream=&Serial;
|
||||
break;
|
||||
|
||||
case thrunge_serial: stream=&Serial; break;
|
||||
case thrunge_serial1:
|
||||
#ifdef SERIAL1_COMMANDS
|
||||
stream=&Serial1;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial2:
|
||||
#ifdef SERIAL2_COMMANDS
|
||||
stream=&Serial2;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial3:
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
stream=&Serial3;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial4:
|
||||
#ifdef SERIAL4_COMMANDS
|
||||
stream=&Serial4;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial5:
|
||||
#ifdef SERIAL5_COMMANDS
|
||||
stream=&Serial5;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial6:
|
||||
#ifdef SERIAL6_COMMANDS
|
||||
stream=&Serial6;
|
||||
#endif
|
||||
break;
|
||||
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
|
||||
case thrunge_lcn:
|
||||
#if defined(LCN_SERIAL)
|
||||
stream=&LCN_SERIAL;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_parse:
|
||||
case thrunge_broadcast:
|
||||
case thrunge_lcd:
|
||||
if (!buffer) buffer=new StringBuffer();
|
||||
buffer->flush();
|
||||
stream=buffer;
|
||||
break;
|
||||
}
|
||||
if (!stream) return;
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// if mega stream it out
|
||||
for (;;strfar++) {
|
||||
char c=pgm_read_byte_far(strfar);
|
||||
if (c=='\0') break;
|
||||
stream->write(c);
|
||||
}
|
||||
#else
|
||||
// UNO/NANO CPUs dont have high memory
|
||||
// 32 bit cpus dont care anyway
|
||||
stream->print((FSH *)strfar);
|
||||
#endif
|
||||
|
||||
// and decide what to do next
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&Serial,F(" *>\n"));
|
||||
break;
|
||||
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
|
||||
case thrunge_parse:
|
||||
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
|
||||
break;
|
||||
case thrunge_broadcast:
|
||||
// TODO CommandDistributor::broadcastText(buffer->getString());
|
||||
break;
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
33
EXRAIL2.h
33
EXRAIL2.h
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -54,6 +55,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -64,9 +68,17 @@ 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,
|
||||
OPCODE_IFLOCO
|
||||
};
|
||||
|
||||
enum thrunger: byte {
|
||||
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
|
||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcd, thrunge_lcn};
|
||||
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
@@ -107,16 +119,17 @@ 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 void clockEvent(int16_t clocktime, 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;
|
||||
static const int16_t FLASH routeIdList[];
|
||||
static const int16_t FLASH automationIdList[];
|
||||
static const int16_t FLASH rosterIdList[];
|
||||
static const int16_t HIGHFLASH routeIdList[];
|
||||
static const int16_t HIGHFLASH automationIdList[];
|
||||
static const int16_t HIGHFLASH rosterIdList[];
|
||||
static const FSH * getRouteDescription(int16_t id);
|
||||
static char getRouteType(int16_t id);
|
||||
static const FSH * getTurnoutDescription(int16_t id);
|
||||
@@ -137,6 +150,7 @@ private:
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
@@ -148,10 +162,12 @@ private:
|
||||
void kill(const FSH * reason=NULL,int operand=0);
|
||||
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
||||
void printMessage2(const FSH * msg);
|
||||
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
|
||||
uint16_t getOperand(byte n);
|
||||
|
||||
static bool diag;
|
||||
static const FLASH byte RouteCode[];
|
||||
static const FLASH int16_t SignalDefinitions[];
|
||||
static const HIGHFLASH byte RouteCode[];
|
||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||
static byte flags[MAX_FLAGS];
|
||||
static LookList * sequenceLookup;
|
||||
static LookList * onThrowLookup;
|
||||
@@ -161,7 +177,8 @@ private:
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
|
||||
static LookList * onChangeLookup;
|
||||
static LookList * onClockLookup;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021-2022 Chris Harlow
|
||||
* © 2020,2021 Chris Harlow. All rights reserved.
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -65,6 +65,7 @@
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLOCO
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
@@ -72,6 +73,7 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
@@ -85,9 +87,12 @@
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
@@ -110,6 +115,9 @@
|
||||
#undef SERIAL1
|
||||
#undef SERIAL2
|
||||
#undef SERIAL3
|
||||
#undef SERIAL4
|
||||
#undef SERIAL5
|
||||
#undef SERIAL6
|
||||
#undef SERVO
|
||||
#undef SERVO2
|
||||
#undef SERVO_TURNOUT
|
||||
@@ -175,6 +183,7 @@
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLOCO(loco_id)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
@@ -182,6 +191,7 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
@@ -192,12 +202,15 @@
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#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)
|
||||
@@ -220,6 +233,9 @@
|
||||
#define SERIAL1(msg)
|
||||
#define SERIAL2(msg)
|
||||
#define SERIAL3(msg)
|
||||
#define SERIAL4(msg)
|
||||
#define SERIAL5(msg)
|
||||
#define SERIAL6(msg)
|
||||
#define SERVO(id,position,profile)
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -55,6 +56,10 @@
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// helper macro to strip leading zeros off time inputs
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -73,14 +78,14 @@ void exrailHalSetup() {
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t FLASH RMFT2::routeIdList[]= {
|
||||
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
// Pass 2a create throttle automation list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
const int16_t FLASH RMFT2::automationIdList[]= {
|
||||
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
@@ -100,30 +105,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
|
||||
// Pass 4... Create Text sending functions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
const int StringMacroTracker1=__COUNTER__;
|
||||
#define THRUNGE(msg,mode) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=mode;\
|
||||
break;\
|
||||
}
|
||||
#undef BROADCAST
|
||||
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
||||
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
|
||||
#undef PARSE
|
||||
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
|
||||
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
|
||||
#undef PRINT
|
||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef SERIAL
|
||||
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||
#undef SERIAL1
|
||||
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
||||
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
|
||||
#undef SERIAL2
|
||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
||||
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
|
||||
#undef SERIAL3
|
||||
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
||||
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
|
||||
#undef SERIAL4
|
||||
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
|
||||
#undef SERIAL5
|
||||
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
|
||||
#undef SERIAL6
|
||||
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
|
||||
#undef LCD
|
||||
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
||||
#define LCD(id,msg) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=thrunge_lcd; \
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
|
||||
void RMFT2::printMessage(uint16_t id) {
|
||||
thrunger tmode;
|
||||
uint32_t strfar=0;
|
||||
byte lcdid=0;
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break ;
|
||||
}
|
||||
if (strfar) thrungeString(strfar,tmode,lcdid);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +187,7 @@ const byte RMFT2::rosterNameCount=0
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t FLASH RMFT2::rosterIdList[]={
|
||||
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
@@ -198,7 +227,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0,0 };
|
||||
|
||||
@@ -254,6 +283,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
|
||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
@@ -261,6 +291,7 @@ 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,
|
||||
@@ -272,11 +303,14 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||
#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),
|
||||
@@ -299,6 +333,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERIAL1(msg) PRINT(msg)
|
||||
#define SERIAL2(msg) PRINT(msg)
|
||||
#define SERIAL3(msg) PRINT(msg)
|
||||
#define SERIAL4(msg) PRINT(msg)
|
||||
#define SERIAL5(msg) PRINT(msg)
|
||||
#define SERIAL6(msg) PRINT(msg)
|
||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
@@ -323,7 +360,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
|
||||
// Build RouteCode
|
||||
const int StringMacroTracker2=__COUNTER__;
|
||||
const FLASH byte RMFT2::RouteCode[] = {
|
||||
const HIGHFLASH byte RMFT2::RouteCode[] = {
|
||||
#include "myAutomation.h"
|
||||
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2022 Bruno Sanches
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
@@ -36,8 +37,13 @@ EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
*/
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
singleton=new EthernetInterface();
|
||||
if (!singleton->connected) singleton=NULL;
|
||||
if (singleton!=NULL) {
|
||||
DIAG(F("Prog Error!"));
|
||||
return;
|
||||
}
|
||||
if ((singleton=new EthernetInterface()))
|
||||
return;
|
||||
DIAG(F("Ethernet not initialized"));
|
||||
};
|
||||
|
||||
|
||||
@@ -62,37 +68,33 @@ EthernetInterface::EthernetInterface()
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
DIAG(F("begin OK."));
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found"));
|
||||
return;
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found or W5100"));
|
||||
}
|
||||
|
||||
unsigned long startmilli = millis();
|
||||
while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection
|
||||
{
|
||||
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
|
||||
if (Ethernet.linkStatus() == LinkON)
|
||||
break;
|
||||
DIAG(F("Ethernet waiting for link (1sec) "));
|
||||
delay(1000);
|
||||
}
|
||||
// now we either do have link of we have a W5100
|
||||
// where we do not know if we have link. That's
|
||||
// the reason to now run checkLink.
|
||||
// CheckLinks sets up outboundRing if it does
|
||||
// not exist yet as well.
|
||||
checkLink();
|
||||
}
|
||||
|
||||
if (Ethernet.linkStatus() == LinkOFF) {
|
||||
DIAG(F("Ethernet cable not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
connected=true;
|
||||
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
/**
|
||||
* @brief Cleanup any resources
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
EthernetInterface::~EthernetInterface() {
|
||||
delete server;
|
||||
delete outboundRing;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,33 +103,73 @@ EthernetInterface::EthernetInterface()
|
||||
*/
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
if (!singleton) return;
|
||||
if (!singleton || (!singleton->checkLink()))
|
||||
return;
|
||||
|
||||
switch (Ethernet.maintain())
|
||||
{
|
||||
switch (Ethernet.maintain()) {
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("Ethernet Error: renewed fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Ethernet Error: rebind fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
|
||||
singleton->loop2();
|
||||
|
||||
}
|
||||
|
||||
void EthernetInterface::loop2()
|
||||
{
|
||||
/**
|
||||
* @brief Checks ethernet link cable status and detects when it connects / disconnects
|
||||
*
|
||||
* @return true when cable is connected, false otherwise
|
||||
*/
|
||||
bool EthernetInterface::checkLink() {
|
||||
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
|
||||
//if we are not connected yet, setup a new server
|
||||
if(!connected) {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
#ifdef IP_ADDRESS
|
||||
setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||
#endif
|
||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
// only create a outboundRing it none exists, this may happen if the cable
|
||||
// gets disconnected and connected again
|
||||
if(!outboundRing)
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
return true;
|
||||
} else { // connected
|
||||
DIAG(F("Ethernet cable disconnected"));
|
||||
connected=false;
|
||||
//clean up any client
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
|
||||
if(clients[socket].connected())
|
||||
clients[socket].stop();
|
||||
}
|
||||
// tear down server
|
||||
delete server;
|
||||
server = nullptr;
|
||||
LCD(4,F("IP: None"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EthernetInterface::loop2() {
|
||||
if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it
|
||||
if (Diag::ETHERNET) DIAG(F("No outboundRing"));
|
||||
return;
|
||||
}
|
||||
// get client from the server
|
||||
EthernetClient client = server->accept();
|
||||
|
||||
@@ -182,7 +224,9 @@ void EthernetInterface::loop()
|
||||
|
||||
// handle at most 1 outbound transmission
|
||||
int socketOut=outboundRing->read();
|
||||
if (socketOut>=0) {
|
||||
if (socketOut >= MAX_SOCK_NUM) {
|
||||
DIAG(F("Ethernet outboundRing socket=%d error"), socketOut);
|
||||
} else if (socketOut >= 0) {
|
||||
int count=outboundRing->count();
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
|
||||
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
|
||||
|
@@ -56,15 +56,16 @@ class EthernetInterface {
|
||||
static void loop();
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
void loop2();
|
||||
EthernetServer * server;
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
~EthernetInterface();
|
||||
void loop2();
|
||||
bool checkLink();
|
||||
EthernetServer * server = NULL;
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * outboundRing;
|
||||
|
||||
RingStream * outboundRing = NULL;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
57
FSH.h
57
FSH.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -34,32 +34,51 @@
|
||||
* PROGMEM use FLASH instead
|
||||
* pgm_read_byte_near use GETFLASH instead.
|
||||
* pgm_read_word_near use GETFLASHW instead.
|
||||
*
|
||||
* Also:
|
||||
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
|
||||
* GETHIGHFLASH,GETHIGHFLASHW to access them
|
||||
*
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
// AVR devices have flash memory mapped differently
|
||||
// progmem can be accessed by _near functions or _far
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define FLASH PROGMEM
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
||||
#define HIGHFLASH __attribute__((section(".fini2")))
|
||||
#define GETFARPTR(data) pgm_get_far_address(data)
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
|
||||
#else
|
||||
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
|
||||
// as there is no progmem above 32kb anyway.
|
||||
#define HIGHFLASH PROGMEM
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
|
||||
#ifdef F
|
||||
#undef F
|
||||
#endif
|
||||
#define F(str) (str)
|
||||
typedef char FSH;
|
||||
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
||||
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
|
||||
#define FLASH
|
||||
#define strlen_P strlen
|
||||
#define strcpy_P strcpy
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||
#ifdef FLASH
|
||||
#undef FLASH
|
||||
#endif
|
||||
#define FLASH PROGMEM
|
||||
#else
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||
#define FLASH PROGMEM
|
||||
#define F(str) (str)
|
||||
typedef char FSH;
|
||||
#define FLASH
|
||||
#define HIGHFLASH
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETFLASH(addr) (*(const byte *)(addr))
|
||||
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
|
||||
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "PORTX-HAL-20220918"
|
||||
#define GITHUB_SHA "devel-202302131946Z"
|
||||
|
@@ -113,7 +113,8 @@ void I2CManagerClass::I2C_init()
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_EnableIRQ(SERCOM3_IRQn);
|
||||
NVIC_SetPriority (SERCOM3_IRQn, 0); /* set Priority */
|
||||
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;
|
||||
|
52
IODevice.cpp
52
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
|
||||
@@ -195,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
|
||||
@@ -361,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() {
|
||||
@@ -467,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) {
|
||||
@@ -479,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;
|
||||
}
|
||||
|
16
IODevice.h
16
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();
|
||||
@@ -160,6 +161,8 @@ public:
|
||||
// once the GPIO port concerned has been read.
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -201,6 +204,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) {
|
||||
@@ -229,9 +236,6 @@ protected:
|
||||
// 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);
|
||||
|
||||
@@ -356,6 +360,7 @@ private:
|
||||
// Device-specific read functions.
|
||||
int _read(VPIN vpin) override;
|
||||
int _readAnalogue(VPIN vpin) override;
|
||||
int _configureAnalogIn(VPIN vpin) override;
|
||||
void _display() override;
|
||||
|
||||
|
||||
@@ -402,5 +407,8 @@ private:
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
#include "IO_EXIOExpander.h"
|
||||
|
||||
#endif // iodevice_h
|
||||
|
||||
#endif // iodevice_h
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2022, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
@@ -36,24 +36,31 @@
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* DFPlayer::create(3500, 5, Serial1);
|
||||
*
|
||||
* Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card;
|
||||
* Writing an analogue value 0-30 to the second pin will set the volume of the output;
|
||||
* Writing a digital value to the first pin will play or stop the file;
|
||||
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
|
||||
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
|
||||
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
|
||||
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
|
||||
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
|
||||
* Writing a digital value of 0 to any pin will stop the player;
|
||||
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
|
||||
*
|
||||
* From EX-RAIL, the following commands may be used:
|
||||
* SET(3500) -- starts playing the first file on the SD card
|
||||
* SET(3501) -- starts playing the second file on the SD card
|
||||
* SET(3500) -- starts playing the first file (file 1) on the SD card
|
||||
* SET(3501) -- starts playing the second file (file 2) on the SD card
|
||||
* etc.
|
||||
* RESET(3500) -- stops all playing on the player
|
||||
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
|
||||
* SERVO(3500,23,0) -- plays file 23 at current volume
|
||||
* SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum)
|
||||
* SERVO(3501,20,0) -- Sets the volume to 20
|
||||
* SERVO(3500,2,Instant) -- plays file 2 at current volume
|
||||
* SERVO(3501,20,Instant) -- Sets the volume to 20
|
||||
*
|
||||
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
|
||||
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
|
||||
* A 1k resistor in series with the module's RX terminal will alleviate this.
|
||||
*
|
||||
* Files on the SD card are numbered according to their order in the directory on the
|
||||
* card (as listed by the DIR command in Windows). This may not match the order of the files
|
||||
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
|
||||
* files be copied into an empty SDcard in the desired order, one at a time.
|
||||
*/
|
||||
|
||||
#ifndef IO_DFPlayer_h
|
||||
@@ -68,6 +75,19 @@ private:
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
|
||||
// When two commands are sent in quick succession, the device sometimes
|
||||
// fails to execute one. A delay is required between successive commands.
|
||||
// This could be implemented by buffering commands and outputting them
|
||||
// from the loop() function, but it would somewhat complicate the
|
||||
// driver. A simpler solution is to output a number of NUL pad characters
|
||||
// between successive command strings if there isn't sufficient elapsed time
|
||||
// between them. At 9600 baud, each pad character takes approximately
|
||||
// 1ms to complete. Experiments indicate that the minimum number of pads
|
||||
// for reliable operation is 17. This gives 17.7ms between the end of one
|
||||
// command and the beginning of the next, or 28ms between successive commands
|
||||
// being completed. I've allowed 20 characters, which is almost 21ms.
|
||||
const int numPadCharacters = 20; // Number of pad characters between commands
|
||||
|
||||
public:
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
|
||||
@@ -83,8 +103,10 @@ protected:
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
_serial->begin(9600);
|
||||
void _begin() override {
|
||||
_serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
|
||||
// Flush any data in input queue
|
||||
while (_serial->available()) _serial->read();
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
|
||||
// Send a query to the device to see if it responds
|
||||
@@ -94,10 +116,10 @@ protected:
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
||||
// Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF"
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
if (c == 0x7E)
|
||||
if (c == 0x7E && _inputIndex == 0)
|
||||
_inputIndex = 1;
|
||||
else if ((c==0xFF && _inputIndex==1)
|
||||
|| (c==0x3D && _inputIndex==3)
|
||||
@@ -124,8 +146,8 @@ protected:
|
||||
} else
|
||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
||||
}
|
||||
// Check if the initial prompt to device has timed out. Allow 1 second
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) {
|
||||
// Check if the initial prompt to device has timed out. Allow 5 seconds
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
|
||||
DIAG(F("DFPlayer device not responding on serial port"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
@@ -161,7 +183,7 @@ protected:
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
// Validate parameter.
|
||||
volume = min(30,volume);
|
||||
volume = min((uint8_t)30,volume);
|
||||
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
@@ -218,6 +240,7 @@ private:
|
||||
|
||||
void sendPacket(uint8_t command, uint16_t arg = 0)
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
uint8_t out[] = { 0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
@@ -231,7 +254,19 @@ private:
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Check how long since the last command was sent.
|
||||
// Each character takes approx 1ms at 9600 baud
|
||||
unsigned long minimumGap = numPadCharacters + sizeof(out);
|
||||
if (currentMillis - _commandSendTime < minimumGap) {
|
||||
// Output some pad characters to add an
|
||||
// artificial delay between commands
|
||||
for (int i=0; i<numPadCharacters; i++)
|
||||
_serial->write(0);
|
||||
}
|
||||
|
||||
// Now output the command
|
||||
_serial->write(out, sizeof(out));
|
||||
_commandSendTime = currentMillis;
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
|
128
IO_EXFastclock.h
Normal file
128
IO_EXFastclock.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* © 2022, Colin Murdoch. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
|
||||
*
|
||||
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_EXFastclock_h
|
||||
#define IO_EXFastclock_h
|
||||
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
bool FAST_CLOCK_EXISTS = true;
|
||||
|
||||
class EXFastClock : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
EXFastClock(uint8_t I2CAddress){
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(uint8_t _I2CAddress) {
|
||||
|
||||
DIAG(F("Checking for Clock"));
|
||||
// Start by assuming we will find the clock
|
||||
// Check if specified I2C address is responding (blocking operation)
|
||||
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
||||
uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress);
|
||||
DIAG(F("Clock check result - %d"), _checkforclock);
|
||||
// XXXX change thistosave2 bytes
|
||||
if (_checkforclock == 0) {
|
||||
FAST_CLOCK_EXISTS = true;
|
||||
//DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress);
|
||||
new EXFastClock(_I2CAddress);
|
||||
}
|
||||
else {
|
||||
FAST_CLOCK_EXISTS = false;
|
||||
//DIAG(F("No Fast Clock found"));
|
||||
LCD(6,F("CLOCK NOT FOUND"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _I2CAddress;
|
||||
|
||||
|
||||
// Initialisation of Fastclock
|
||||
void _begin() override {
|
||||
|
||||
if (FAST_CLOCK_EXISTS == true) {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
//LCD(6,F("CLOCK NOT FOUND"));
|
||||
DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processing loop to obtain clock time
|
||||
|
||||
void _loop(unsigned long currentMicros) override{
|
||||
|
||||
if (FAST_CLOCK_EXISTS==true) {
|
||||
uint8_t readBuffer[3];
|
||||
byte a,b;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
I2CManager.read(_I2CAddress, readBuffer, 3);
|
||||
// XXXX change this to save a few bytes
|
||||
a = readBuffer[0];
|
||||
b = readBuffer[1];
|
||||
//_clocktime = (a << 8) + b;
|
||||
//_clockrate = readBuffer[2];
|
||||
|
||||
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
|
||||
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
|
||||
|
||||
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
|
||||
// Clock interval is 60/ clockspeed i.e 60/b seconds
|
||||
delayUntil(currentMicros + ((60/b) * 1000000));
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Display EX-FastClock device driver info.
|
||||
void _display() {
|
||||
DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
263
IO_EXIOExpander.h
Normal file
263
IO_EXIOExpander.h
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of EX-CommandStation
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
|
||||
* This device driver will configure the device on startup, along with
|
||||
* interacting with the device for all input/output duties.
|
||||
*
|
||||
* To create EX-IOExpander devices, these are defined in myHal.cpp:
|
||||
* (Note the device driver is included by default)
|
||||
*
|
||||
* void halSetup() {
|
||||
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
|
||||
* EXIOExpander::create(800, 18, 0x65);
|
||||
* }
|
||||
*
|
||||
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
|
||||
* device in use. There is no way for the device driver to sanity check pins are used for the
|
||||
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
|
||||
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
|
||||
*/
|
||||
|
||||
#ifndef IO_EX_IOEXPANDER_H
|
||||
#define IO_EX_IOEXPANDER_H
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-IOExpander.
|
||||
*/
|
||||
class EXIOExpander : public IODevice {
|
||||
public:
|
||||
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
Fast = 1, // Takes around 500ms end-to-end
|
||||
Medium = 2, // 1 second end-to-end
|
||||
Slow = 3, // 2 seconds end-to-end
|
||||
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
|
||||
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
|
||||
};
|
||||
|
||||
static void create(VPIN vpin, int nPins, uint8_t i2cAddress) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_i2cAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void _begin() {
|
||||
// Initialise EX-IOExander device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
_command4Buffer[0] = EXIOINIT;
|
||||
_command4Buffer[1] = _nPins;
|
||||
_command4Buffer[2] = _firstVpin & 0xFF;
|
||||
_command4Buffer[3] = _firstVpin >> 8;
|
||||
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
|
||||
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4);
|
||||
if (_receive3Buffer[0] == EXIOPINS) {
|
||||
_numDigitalPins = _receive3Buffer[1];
|
||||
_numAnaloguePins = _receive3Buffer[2];
|
||||
_digitalPinBytes = (_numDigitalPins + 7)/8;
|
||||
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
|
||||
_analoguePinBytes = _numAnaloguePins * 2;
|
||||
_analogueInputStates = (byte*) calloc(_analoguePinBytes, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
} else {
|
||||
DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
// We now need to retrieve the analogue pin map
|
||||
_command1Buffer[0] = EXIOINITA;
|
||||
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1);
|
||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||
_command1Buffer[0] = EXIOVER;
|
||||
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"),
|
||||
_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
||||
if (paramCount != 1) return false;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (configType == CONFIGURE_INPUT) {
|
||||
bool pullup = params[0];
|
||||
_digitalOutBuffer[0] = EXIODPUP;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = pullup;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
||||
if (_command1Buffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Analogue input pin configuration, used to enable on EX-IOExpander device
|
||||
int _configureAnalogIn(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
_command2Buffer[0] = EXIOENAN;
|
||||
_command2Buffer[1] = pin;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2);
|
||||
if (_command1Buffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
(void)currentMicros; // remove warning
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
_command1Buffer[0] = EXIORDD;
|
||||
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1);
|
||||
_command1Buffer[0] = EXIORDAN;
|
||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
|
||||
}
|
||||
|
||||
// Obtain the correct analogue input value
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t _pinLSBByte;
|
||||
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
|
||||
if (_analoguePinMap[aPin] == pin) {
|
||||
_pinLSBByte = aPin * 2;
|
||||
}
|
||||
}
|
||||
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||
}
|
||||
|
||||
// Obtain the correct digital input value
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t pinByte = pin / 8;
|
||||
bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8);
|
||||
return value;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
_digitalOutBuffer[0] = EXIOWRD;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = value;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
_servoBuffer[0] = EXIOWRAN;
|
||||
_servoBuffer[1] = pin;
|
||||
_servoBuffer[2] = value & 0xFF;
|
||||
_servoBuffer[3] = value >> 8;
|
||||
_servoBuffer[4] = profile;
|
||||
_servoBuffer[5] = duration & 0xFF;
|
||||
_servoBuffer[6] = duration >> 8;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"),
|
||||
_i2cAddress, _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
uint8_t _i2cAddress;
|
||||
uint8_t _numDigitalPins = 0;
|
||||
uint8_t _numAnaloguePins = 0;
|
||||
byte _digitalOutBuffer[3];
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
byte* _digitalInputStates;
|
||||
byte* _analogueInputStates;
|
||||
uint8_t _digitalPinBytes = 0;
|
||||
uint8_t _analoguePinBytes = 0;
|
||||
byte _command1Buffer[1];
|
||||
byte _command2Buffer[2];
|
||||
byte _command4Buffer[4];
|
||||
byte _receive3Buffer[3];
|
||||
byte _servoBuffer[7];
|
||||
uint8_t* _analoguePinMap;
|
||||
|
||||
// EX-IOExpander protocol flags
|
||||
enum {
|
||||
EXIOINIT = 0xE0, // Flag to initialise setup procedure
|
||||
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
|
||||
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
|
||||
EXIOVER = 0xE3, // Flag to get version
|
||||
EXIORDAN = 0xE4, // Flag to read an analogue input
|
||||
EXIOWRD = 0xE5, // Flag for digital write
|
||||
EXIORDD = 0xE6, // Flag to read digital input
|
||||
EXIOENAN = 0xE7, // Flag to enable an analogue pin
|
||||
EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings
|
||||
EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers
|
||||
EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM)
|
||||
EXIOERR = 0xEF, // Flag we've received an error
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
@@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initialisation of TurntableEX
|
||||
// Initialisation of EXTurntable
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
@@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
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"),
|
||||
DIAG(F("EX-Turntable 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);
|
||||
@@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
|
||||
// Display Turnetable-EX device driver info.
|
||||
void EXTurntable::_display() {
|
||||
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -138,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))
|
||||
@@ -186,4 +186,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_HCSR04_H
|
||||
#endif //IO_HCSR04_H
|
||||
|
127
IO_RotaryEncoder.h
Normal file
127
IO_RotaryEncoder.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of EX-CommandStation
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
|
||||
*
|
||||
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
|
||||
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
|
||||
*
|
||||
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
|
||||
* can be tested in EX-RAIL with:
|
||||
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
|
||||
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
|
||||
*
|
||||
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
||||
#ifndef IO_ROTARYENCODER_H
|
||||
#define IO_ROTARYENCODER_H
|
||||
|
||||
#include "EXRAIL2.h"
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Initiate the device
|
||||
void _begin() {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
byte _getVersion[1] = {RE_VER};
|
||||
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
_buffer[0] = RE_OP;
|
||||
I2CManager.write(_I2CAddress, _buffer, 1);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
I2CManager.read(_I2CAddress, _buffer, 1);
|
||||
_position = _buffer[0];
|
||||
// This here needs to have a change check, ie. position is a different value.
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_position != _previousPosition) {
|
||||
_previousPosition = _position;
|
||||
RMFT2::changeEvent(_firstVpin,1);
|
||||
} else {
|
||||
RMFT2::changeEvent(_firstVpin,0);
|
||||
}
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000);
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _position;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin == _firstVpin + 1) {
|
||||
byte _feedbackBuffer[2] = {RE_OP, value};
|
||||
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
uint8_t _I2CAddress;
|
||||
int8_t _position;
|
||||
int8_t _previousPosition = 0;
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _buffer[1];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
|
||||
enum {
|
||||
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
|
||||
RE_OP = 0xA1, // Flag for normal operation
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
172
IO_duinoNodes.h
Normal file
172
IO_duinoNodes.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* © 2022, Chris Harlow. All rights reserved.
|
||||
* Based on original by: Robin Simonds, Beagle Bay Inc
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef IO_duinoNodes_h
|
||||
#define IO_duinoNodes_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
|
||||
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
|
||||
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
|
||||
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
|
||||
|
||||
|
||||
|
||||
class IO_duinoNodes : public IODevice {
|
||||
|
||||
public:
|
||||
IO_duinoNodes(VPIN firstVpin, int nPins,
|
||||
byte clockPin, byte latchPin, byte dataPin,
|
||||
const byte* pinmap) :
|
||||
IODevice(firstVpin, nPins) {
|
||||
|
||||
_latchPin=latchPin;
|
||||
_clockPin=clockPin;
|
||||
_dataPin=dataPin;
|
||||
_pinMap=pinmap;
|
||||
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
|
||||
_pinValues=(byte*) calloc(_nShiftBytes,1);
|
||||
// Connect to HAL so my _write, _read and _loop will be called as required.
|
||||
IODevice::addDevice(this);
|
||||
}
|
||||
|
||||
// Called by HAL to start handling this device
|
||||
void _begin() override {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
pinMode(_latchPin,OUTPUT);
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_pinMap) _loopInput(currentMicros);
|
||||
else if (_xmitPending) _loopOutput();
|
||||
}
|
||||
|
||||
void _loopInput(unsigned long currentMicros) {
|
||||
|
||||
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
|
||||
_prevMicros=currentMicros;
|
||||
|
||||
//set latch to HIGH to freeze & store parallel data
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
//set latch to LOW to enable the data to be transmitted serially
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
|
||||
// stream in the bitmap using mapping order provided at constructor
|
||||
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
|
||||
byte newByte=0;
|
||||
for (int xmitBit=0;xmitBit<8; xmitBit++) {
|
||||
ArduinoPins::fastWriteDigital(_clockPin, LOW);
|
||||
delayMicroseconds(1);
|
||||
bool data = ArduinoPins::fastReadDigital(_dataPin);
|
||||
byte map=_pinMap[xmitBit];
|
||||
if (data) newByte |= map;
|
||||
else newByte &= ~map;
|
||||
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
_pinValues[xmitByte]=newByte;
|
||||
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
|
||||
}
|
||||
}
|
||||
|
||||
void _loopOutput() {
|
||||
// stream out the bitmap (highest pin first)
|
||||
_xmitPending=false;
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
|
||||
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clockPin,LOW);
|
||||
}
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
bool b=DN_GET_BIT(pin);
|
||||
return b?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
bool oldval=DN_GET_BIT(pin);
|
||||
bool newval=value!=0;
|
||||
if (newval==oldval) return; // no change
|
||||
if (newval) DN_SET_BIT(pin);
|
||||
else DN_CLR_BIT(pin);
|
||||
_xmitPending=true; // shift register will be sent on next _loop()
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
||||
_pinMap?F("IN"):F("OUT"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
}
|
||||
|
||||
private:
|
||||
static const unsigned long POLL_MICROS=100000; // 10 / S
|
||||
unsigned long _prevMicros;
|
||||
int _nShiftBytes=0;
|
||||
VPIN _latchPin,_clockPin,_dataPin;
|
||||
byte* _pinValues;
|
||||
bool _xmitPending; // Only relevant in output mode
|
||||
const byte* _pinMap; // NULL in output mode
|
||||
};
|
||||
|
||||
class IO_DNIN8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0,7,6,5,1,2,3,4
|
||||
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class IO_DNIN8K {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
|
||||
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
};
|
||||
|
||||
class IO_DNOU8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
2
LCN.cpp
2
LCN.cpp
@@ -43,7 +43,7 @@ void LCN::loop() {
|
||||
|
||||
while (stream->available()) {
|
||||
int ch = stream->read();
|
||||
if (ch >= 0 && ch <= '9') { // accumulate id value
|
||||
if (ch >= '0' && ch <= '9') { // accumulate id value
|
||||
id = 10 * id + ch - '0';
|
||||
}
|
||||
else if (ch == 't' || ch == 'T') { // Turnout opcodes
|
||||
|
186
MotorDriver.cpp
186
MotorDriver.cpp
@@ -26,28 +26,9 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
#include <soc/sens_struct.h>
|
||||
#undef ADC_INPUT_MAX_VALUE
|
||||
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||
|
||||
int IRAM_ATTR local_adc1_get_raw(int channel) {
|
||||
uint16_t adc_value;
|
||||
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas_start1.meas1_start_sar = 0;
|
||||
SENS.sar_meas_start1.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas_start1.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas_start1.meas1_data_sar;
|
||||
return adc_value;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
@@ -104,21 +85,12 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
||||
pinMode(brakePin, OUTPUT);
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
pinMode(currentPin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
|
||||
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
|
||||
#else
|
||||
pinMode(currentPin, INPUT);
|
||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||
#endif
|
||||
senseOffset = ADCee::init(currentPin);
|
||||
}
|
||||
|
||||
faultPin=fault_pin;
|
||||
@@ -133,7 +105,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADC_INPUT_MAX_VALUE) {
|
||||
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
|
||||
@@ -141,8 +113,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
// maximum value instead.
|
||||
|
||||
// DIAG(F("Changing short detection value from %d to %d mA"),
|
||||
// raw2mA(rawCurrentTripValue), raw2mA(ADC_INPUT_MAX_VALUE-senseOffset));
|
||||
rawCurrentTripValue=ADC_INPUT_MAX_VALUE-senseOffset;
|
||||
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
|
||||
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
|
||||
}
|
||||
|
||||
if (currentPin==UNUSED_PIN)
|
||||
@@ -174,6 +146,8 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (DCinuse)
|
||||
setDCSignal(curSpeedCode);
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
@@ -181,6 +155,13 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
if (DCinuse) {
|
||||
// remember current (DC) speed
|
||||
// but set PWM to zero/stop
|
||||
byte s = curSpeedCode;
|
||||
setDCSignal(128);
|
||||
curSpeedCode = s;
|
||||
}
|
||||
}
|
||||
powerMode=mode;
|
||||
}
|
||||
@@ -209,24 +190,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.
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
|
||||
#else
|
||||
noInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
interrupts();
|
||||
#endif
|
||||
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);
|
||||
@@ -234,32 +209,108 @@ int MotorDriver::getCurrentRaw() {
|
||||
|
||||
}
|
||||
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
#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,
|
||||
349, 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) {
|
||||
curSpeedCode = speedcode;
|
||||
DCinuse = true;
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
if (powerPin == 3 || powerPin == 11)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // D3, D11: 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
|
||||
// As timer 0 is the system timer, we leave it alone
|
||||
//TCCR0B = (TCCR0B & B11111000) | B00000100; // D4, D13 : 122 or 244Hz?
|
||||
// As we use timer 1 for DCC we leave it alone
|
||||
//TCCR1B = (TCCR1B & B11111000) | B00000100; // D11, D12 : 122Hz
|
||||
switch(powerPin) {
|
||||
case 9:
|
||||
case 10:
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // D9, D10 : 122Hz
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 5:
|
||||
TCCR3B = (TCCR3B & B11111000) | B00000100; // D2, D3, D5 : 122Hz but maxcount and thus divisor differs
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
TCCR4B = (TCCR4B & B11111000) | B00000100; // D6, D7, D8 : 122Hz but maxcount and thus divisor differs
|
||||
break;
|
||||
case 44:
|
||||
case 45:
|
||||
case 46:
|
||||
TCCR5B = (TCCR5B & B11111000) | B00000100; // D44,D45,D46: 122Hz but maxcount and thus divisor differs
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
byte pwmratio;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
{
|
||||
int f = 131;
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
DCCEXanalogWriteFrequency(powerPin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) pwmratio = 0;
|
||||
else if (tSpeed >= 127) pwmratio = 255;
|
||||
else pwmratio = 2 * tSpeed;
|
||||
if (invertPower)
|
||||
pwmratio =255-pwmratio;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(powerPin,pwmratio);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
analogWrite(powerPin,pwmratio);
|
||||
#endif
|
||||
//DIAG(F("DCSignal %d"), speedcode);
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
@@ -287,23 +338,12 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
}
|
||||
}
|
||||
|
||||
int MotorDriver::getCurrentRawInInterrupt() {
|
||||
|
||||
// IMPORTANT: This function must be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
#ifdef ARDUINO_ARCH_ESP32 //On ESP we do all in loop() instead of in interrupt
|
||||
return getCurrentRaw();
|
||||
#else
|
||||
return analogRead(currentPin)-senseOffset;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
|
||||
return (int32_t)raw * senseFactorInternal / senseScale;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -146,26 +146,27 @@ class MotorDriver {
|
||||
void setDCSignal(byte speedByte);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
pinMode(brakePin, OUTPUT);
|
||||
pinMode(powerPin, OUTPUT);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
ledcDetachPin(brakePin);
|
||||
ledcDetachPin(powerPin);
|
||||
#else
|
||||
setDCSignal(128);
|
||||
#endif
|
||||
DCinuse = false;
|
||||
};
|
||||
int getCurrentRaw();
|
||||
int getCurrentRawInInterrupt();
|
||||
int getCurrentRaw(bool fromISR=false);
|
||||
unsigned int raw2mA( int raw);
|
||||
unsigned int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
inline bool powerPinCanPWM() {
|
||||
#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)));
|
||||
#ifdef digitalPinHasPWM
|
||||
return digitalPinHasPWM(powerPin);
|
||||
#else
|
||||
return (brakePin<14 && brakePin >1);
|
||||
#warning No good digitalPinHasPWM doing approximation
|
||||
return (powerPin<14 && powerPin >1);
|
||||
#endif //digitalPinToTimer
|
||||
#endif //ESP32/ARM
|
||||
}
|
||||
@@ -183,6 +184,10 @@ class MotorDriver {
|
||||
isProgTrack = on;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
#endif
|
||||
private:
|
||||
bool isProgTrack = false; // tells us if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
@@ -212,6 +217,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;
|
||||
|
||||
@@ -225,6 +234,7 @@ class MotorDriver {
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
bool DCinuse = false;
|
||||
byte curSpeedCode = 0;
|
||||
};
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* (c) 2020 Chris Harlow. All rights reserved.
|
||||
@@ -59,15 +59,16 @@
|
||||
|
||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
#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, 1.95, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 1500, 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
|
||||
@@ -157,4 +158,31 @@
|
||||
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, A4, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
|
||||
//
|
||||
#endif
|
||||
|
39
Release_Notes/duinoNodes.md
Normal file
39
Release_Notes/duinoNodes.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Using Lew's Duino Gear boards:
|
||||
|
||||
1. DNIN8 Input
|
||||
This is a shift-register implementation of a digital input collector.
|
||||
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
|
||||
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
|
||||
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
|
||||
|
||||
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
|
||||
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
|
||||
|
||||
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
|
||||
|
||||
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
|
||||
|
||||
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
|
||||
|
||||
|
||||
DNOU8 works the same way,
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.
|
@@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
|
||||
}
|
||||
|
||||
size_t RingStream::printFlash(const FSH * flashBuffer) {
|
||||
// This function does not work on a 32 bit processor where the runtime
|
||||
// sometimes misrepresents the pointer size in uintptr_t.
|
||||
// In any case its not really necessary in a 32 bit processor because
|
||||
// we have adequate ram.
|
||||
if (sizeof(void*)>2) return print(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.
|
||||
@@ -107,8 +114,11 @@ int RingStream::read() {
|
||||
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
|
||||
if (sizeof(void*)>2) {
|
||||
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
|
||||
return '?';
|
||||
}
|
||||
// read address bytes LSB first (size depends on CPU)
|
||||
uintptr_t iFlash=0;
|
||||
for (byte f=0; f<sizeof(iFlash); f++) {
|
||||
@@ -120,10 +130,6 @@ int RingStream::read() {
|
||||
_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() {
|
||||
@@ -189,11 +195,6 @@ 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
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ class RingStream : public Print {
|
||||
|
||||
public:
|
||||
RingStream( const uint16_t len);
|
||||
static const int THIS_IS_A_RINGSTREAM=77;
|
||||
static const int THIS_IS_A_RINGSTREAM=777;
|
||||
virtual size_t write(uint8_t b);
|
||||
|
||||
// This availableForWrite function is subverted from its original intention so that a caller
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
@@ -51,6 +51,18 @@ void SerialManager::init() {
|
||||
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);
|
||||
|
@@ -32,7 +32,7 @@ class StringBuffer : public Print {
|
||||
private:
|
||||
static const int buffer_max=64; // enough for long text msgs to throttles
|
||||
int16_t _pos_write;
|
||||
char _buffer[buffer_max+1];
|
||||
char _buffer[buffer_max+2];
|
||||
};
|
||||
|
||||
#endif
|
@@ -70,7 +70,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
char* flash=(char*)format;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
if (c=='\0') return;
|
||||
if (c=='\0') break; // to va_end()
|
||||
if(c!='%') { stream->print(c); continue; }
|
||||
|
||||
bool formatContinues=false;
|
||||
@@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
{
|
||||
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.
|
||||
@@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
|
||||
((RingStream *)stream)->printFlash(flash);
|
||||
else
|
||||
#endif
|
||||
#endif
|
||||
stream->print(flash);
|
||||
break;
|
||||
}
|
||||
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); 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;
|
||||
@@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
|
||||
case '\r': stream->print(F("\\r")); break;
|
||||
case '\0': stream->print(F("\\0")); return;
|
||||
case '\t': stream->print(F("\\t")); break;
|
||||
case '\\': stream->print(F("\\")); break;
|
||||
default: stream->print(c);
|
||||
case '\\': stream->print(F("\\\\")); break;
|
||||
default: stream->write(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,49 @@ int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
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
|
||||
@@ -134,10 +177,10 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
// DC tracks require a motorDriver that can set power pin PWM
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
&& !track[trackToSet]->powerPinCanPWM()) {
|
||||
DIAG(F("Power pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,7 +218,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// EXT is a special case where the signal pin is
|
||||
@@ -353,7 +395,6 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
|
@@ -27,8 +27,9 @@
|
||||
#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;
|
||||
@@ -75,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);
|
||||
|
@@ -171,9 +171,14 @@ public:
|
||||
// Save all turnout definitions
|
||||
static void store();
|
||||
#endif
|
||||
static void printAll(Print *stream) {
|
||||
static bool printAll(Print *stream) {
|
||||
bool gotOne=false;
|
||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
if (!tt->isHidden()) {
|
||||
gotOne=true;
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
return gotOne;
|
||||
}
|
||||
|
||||
|
||||
|
331
WiThrottle.cpp
331
WiThrottle.cpp
@@ -63,58 +63,6 @@
|
||||
|
||||
WiThrottle * WiThrottle::firstThrottle=NULL;
|
||||
|
||||
static uint8_t xstrcmp(const char *s1, const char *s2) {
|
||||
while(*s1 != '\0' && *s2 != '\0') {
|
||||
if (*s1 != *s2) return 1;
|
||||
s1++;
|
||||
s2++;
|
||||
}
|
||||
if (*s1 == '\0' && *s2 == '\0')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void WiThrottle::findUniqThrottle(int id, char *u) {
|
||||
WiThrottle *wtmyid = NULL;
|
||||
WiThrottle *wtmyuniq = NULL;
|
||||
u[16] = '\0';
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
//DIAG(F("looking at %d as %s"),wt->clientid, wt->uniq);
|
||||
if (wtmyid == NULL && wt->clientid == id)
|
||||
wtmyid = wt;
|
||||
if (wtmyuniq == NULL && xstrcmp(u, wt->uniq) == 0)
|
||||
wtmyuniq = wt;
|
||||
}
|
||||
if (wtmyid == NULL) { // should not happen
|
||||
DIAG(F("Did not find my own wiThrottle handle"));
|
||||
return;
|
||||
}
|
||||
if (wtmyid == wtmyuniq) { // all well, just return;
|
||||
return;
|
||||
}
|
||||
if (wtmyuniq == NULL) { // register uniq in the found id
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
|
||||
return;
|
||||
}
|
||||
// do the copy (all other options above)
|
||||
for(int n=0; n < MAX_MY_LOCO; n++)
|
||||
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
|
||||
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
|
||||
wtmyid->heartBeat = wtmyuniq->heartBeat;
|
||||
wtmyid->initSent = wtmyuniq->initSent;
|
||||
wtmyid->exRailSent = wtmyuniq->exRailSent;
|
||||
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
|
||||
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
|
||||
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE)
|
||||
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
|
||||
forget(wtmyuniq->clientid); // do not use wtmyid after this
|
||||
}
|
||||
|
||||
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==wifiClient) return wt;
|
||||
@@ -124,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
void WiThrottle::forget( byte clientId) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==clientId) {
|
||||
DIAG(F("Withrottle client %d dropped"),clientId);
|
||||
delete wt;
|
||||
break;
|
||||
}
|
||||
@@ -148,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
|
||||
nextThrottle=firstThrottle;
|
||||
firstThrottle= this;
|
||||
clientid=wificlientid;
|
||||
initSent=false; // prevent sending heartbeats before connection completed
|
||||
heartBeatEnable=false; // until client turns it on
|
||||
turnoutListHash = -1; // make sure turnout list is sent once
|
||||
exRailSent=false;
|
||||
mostRecentCab=0;
|
||||
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
|
||||
}
|
||||
@@ -176,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
|
||||
heartBeat=millis();
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||
|
||||
// On first few commands, send turnout, roster and routes
|
||||
if (introSent) {
|
||||
if (!turnoutsSent) sendTurnouts(stream);
|
||||
else if(!rosterSent) sendRoster(stream);
|
||||
else if (!routesSent) sendRoutes(stream);
|
||||
else if (!heartrateSent) {
|
||||
heartrateSent=true;
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
|
||||
|
||||
if (initSent) {
|
||||
// Send turnout list if changed since last sent (will replace list on client)
|
||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||
}
|
||||
|
||||
else if (!exRailSent) {
|
||||
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
||||
exRailSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
for (byte pass=0;pass<2;pass++) {
|
||||
// first pass automations, second pass routes.
|
||||
for (int ix=0;;ix++) {
|
||||
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
||||
pass?'A':'R',id,desc, pass?'4':'2');
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#endif
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
}
|
||||
break;
|
||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
|
||||
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
||||
break;
|
||||
case 'M': // multithrottle
|
||||
multithrottle(stream, cmd);
|
||||
break;
|
||||
case 'H': // send initial connection info after receiving "HU" message
|
||||
if (cmd[1] == 'U') {
|
||||
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
#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);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
stream->write('\n'); // end roster
|
||||
#endif
|
||||
|
||||
|
||||
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
|
||||
initSent = true;
|
||||
if (cmd[1] == 'U') {
|
||||
sendIntro(stream);
|
||||
}
|
||||
break;
|
||||
case 'Q': //
|
||||
@@ -306,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||
}
|
||||
}
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
|
||||
delete this;
|
||||
break;
|
||||
}
|
||||
@@ -367,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
}
|
||||
//use first empty "slot" on this client's list, will be added to DCC registration list
|
||||
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
int fkeys=29;
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||
if (!functionNames) {
|
||||
// no roster, use presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||
}
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
sendFunctions(stream,loco);
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
|
||||
@@ -529,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
|
||||
// for each WiThrottle, check the heartbeat and broadcast needed
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
wt->checkHeartbeat(stream);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
@@ -544,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
|
||||
}
|
||||
}
|
||||
//haba no, not necessary the only throttle and it may come back
|
||||
//delete this;
|
||||
// if it does come back, the throttle should re-acquire
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -640,9 +488,128 @@ 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"));
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::sendIntro(Print* stream) {
|
||||
introSent=true;
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
|
||||
}
|
||||
|
||||
void WiThrottle::sendTurnouts(Print* stream) {
|
||||
turnoutsSent=true;
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
void WiThrottle::sendRoster(Print* stream) {
|
||||
rosterSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
void WiThrottle::sendRoutes(Print* stream) {
|
||||
routesSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
|
||||
}
|
||||
// second pass routes.
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
int16_t locoid=myLocos[loco].cab;
|
||||
int fkeys=29;
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||
if (!functionNames) {
|
||||
// no roster, use non-exrail presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||
}
|
||||
}
|
||||
|
16
WiThrottle.h
16
WiThrottle.h
@@ -45,7 +45,8 @@ class WiThrottle {
|
||||
~WiThrottle();
|
||||
|
||||
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
|
||||
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
|
||||
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
|
||||
static WiThrottle* firstThrottle;
|
||||
static int getInt(byte * cmd);
|
||||
@@ -61,10 +62,12 @@ class WiThrottle {
|
||||
MYLOCO myLocos[MAX_MY_LOCO];
|
||||
bool heartBeatEnable;
|
||||
unsigned long heartBeat;
|
||||
bool initSent; // valid connection established
|
||||
bool exRailSent; // valid connection established
|
||||
bool introSent=false;
|
||||
bool turnoutsSent=false;
|
||||
bool rosterSent=false;
|
||||
bool routesSent=false;
|
||||
bool heartrateSent=false;
|
||||
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);
|
||||
@@ -74,6 +77,11 @@ class WiThrottle {
|
||||
void accessory(RingStream *, byte* cmd);
|
||||
void checkHeartbeat(RingStream * stream);
|
||||
void markForBroadcast2(int cab);
|
||||
void sendIntro(Print * stream);
|
||||
void sendTurnouts(Print * stream);
|
||||
void sendRoster(Print * stream);
|
||||
void sendRoutes(Print * stream);
|
||||
void sendFunctions(Print* stream, byte loco);
|
||||
// callback stuff to support prog track acquire
|
||||
static RingStream * stashStream;
|
||||
static WiThrottle * stashInstance;
|
||||
|
@@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
|
||||
}
|
||||
|
||||
|
||||
if (pendingCipsend) {
|
||||
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
|
||||
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||
pendingCipsend=false;
|
||||
@@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||
|
||||
if (ch=='S') { // SEND OK probably
|
||||
loopState=SKIPTOEND;
|
||||
lastCIPSEND=0; // no need to wait next time
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
|
||||
pendingCipsend=(clientPendingCIPSEND>=0);
|
||||
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
|
||||
loopState=SKIPTOEND;
|
||||
break;
|
||||
}
|
||||
|
@@ -68,7 +68,9 @@ class WifiInboundHandler {
|
||||
Stream * wifiStream;
|
||||
|
||||
static const int INBOUND_RING = 512;
|
||||
static const int OUTBOUND_RING = 2048;
|
||||
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
|
||||
|
||||
static const int CIPSENDgap=100; // millis() between retries of cipsend.
|
||||
|
||||
RingStream * inboundRing;
|
||||
RingStream * outboundRing;
|
||||
@@ -79,5 +81,7 @@ class WifiInboundHandler {
|
||||
int clientPendingCIPSEND=-1;
|
||||
int currentReplySize;
|
||||
bool pendingCipsend;
|
||||
uint32_t lastCIPSEND=0; // millis() of previous cipsend
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
|
||||
while (wifiStream->available()) stream->write(wifiStream->read());
|
||||
if (stream->available()) {
|
||||
int cx=stream->read();
|
||||
// A newline followed by !!! is an exit
|
||||
// A newline followed by ! is an exit
|
||||
if (cx=='\n' || cx=='\r') startOfLine=true;
|
||||
else if (startOfLine && cx=='!') break;
|
||||
else startOfLine=false;
|
||||
stream->write(cx);
|
||||
wifiStream->write(cx);
|
||||
}
|
||||
}
|
||||
@@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
|
||||
char *locator = (char *)waitfor;
|
||||
DIAG(F("Wifi Check: [%E]"), waitfor);
|
||||
while ( millis() - startTime < timeout) {
|
||||
while (wifiStream->available()) {
|
||||
int ch = wifiStream->read();
|
||||
int nextchar;
|
||||
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
|
||||
char ch = (char)nextchar;
|
||||
if (echo) {
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else USB_SERIAL.print((char)ch);
|
||||
else USB_SERIAL.print(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
|
||||
@@ -143,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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -188,14 +192,18 @@ 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
|
||||
@@ -216,4 +224,8 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
|
||||
// FastClock Enabler
|
||||
// To build the FastClock code into the CS please uncomment the line below
|
||||
//#define USEFASTCLOCK
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
12
defines.h
12
defines.h
@@ -3,7 +3,7 @@
|
||||
* © 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
|
||||
@@ -114,8 +114,12 @@
|
||||
#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
|
||||
@@ -145,6 +149,12 @@
|
||||
#define CPU_TYPE_ERROR
|
||||
#endif
|
||||
|
||||
// replace board type if provided by compiler
|
||||
#ifdef BOARD_NAME
|
||||
#undef ARDUINO_TYPE
|
||||
#define ARDUINO_TYPE BOARD_NAME
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WIFI_ON: All prereqs for running with WIFI are met
|
||||
|
127
installer.sh
Executable file
127
installer.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/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 `basename "$DCCEXGITURL"` ; then
|
||||
: assume we are almost there
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
if test -d .git ; then
|
||||
: assume we are right here
|
||||
git pull
|
||||
else
|
||||
git clone "$DCCEXGITURL"
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
|
||||
# prepare versions
|
||||
VERSIONS=/tmp/versions.$$
|
||||
git tag --sort=v:refname | grep Prod | tail -1 > $VERSIONS
|
||||
echo master >> $VERSIONS
|
||||
git tag --sort=v:refname | grep Devel | tail -1 >> $VERSIONS
|
||||
echo devel >> $VERSIONS
|
||||
|
||||
# ask user what version to use
|
||||
echo "What version to use? (give line number) If in doubt, use 1"
|
||||
cat -n $VERSIONS
|
||||
echo -n "> "
|
||||
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $VERSIONS`
|
||||
git checkout $LINE
|
||||
|
||||
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 > /dev/null # download missing components
|
||||
$ACLI board list | grep serial > $BOARDS # real run
|
||||
if test -s $BOARDS ; then
|
||||
: all well
|
||||
else
|
||||
echo "$ACLI: No boards found"
|
||||
exit 255
|
||||
fi
|
||||
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"
|
@@ -20,7 +20,8 @@
|
||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||
#include "IO_DFPlayer.h" // MP3 sound player
|
||||
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
@@ -160,6 +161,63 @@ void halSetup() {
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-Turntable turntable instance.
|
||||
//=======================================================================
|
||||
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=600
|
||||
// Number of VPINs=1 (Note there is no reason to change this)
|
||||
// I2C address=0x60
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
|
||||
|
||||
//EXTurntable::create(600, 1, 0x60);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-IOExpander instance.
|
||||
//=======================================================================
|
||||
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=an available Vpin
|
||||
// Number of VPINs=pin count (must match device in use as per documentation)
|
||||
// I2C address=an available I2C address (default 0x65)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
|
||||
// The example is for an Arduino Nano.
|
||||
|
||||
//EXIOExpander::create(800, 18, 0x65);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a rotary encoder instance.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// firstVpin = First available Vpin to allocate
|
||||
// numPins= Number of Vpins to allocate, can be either 1 or 2
|
||||
// i2cAddress = Available I2C address (default 0x70)
|
||||
|
||||
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
||||
//RotaryEncoder::create(700, 1, 0x70);
|
||||
//RotaryEncoder::create(701, 2, 0x71);
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-FastClock instance.
|
||||
//=======================================================================
|
||||
// EXFastCLock::create(I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
//
|
||||
// I2C address=0x55 (decimal 85)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default.
|
||||
|
||||
|
||||
// EXFastClock::create(0x55);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -18,7 +18,8 @@ default_envs =
|
||||
samd21-dev-usb
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-STM32F411RE
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
@@ -50,18 +51,6 @@ 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_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
board = megaatmega2560
|
||||
@@ -108,6 +97,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -155,6 +145,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:nano]
|
||||
platform = atmelavr
|
||||
@@ -172,12 +163,21 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Nucleo-STM32F411RE]
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F446RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
@@ -185,7 +185,7 @@ monitor_echo = yes
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -193,7 +193,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -201,7 +201,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -209,7 +209,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -217,6 +217,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
lib_ignore =
|
||||
|
||||
|
23
version.h
23
version.h
@@ -4,7 +4,28 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.4"
|
||||
#define VERSION "4.2.17"
|
||||
// 4.2.17 LCN bugfix
|
||||
// 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software
|
||||
// 4.2.15 Add basic experimental PWM support to EX-IOExpander
|
||||
// EX-IOExpander 0.0.14 minimum required
|
||||
// 4.2.14 STM32F4xx fast ADC read implementation
|
||||
// 4.2.13 Broadcast power for <s> again
|
||||
// 4.2.12 Bugfix for issue #299 TurnoutDescription NULL
|
||||
// 4.2.11 Exrail IFLOCO feature added
|
||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||
// IO_EXIOExpander.h input speed optimisation
|
||||
// 4.2.9 duinoNodes support
|
||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
// Report BOARD_TYPE if provided by compiler
|
||||
// 4.2.7 FIX: Static IP addr
|
||||
// FIX: Reuse WiThrottle list entries
|
||||
// 4.2.6 FIX: Remove RAM thief
|
||||
// FIX: ADC port 8-15 fix
|
||||
// 4.2.5 Make GETFLASHW code more universal
|
||||
// FIX: Withrottle roster index
|
||||
// Ethernet start improvement and link detection
|
||||
// 4.2.4 ESP32 experimental BT support
|
||||
// More DC configurations possible and lower frequency
|
||||
// Handle decoders that do not ack at write better
|
||||
|
Reference in New Issue
Block a user